refactor: move weights to models/ + per-mode README; update paths, deploy, docs
ci / smoke (push) Canceled after 0s

This commit is contained in:
andrew committed 2026-09-11 15:52:38 +07:00
1 parent 6e4af50646
commit 262f7d064e
10 files changed
+157 -55

No files matched your search

+12 -8
View File
@@ -34,10 +34,13 @@ Indonesian (`karung`=sack, `truk`=truck); YOLO class names are English
tracker keeps `("sack", "truck", "box")` (`src/tracking.py`); counting uses
`MultiClassLineCounter` = dual `LineCrossCounter`s on one shared line
(`src/counting.py`). Same line geometry + 30px dedup for sacks and boxes.
- Verified checkpoint classes: `truck-detector`={truck}, `model_karung_truk`/`v4-best`=
{sack,truck}, `karung-dimuat-*-seg-200e`={person,sack} (seg; persons drawn, never
counted), `best`={sack}. `predict.py` auto-picks `MODEL_PATH` env, else
`model_karung_truk.engine` > `.pt` > `v4-best.pt`.
- All weights live in `models/` (untracked; per-mode detector/filter matrix in
`models/README.md`). Verified classes: `truck-detector`={truck},
`model_karung_truk`/`v4-best`={sack,truck}, `karung-dimuat-*-seg-200e`=
{person,sack} (seg; persons drawn, never counted), `best`={sack},
`yolo11n-…-sack+box`={sack,box}. `predict.py` auto-picks `MODEL_PATH` env, else
`models/v4-best.engine` > `.pt` > `v4-best (1).pt` > `models/model_karung_truk.*`.
`src/config.py` defaults: `models/best.engine` (sack) / `models/truck-detector.engine`.
- Counting trigger is the bbox **top edge (y1)** vs a truck-anchored line band
(`src/counting.py`, `src/truck_roi.py`); truck detect runs every 15th frame only.
- **Tests:** `python -m pytest tests/ -q` (smoke tests for `counting`, `batch`,
@@ -52,9 +55,10 @@ Indonesian (`karung`=sack, `truk`=truck); YOLO class names are English
## Deploy (Jetson `192.168.192.96`, user `jetson`)
- `deploy_to_jetson.py` syncs only `predict.py`, `counter_dashboard.py`,
`templates/{operator,monitoring,base}.html`, `.env`, then restarts services.
- `deploy_to_jetson.py` syncs `predict.py`, `counter_dashboard.py`,
`templates/{operator,monitoring,base}.html`, `.env`, **plus `models/*.engine`**
(skips missing local files; creates remote `models/`). `.pt`/`.onnx` stay local.
Edits elsewhere (e.g. `src/`, `zones.json`) need manual sync.
- Services: `karung-counter` (`predict.py`), `karung-counter-dashboard`
(`counter_dashboard.py`, ports 5000/5721). TensorRT export: `export_model.py`
(`export_v4.py` variant) — run on the Jetson, then repoint the loader at `.engine`.
(`counter_dashboard.py`, ports 5000/5721). TensorRT export:
`python export_model.py models/<name>.pt` — run on the Jetson.
+10 -6
View File
@@ -52,15 +52,19 @@ add SQLite persistence, live-frame publishing to `/dev/shm`, and zone polygons.
## Models
All weights live in `models/` (see [`models/README.md`](models/README.md) for the
per-mode detector/filter matrix).
| File | Classes | Role |
|---|---|---|
| `truck-detector.pt` (`.engine`) | `truck` | Specialized truck detector (ROI, batch lifecycle) |
| `model_karung_truk.pt` / `v4-best.pt` (`.engine`, `.onnx`) | `sack`, `truck` | Combined sack+truck model (legacy default in `predict.py`) |
| `karung-dimuat-detection-di-feedmill-yolo26n-seg-200e.pt` (`.engine`) | `person`, `sack` | Segmentation model: sacks counted, persons drawn/skipped |
| `yolo11n-bbox-100ep-sack+box-20260909-best.pt` | `sack`, `box` | New bbox model: sacks **and** boxes in one model |
| `best.pt` (`.engine`, `.onnx`) | `sack` | Sack-only baseline |
| `models/truck-detector.{pt,engine}` | `truck` | Specialized truck detector (ROI, batch lifecycle) |
| `models/v4-best.{pt,onnx,engine}` / `models/model_karung_truk.{pt,onnx,engine}` | `sack`, `truck` | Combined sack+truck (Modes A/B/C/D truck; Modes A/C sack) |
| `models/karung-dimuat-…-seg-200e.{pt,onnx,engine}` | `person`, `sack` | Segmentation model: sacks counted, persons drawn/skipped |
| `models/yolo11n-bbox-100ep-sack+box-20260909-best.{pt,onnx,engine}` | `sack`, `box` | Unified sack+box (Modes B/C/D) |
| `models/best.{pt,onnx,engine}` | `sack` | Sack-only specialist (Mode D sack) |
`.pt` = PyTorch, `.engine` = TensorRT FP16 (Jetson GPU), `.onnx` = ONNX export.
`.pt` = PyTorch, `.engine` = TensorRT FP16 (Jetson GPU, what production loads),
`.onnx` = ONNX export artifact.
See [`docs/models.md`](docs/models.md) for the multi-model / multi-class / hybrid
support matrix.
+24 -2
View File
@@ -1,13 +1,25 @@
import paramiko
import os
# Production loads .engine weights from models/ (see models/README.md).
# .pt/.onnx stay local-only (dev/export) — not synced to save bandwidth.
MODEL_ENGINES = [
"v4-best.engine",
"yolo11n-bbox-100ep-sack+box-20260909-best.engine",
"best.engine",
"truck-detector.engine",
"model_karung_truk.engine",
]
files_to_sync = [
("templates/operator.html", "/home/jetson/karung/templates/operator.html"),
("templates/monitoring.html", "/home/jetson/karung/templates/monitoring.html"),
("templates/base.html", "/home/jetson/karung/templates/base.html"),
("counter_dashboard.py", "/home/jetson/karung/counter_dashboard.py"),
("predict.py", "/home/jetson/karung/predict.py"),
(".env", "/home/jetson/karung/.env")
(".env", "/home/jetson/karung/.env"),
] + [
(f"models/{f}", f"/home/jetson/karung/models/{f}") for f in MODEL_ENGINES
]
def run():
@@ -16,9 +28,19 @@ def run():
client.connect('192.168.192.96', username='jetson', password='jetson', timeout=10)
sftp = client.open_sftp()
# Ensure remote models/ dir exists (sftp.put fails otherwise)
try:
sftp.stat("/home/jetson/karung/models")
except FileNotFoundError:
print("Creating remote /home/jetson/karung/models/ ...")
sftp.mkdir("/home/jetson/karung/models")
print("Uploading updated files to Jetson...")
for local_f, remote_f in files_to_sync:
if not os.path.exists(local_f):
print(f"SKIP (missing local file): {local_f}")
continue
print(f"Uploading {local_f} -> {remote_f}")
sftp.put(local_f, remote_f)
+3 -2
View File
@@ -20,7 +20,8 @@ Template: `.env.example`. Production values live in `.env` (git-ignored).
| `OFFICE_PORT` | `5721` | Second dashboard port |
| `FLASK_DEBUG` | `false` | Flask debug |
| `RTSP_URL` | — | Camera stream URL |
| `MODEL_PATH` | auto (`model_karung_truk.engine` > `.pt` > `v4-best.pt`) | Override combined-model weights (`predict.py:372`) |
| `MODEL_PATH` | auto (`models/v4-best.engine` > `.pt` > `v4-best (1).pt` > `models/model_karung_truk.engine` > `.pt`) | Override combined-model weights (`predict.py`) |
| `MODEL_MODE` | `B` | Model pipeline mode A/B/C/D (see `models/README.md`); also settable via `--model-mode` or dashboard (applies on restart) |
| `BATCH_MERGE_THRESHOLD_SECONDS` | `300` | Merge window for adjacent batches |
On Windows dev machines these resolve to `d:/Belajar/menghitung karung/...`.
@@ -32,7 +33,7 @@ On Windows dev machines these resolve to `d:/Belajar/menghitung karung/...`.
| Key | Default |
|---|---|
| `LOCAL_RTSP` / `JETSON_RTSP` | `""` |
| `MODEL_SACK_PATH` / `MODEL_TRUCK_PATH` | `./models/sack-model.pt`, `./models/truck-model.pt` |
| `MODEL_SACK_PATH` / `MODEL_TRUCK_PATH` | `./models/best.engine`, `./models/truck-detector.engine` |
| `COUNTING_LINE_Y` / `_X_START` / `_X_END` | `0.60` / `0.38` / `0.72` (fractions; initial line before ROI sync) |
| `SACK_CONF_THRESHOLD` / `TRUCK_CONF_THRESHOLD` | `0.40` / `0.50` |
| `BATCH_TIMEOUT_SECONDS` | `30` |
+7 -5
View File
@@ -21,14 +21,16 @@ systemctl status karung-counter karung-counter-dashboard --no-pager
## Deploy flow (`deploy_to_jetson.py`)
Paramiko sync of `templates/{operator,monitoring,base}.html`, `counter_dashboard.py`,
`predict.py`, `.env` → `192.168.192.96:/home/jetson/karung/`, then restarts both
`predict.py`, `.env` **plus `models/*.engine`** (v4-best, yolo11n-sack+box, best,
truck-detector, model_karung_truk) → `192.168.192.96:/home/jetson/karung/`
(creates remote `models/` if missing, skips missing local files), then restarts both
services and checks status + ports (5000/5721). Run from the dev machine.
`.pt`/`.onnx` stay local-only (dev/export).
## TensorRT export
On the Jetson (needs CUDA): `python3 export_model.py` exports
`karung-dimuat-detection-di-feedmill-yolo26n-seg-200e.pt` → FP16 `.engine`;
`export_v4.py` is the `v4-best` variant. Point `MODEL_PATH` / loader constants at the
`.engine` afterwards.
On the Jetson (needs CUDA): `python3 export_model.py models/<name>.pt` exports to
FP16 `.engine` next to the `.pt` (default: karung-dimuat seg model). Production
loads `.engine` only — see `models/README.md` for which weights each mode uses.
## Runtime data files
- SQLite `jetson_counter.db`: `batches(counting_date, batch_number, camera_name,
+18 -12
View File
@@ -6,14 +6,17 @@ see `export_model.py` / `export_v4.py`).
## Inventory
| File (+ `.engine` / `.onnx` where present) | Classes | Task | Used by |
All weights live in `models/` — full per-mode detector/filter matrix in
[`models/README.md`](../models/README.md).
| File | Classes | Task | Used by |
|---|---|---|---|
| `truck-detector.pt` | `{0: truck}` | bbox | Truck branch: `src/main.py`, `predict_new.py`, `rpo_iki/count.py::calibrate_truck_dynamically` |
| `model_karung_truk.pt` | `{0: sack, 1: truck}` | bbox | Legacy default combined model in `predict.py` (auto-picked `MODEL_PATH`) |
| `v4-best.pt` (`v4-best (1).pt` = copy) | `{0: sack, 1: truck}` | bbox | Alternate combined model |
| `karung-dimuat-detection-di-feedmill-yolo26n-seg-200e.pt` | `{0: person, 1: sack}` | **seg** | Sack segmentation + person exclusion |
| `yolo11n-bbox-100ep-sack+box-20260909-best.pt` (`.engine`) | `{0: sack, 1: box}` | bbox | Sack+box model, consumed via `BoxDetector` + shared tracker |
| `best.pt` | `{0: sack}` | seg | Sack-only baseline (`simple_predict.py`) |
| `models/truck-detector.{pt,engine}` | `{0: truck}` | bbox | Dedicated truck specialist; `src/config.py` default truck model |
| `models/model_karung_truk.{pt,onnx,engine}` | `{0: sack, 1: truck}` | bbox | Legacy combined alias (auto-pick fallback in `predict.py`) |
| `models/v4-best.{pt,onnx,engine}` (`v4-best (1).pt` = duplicate copy) | `{0: sack, 1: truck}` | bbox | Combined model (Modes A/B/C/D truck; Modes A/C sack) |
| `models/karung-dimuat-detection-di-feedmill-yolo26n-seg-200e.{pt,onnx,engine}` | `{0: person, 1: sack}` | **seg** | Person-exclusion seg model (legacy `predict_new.py`) |
| `models/yolo11n-bbox-100ep-sack+box-20260909-best.{pt,onnx,engine}` | `{0: sack, 1: box}` | bbox | Unified sack+box (Modes B/C/D) |
| `models/best.{pt,onnx,engine}` | `{0: sack}` | seg | Sack-only specialist (Mode D sack; `src/config.py` default) |
Model registry for the `rpo_iki` engine: `rpo_iki/configs/model_registry.json`
(currently pins `karung-dimuat-seg-200e`, mAP50-mask 0.899, val MAE 0.67).
@@ -59,9 +62,12 @@ Two dedicated models run in parallel on the same frames:
events tagged with `class_name`; sack and box track IDs live in separate spaces.
## Choosing / swapping a model
- `src/` pipeline: `MODEL_SACK_PATH` / `MODEL_TRUCK_PATH` env vars (see
- `src/` pipeline: `MODEL_SACK_PATH` / `MODEL_TRUCK_PATH` env vars (defaults:
`./models/best.engine` / `./models/truck-detector.engine`; see
[`configuration.md`](configuration.md)) or `--source` for files.
- `predict.py`: `MODEL_PATH` env var, else auto-picks `model_karung_truk.engine` >
`model_karung_truk.pt` > `v4-best.pt`.
- TensorRT: after training, run `export_model.py` (FP16 `.engine`) on the Jetson and
point the loader at the `.engine` file.
- `predict.py`: `MODEL_PATH` env var, else auto-picks `models/v4-best.engine` >
`models/v4-best.pt` > `models/v4-best (1).pt` > `models/model_karung_truk.engine` >
`models/model_karung_truk.pt`; `--model-mode A|B|C|D` (or `MODEL_MODE` env) picks
the pipeline; `--box-model` overrides the yolo11n weights.
- TensorRT: `python export_model.py models/<name>.pt` (FP16 `.engine`) on the Jetson;
production loads `.engine` only. `deploy_to_jetson.py` syncs the `.engine` files.
+9 -2
View File
@@ -1,11 +1,18 @@
from ultralytics import YOLO
import torch
import os
import sys
# All weights live in models/ (see models/README.md).
MODELS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "models")
def main():
model_path = "karung-dimuat-detection-di-feedmill-yolo26n-seg-200e.pt"
# Usage: python export_model.py [model.pt] (default: karung-dimuat seg model)
default_model = os.path.join(MODELS_DIR, "karung-dimuat-detection-di-feedmill-yolo26n-seg-200e.pt")
model_path = sys.argv[1] if len(sys.argv) > 1 else default_model
if not os.path.exists(model_path):
print(f"Error: {model_path} tidak ditemukan di folder ini!")
print(f"Error: {model_path} tidak ditemukan!")
print(f"Usage: python export_model.py [path-ke-model.pt]")
return
print("=" * 60)
+54
View File
@@ -0,0 +1,54 @@
# Model Zoo
All weights live here. Three formats coexist per model:
- `.pt` — PyTorch checkpoint (training source, dev, re-export)
- `.onnx` — ONNX intermediate (export pipeline artifact)
- `.engine` — TensorRT FP16, **what production loads on the Jetson**
Class names below are read directly from each checkpoint (`YOLO(path).names`).
## Inventory
| File | Classes | Task | Role |
|---|---|---|---|
| `truck-detector.{pt,engine}` | `{0: truck}` | bbox | Dedicated truck specialist (ROI, batch lifecycle) |
| `v4-best.{pt,onnx,engine}` (`v4-best (1).pt` = duplicate copy) | `{0: sack, 1: truck}` | bbox | Combined sack+truck (Modes A/B/C/D truck; Modes A/C sack) |
| `model_karung_truk.{pt,onnx,engine}` | `{0: sack, 1: truck}` | bbox | Legacy combined alias (auto-pick fallback) |
| `yolo11n-bbox-100ep-sack+box-20260909-best.{pt,onnx,engine}` | `{0: sack, 1: box}` | bbox | Unified sack+box (Modes B/C/D) |
| `best.{pt,onnx,engine}` | `{0: sack}` | seg | Sack-only specialist (Mode D sack) |
| `karung-dimuat-detection-di-feedmill-yolo26n-seg-200e.{pt,onnx,engine}` | `{0: person, 1: sack}` | **seg** | Person-exclusion seg model (legacy `predict_new.py`) |
## Model Modes (`predict.py --model-mode`, default **B**)
Counting filters by **class name, not ID** (`BaseDetector(class_filter)` in
`src/detection.py`; tracker allow-list in `src/tracking.py`; dual counters in
`src/counting.py`). "Filtered out" = class exists in the checkpoint but is
dropped before tracking/counting.
| Mode | Truck detector (filter) | Sack detector (filter) | Box detector (filter) |
|---|---|---|---|
| **A** | `v4-best.engine` → keep `truck`, drop `sack` | `v4-best.engine` (shared) → keep `sack`, drop `truck` | — (no box counting) |
| **B** (default) | `v4-best.engine` → keep `truck`, drop `sack` | `yolo11n-….engine` → keep `sack`, drop `box` | `yolo11n-….engine` (shared with sack) → keep `box`, drop `sack` |
| **C** | `v4-best.engine` → keep `truck`, drop `sack` | `v4-best.engine` (shared) → keep `sack`, drop `truck` | `yolo11n-….engine` → keep `box`, drop `sack` |
| **D** | `v4-best.engine` → keep `truck`, drop `sack` | `best.engine` → `sack` only (nothing to drop) | `yolo11n-….engine` → keep `box`, drop `sack` |
Notes:
- Mode B loads **one** yolo11n handle shared by the sack and box paths (single
tracker, single track-ID space); modes C/D run a dedicated box tracker
(separate ID space + own stabilizer, so sack/box IDs never collide).
- All `.engine` files verified to coexist: 2 engines ~16 MB, 3 engines ~24 MB
peak of 7.6 GB Jetson GPU. If `.pt` is ever used, load PyTorch models **before**
TensorRT engines or CUDA init fails.
- Mode switches via dashboard persist to `batch_mode.json` and apply on next
`karung-counter` restart (models load once at startup).
## Exporting
```bash
python export_model.py models/<name>.pt # → FP16 .engine next to the .pt
```
## Deploy
`deploy_to_jetson.py` syncs the `.engine` files only (what production loads);
`.pt`/`.onnx` stay local for dev/export.
+15 -13
View File
@@ -444,20 +444,22 @@ last_seen_near_person_frame = {}
blocked_due_to_duplicate = {}
# --- Model Paths ---
# All weights live in models/ (see models/README.md for mode/filter matrix).
# Model gabungan karung + truk terbaru (v4-best TensorRT / PyTorch)
_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
_MODELS_DIR = os.path.join(_BASE_DIR, "models")
_env_model = os.getenv("MODEL_PATH")
if _env_model and os.path.exists(_env_model):
COMBINED_MODEL_PATH = _env_model
else:
_candidates = [
os.path.join(_BASE_DIR, "v4-best.engine"),
os.path.join(_BASE_DIR, "v4-best.pt"),
os.path.join(_BASE_DIR, "v4-best (1).pt"),
os.path.join(_BASE_DIR, "model_karung_truk.engine"),
os.path.join(_BASE_DIR, "model_karung_truk.pt"),
os.path.join(_MODELS_DIR, "v4-best.engine"),
os.path.join(_MODELS_DIR, "v4-best.pt"),
os.path.join(_MODELS_DIR, "v4-best (1).pt"),
os.path.join(_MODELS_DIR, "model_karung_truk.engine"),
os.path.join(_MODELS_DIR, "model_karung_truk.pt"),
]
COMBINED_MODEL_PATH = next((p for p in _candidates if os.path.exists(p)), "v4-best.pt")
COMBINED_MODEL_PATH = next((p for p in _candidates if os.path.exists(p)), os.path.join(_MODELS_DIR, "v4-best.pt"))
def _pick_box_model(explicit: str | None) -> str:
@@ -465,23 +467,23 @@ def _pick_box_model(explicit: str | None) -> str:
if explicit:
return explicit
for cand in (
os.path.join(_BASE_DIR, "yolo11n-bbox-100ep-sack+box-20260909-best.engine"),
os.path.join(_BASE_DIR, "yolo11n-bbox-100ep-sack+box-20260909-best.pt"),
os.path.join(_MODELS_DIR, "yolo11n-bbox-100ep-sack+box-20260909-best.engine"),
os.path.join(_MODELS_DIR, "yolo11n-bbox-100ep-sack+box-20260909-best.pt"),
):
if os.path.exists(cand):
return cand
return os.path.join(_BASE_DIR, "yolo11n-bbox-100ep-sack+box-20260909-best.engine")
return os.path.join(_MODELS_DIR, "yolo11n-bbox-100ep-sack+box-20260909-best.engine")
def _pick_sack_only_model() -> str:
"""Resolve best.pt sack-only weights: .engine > .pt."""
"""Resolve best sack-only weights: .engine > .pt."""
for cand in (
os.path.join(_BASE_DIR, "best.engine"),
os.path.join(_BASE_DIR, "best.pt"),
os.path.join(_MODELS_DIR, "best.engine"),
os.path.join(_MODELS_DIR, "best.pt"),
):
if os.path.exists(cand):
return cand
return os.path.join(_BASE_DIR, "best.engine")
return os.path.join(_MODELS_DIR, "best.engine")
# --- Model pipeline modes -------------------------------------------------
+5 -5
View File
@@ -17,9 +17,9 @@ class Config:
local_rtsp: str = ""
jetson_rtsp: str = ""
# Model paths
sack_model_path: str = "./models/sack-model.pt"
truck_model_path: str = "./models/truck-model.pt"
# Model paths (canonical production weights in models/; see models/README.md)
sack_model_path: str = "./models/best.engine"
truck_model_path: str = "./models/truck-detector.engine"
# Counting line (fractions of frame dimensions)
counting_line_y: float = 0.60
@@ -45,9 +45,9 @@ def load_config(env_path: str = ".env") -> Config:
return Config(
local_rtsp=os.getenv("LOCAL_RTSP", ""),
jetson_rtsp=os.getenv("JETSON_RTSP", ""),
sack_model_path=os.getenv("MODEL_SACK_PATH", "./models/sack-model.pt"),
sack_model_path=os.getenv("MODEL_SACK_PATH", "./models/best.engine"),
truck_model_path=os.getenv(
"MODEL_TRUCK_PATH", "./models/truck-model.pt"
"MODEL_TRUCK_PATH", "./models/truck-detector.engine"
),
counting_line_y=float(os.getenv("COUNTING_LINE_Y", "0.60")),
counting_line_x_start=float(