Files
chicken-weight-estimator/README.md
T
2026-07-29 14:55:55 +07:00

284 lines
11 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.
# 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**.