first commit
This commit is contained in:
commit
c666715e29
1 file changed
+283
@@ -0,0 +1,283 @@
|
||||
# Weight Estimator
|
||||
|
||||
Offline experiment pipeline for estimating daily average broiler weight per cage
|
||||
from cam2/cam3 top-down video and physical scale readings.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
cd "/media/asus/One Touch/LABS/weight-estimator/try-weight-chicken"
|
||||
python -m pip install -e .
|
||||
|
||||
# Chicken counter (YOLO + BoT-SORT) — required for extract / QA / predict:
|
||||
python -m pip install -e "/media/asus/One Touch/LABS/chicken-counting/chicken-sukawarna-existing"
|
||||
# Optional Jetson extras:
|
||||
python -m pip install -e ".[jetson]"
|
||||
```
|
||||
|
||||
**Venv used on this machine:**
|
||||
|
||||
```bash
|
||||
source /media/asus/DATA/wchicken/bin/activate
|
||||
cd "/media/asus/One Touch/LABS/weight-estimator/try-weight-chicken"
|
||||
```
|
||||
|
||||
All commands below assume that venv is active and you are in `try-weight-chicken`.
|
||||
Config default: `configs/weight_cc2_cc3.yaml`.
|
||||
|
||||
## Command guide
|
||||
|
||||
### CLI map
|
||||
|
||||
| Command | Purpose | GPU? |
|
||||
|---------|---------|------|
|
||||
| `audit` | CSV ↔ video path check | no |
|
||||
| `calibration-audit` | Save calibration frames | no |
|
||||
| `validate-calibration` | Homography error check | no |
|
||||
| `extract` | Unique-track feature extract → `detections.parquet` | **yes** |
|
||||
| `aggregate` | Detections → `cage_day_features.parquet` | no |
|
||||
| `train` | Fit vision-primary model → `model_bundle.joblib` | no |
|
||||
| `evaluate` | Late-holdout GO/NO_GO + plots | no |
|
||||
| `filter-overlay` | JPG overlays or single QA MP4 | **yes** |
|
||||
| `qa-review-videos` | Fixed 5-date × CC2/CC3 review MP4s | **yes** |
|
||||
| `predict` | One-day average weight from video(s) | **yes** |
|
||||
| `compare-filters` | Strict vs legacy filter report | no |
|
||||
| `run-all` | Full pipeline (optional `--synthetic`) | maybe |
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli --help
|
||||
python -m weight_estimator.cli <command> --help
|
||||
# If entrypoint installed:
|
||||
weight-estimator <command> --help
|
||||
```
|
||||
|
||||
### 1. Audit / calibration
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli audit --config configs/weight_cc2_cc3.yaml
|
||||
python -m weight_estimator.cli calibration-audit --config configs/weight_cc2_cc3.yaml
|
||||
python -m weight_estimator.cli validate-calibration --config configs/weight_cc2_cc3.yaml
|
||||
```
|
||||
|
||||
### 2. Full extract (unique chicken / track along video)
|
||||
|
||||
Extract runs **BoT-SORT every frame**, keeps **one best-confidence observation per `track_id`**,
|
||||
target **400 unique tracks** per camera/day, single forward pass, stops on target **or** optical-flow reverse.
|
||||
|
||||
```bash
|
||||
mkdir -p logs
|
||||
# Backup previous detections
|
||||
cp -a output/detections.parquet "output/detections_pre_extract_$(date +%Y%m%d_%H%M).parquet"
|
||||
|
||||
tmux kill-session -t weight-extract 2>/dev/null || true
|
||||
STAMP=$(date +%Y%m%d_%H%M)
|
||||
tmux new-session -d -s weight-extract \
|
||||
"cd '/media/asus/One Touch/LABS/weight-estimator/try-weight-chicken' && \
|
||||
source /media/asus/DATA/wchicken/bin/activate && \
|
||||
PYTHONUNBUFFERED=1 python -m weight_estimator.cli extract \
|
||||
--config configs/weight_cc2_cc3.yaml \
|
||||
2>&1 | tee logs/extract_${STAMP}.log; \
|
||||
echo EXIT_CODE=\$? | tee -a logs/extract_${STAMP}.log; exec bash"
|
||||
|
||||
tail -f logs/extract_${STAMP}.log
|
||||
# Done when you see: Extracted N detections -> .../detections.parquet and EXIT_CODE=0
|
||||
```
|
||||
|
||||
Useful log lines:
|
||||
|
||||
- `unique_track_mode track_every_n=1 … keep_best=True multipass=False target=400`
|
||||
- `reverse stop at frame=N`
|
||||
- `done valid=… unique=… replaced=… inferred=…`
|
||||
|
||||
Key YAML knobs (`sampling:` in `configs/weight_cc2_cc3.yaml`):
|
||||
|
||||
| Knob | Typical | Meaning |
|
||||
|------|---------|---------|
|
||||
| `track_every_n_frames` | `1` | YOLO/track every N frames |
|
||||
| `per_track_cap` | `1` | Max stored obs per track_id |
|
||||
| `keep_best_per_track` | `true` | Keep highest-confidence obs |
|
||||
| `target_detections` / late | `400` | Unique-track target |
|
||||
| `allow_stride_multipass` | `false` | No video re-open (keeps IDs) |
|
||||
| `stop_on_backward` | `true` | Optical-flow reverse end |
|
||||
|
||||
### 3. Aggregate → train → evaluate (after extract)
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli aggregate --config configs/weight_cc2_cc3.yaml
|
||||
python -m weight_estimator.cli train --config configs/weight_cc2_cc3.yaml --model ridge
|
||||
python -m weight_estimator.cli evaluate --config configs/weight_cc2_cc3.yaml
|
||||
```
|
||||
|
||||
- **GO** if late-holdout MAPE ≤ 15% vs consensus (`berat_aktual_g`).
|
||||
- Report: `output/validation_report.json`
|
||||
- Bundle: `output/model_bundle.joblib`
|
||||
|
||||
Re-apply size MAD only (no re-extract):
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli aggregate --config configs/weight_cc2_cc3.yaml --refilter-size
|
||||
```
|
||||
|
||||
### 4. QA review videos (visual check of valid / unique OK)
|
||||
|
||||
Writes overlay MP4s for fixed dates: cycle days **4, 15, 21, 30, 47** × **CC2 + CC3** (10 files).
|
||||
|
||||
Behavior matches extract tracking:
|
||||
|
||||
- Track **every frame**
|
||||
- HUD: `unique_OK=N/400` (cumulative unique accepted `track_id`s) + `frame_OK` / `rej`
|
||||
- Boxes: `OK#track_id`
|
||||
- Stops at **target 400** (same as extract) or reverse-stop if target not reached
|
||||
- Encodes every `--frame-stride` frames (default 10)
|
||||
|
||||
```bash
|
||||
mkdir -p logs output/qa_review_videos
|
||||
STAMP=$(date +%Y%m%d_%H%M)
|
||||
tmux kill-session -t weight-qa-videos 2>/dev/null || true
|
||||
tmux new-session -d -s weight-qa-videos \
|
||||
"cd '/media/asus/One Touch/LABS/weight-estimator/try-weight-chicken' && \
|
||||
source /media/asus/DATA/wchicken/bin/activate && \
|
||||
PYTHONUNBUFFERED=1 python -m weight_estimator.cli qa-review-videos \
|
||||
--config configs/weight_cc2_cc3.yaml \
|
||||
--output-dir output/qa_review_videos \
|
||||
--frame-stride 10 \
|
||||
2>&1 | tee logs/qa_review_${STAMP}.log; \
|
||||
echo EXIT_CODE=\$? | tee -a logs/qa_review_${STAMP}.log; exec bash"
|
||||
|
||||
tail -f logs/qa_review_${STAMP}.log
|
||||
```
|
||||
|
||||
Outputs:
|
||||
|
||||
- `output/qa_review_videos/{band}_day{N}_{date}_{CCx}.mp4`
|
||||
- `output/qa_review_videos/qa_review_summary.json`
|
||||
|
||||
Single-video QA MP4 (same unique-OK / target logic):
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli filter-overlay \
|
||||
--config configs/weight_cc2_cc3.yaml \
|
||||
--video "/media/asus/One Touch/VIDEOS/cycle7/kandang-atas/2026-05-26/kandang_1_camera_2_2026-05-26_120057.mp4" \
|
||||
--camera-id CC2 \
|
||||
--cycle-day 4 \
|
||||
--frame-stride 10 \
|
||||
--write-video output/qa_review_videos/manual_day4_CC2.mp4
|
||||
```
|
||||
|
||||
JPG frame overlays only (legacy tuning):
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli filter-overlay \
|
||||
--config configs/weight_cc2_cc3.yaml \
|
||||
--video /path/to/video.mp4 \
|
||||
--camera-id CC2 \
|
||||
--cycle-day 15 \
|
||||
--frame-stride 30 \
|
||||
--max-frames 50 \
|
||||
--output-dir output/filter_overlays
|
||||
```
|
||||
|
||||
### 5. Predict (daily use)
|
||||
|
||||
Formula: **`DOC + max(0, vision_gain(features))`** (vision-primary). DOC is required.
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli predict \
|
||||
--config configs/weight_cc2_cc3.yaml \
|
||||
--doc-weight 32 \
|
||||
--doc-date 2026-05-22 \
|
||||
--date 2026-06-15 \
|
||||
--model output/model_bundle.joblib \
|
||||
--cc2 /path/to/kandang_1_camera_2_XXXX.mp4 \
|
||||
--cc3 /path/to/kandang_1_camera_3_XXXX.mp4 \
|
||||
--output-dir output/predict_2026-06-15
|
||||
```
|
||||
|
||||
Writes `prediction.json` under `--output-dir`.
|
||||
|
||||
### 6. tmux helpers
|
||||
|
||||
```bash
|
||||
tmux ls
|
||||
tmux attach -t weight-extract # or weight-qa-videos
|
||||
# detach: Ctrl+b then d
|
||||
tmux kill-session -t weight-extract
|
||||
tmux kill-session -t weight-qa-videos
|
||||
nvidia-smi
|
||||
pgrep -af 'weight_estimator.cli'
|
||||
```
|
||||
|
||||
### 7. Comparison Excel (Manual / IoT / Vision)
|
||||
|
||||
After evaluate, refresh comparison under `output/comparison_consensus/`
|
||||
(e.g. `Perbandingan_Manual_IoT_Vision.xlsx`: columns IoT → Manual → Vision,
|
||||
akurasi = 100% − |error|, green/yellow/red **cell background**).
|
||||
|
||||
Typical path after a new extract:
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli aggregate --config configs/weight_cc2_cc3.yaml
|
||||
python -m weight_estimator.cli train --config configs/weight_cc2_cc3.yaml --model ridge
|
||||
python -m weight_estimator.cli evaluate --config configs/weight_cc2_cc3.yaml
|
||||
# then regenerate Perbandingan Excel from prediction_vs_manual_iot.csv (agent/script)
|
||||
```
|
||||
|
||||
## Quick start (synthetic demo)
|
||||
|
||||
When videos are not available locally:
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli run-all --config configs/weight_cc2_cc3.yaml --synthetic
|
||||
```
|
||||
|
||||
Outputs land in `output/`:
|
||||
|
||||
- `audit_report.json`
|
||||
- `calibration_audit.json`
|
||||
- `detections.parquet`
|
||||
- `cage_day_features.parquet`
|
||||
- `model_bundle.joblib`
|
||||
- `validation_report.json`
|
||||
- `bland_altman_*.png`
|
||||
|
||||
## Configuration
|
||||
|
||||
- [`configs/weight_cc2_cc3.yaml`](configs/weight_cc2_cc3.yaml) — data paths, sampling, quality filters
|
||||
- [`configs/calibration/CC2_homography.yaml`](configs/calibration/CC2_homography.yaml) — CC2 pixel→cm points
|
||||
- [`configs/calibration/CC3_homography.yaml`](configs/calibration/CC3_homography.yaml) — CC3 pixel→cm points
|
||||
- Chicken batch / YOLO: [`cycle7_batch.yaml`](../../chicken-counting/chicken-sukawarna-existing/configs/cycle7_batch.yaml)
|
||||
|
||||
Labels (consensus Manual+IoT): `data/historical_weights_manual_iot_review.csv`
|
||||
|
||||
## Quality filters
|
||||
|
||||
Detections must pass **all** frame tiers before entering the weight sample:
|
||||
|
||||
1. **Class gate** — class 0 only; reject not-chicken / half-chicken
|
||||
2. **Containment** — ROI overlap + sampling corridors
|
||||
3. **Isolation** — max IoU and min centroid distance
|
||||
4. **Shape** — confidence, aspect ratio, area vs frame median
|
||||
5. **Frame gate** — clean_ratio and ≥ 1 accepted bird
|
||||
6. **Size distribution (second filter)** — per camera-day MAD z-score on `area_cm2`
|
||||
|
||||
Rejection breakdown: `output/filter_report.json`.
|
||||
|
||||
**YOLO vs post-filter:** YOLO `conf` / `iou` live in `cycle7_batch.yaml`. Weight `quality.conf_min` must stay aligned with batch `early_cycle`. Re-extract after changing either.
|
||||
|
||||
```bash
|
||||
python -m weight_estimator.cli compare-filters --config configs/weight_cc2_cc3.yaml
|
||||
```
|
||||
|
||||
## Design notes
|
||||
|
||||
- **DOC-essential:** labels require Cycle ID, DOC Date, DOC Weight (g). Age = `(Tanggal − DOC Date).days`.
|
||||
- **Vision-primary:** `predicted_avg_g = DOC + max(0, vision_gain(features))`. Age-only / Gompertz is **not** used for GO/NO_GO or comparison reports.
|
||||
- **GO/NO_GO:** late-cycle holdout MAPE ≤ 15% vs consensus (`berat_aktual_g`).
|
||||
- **Unit of analysis:** one row per `(cage, date)`; detections are aggregated, not individually labeled.
|
||||
- **Unique-track sampling:** track every frame; `per_track_cap: 1` + `keep_best_per_track`; target **400** unique tracks; `stop_on_backward`; no multipass re-open. Diversity should be ~1.0. During extract, `extract_overlay_every_n_frames: 1000` saves frame-tier overlays to `output/extract_filter_overlays/{date}_{camera}/`.
|
||||
- **Size distribution:** MAD z-score on `area_cm2`. Band `z_max`: early **3.0** (≤17), mid **2.5** (18–24), late **2.0** (≥25). Tune with `aggregate --refilter-size` when only size gates change.
|
||||
- **Early / mid cycle:** `early_cycle.day_max` **17**, `mid_cycle` **18–24**, then late/strict. Batch early YOLO: `conf` 0.12.
|
||||
- **Camera fusion:** gate cams below `fuse_min_detections`, weight by `detection_count`; else best cam. Re-aggregate after fusion changes (no re-extract).
|
||||
- **Segmentation:** YOLO-seg via `detection.model_path`; mask geometry preferred with bbox fallback. Production features: `features.preset: mask_max`.
|
||||
- **Train ages:** **3–35**. Evaluate late holdout **29–35** (fit on **3–28**).
|
||||
- **Excluded days:** `exclude_cycle_days` (cycle7: 0–2). DOC weight examples: **32 g**.
|
||||
Reference in new issue
Block a user