Metadata-Version: 2.4
Name: chicken-counter
Version: 0.1.0
Summary: First-pass Jetson chicken counting pipeline with YOLO and BoT-SORT.
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.26
Requires-Dist: opencv-python>=4.10
Requires-Dist: PyYAML>=6.0.2
Requires-Dist: ultralytics>=8.4.38

# Chicken Counter

First-pass Python pipeline for Jetson-style chicken counting using Ultralytics YOLO
tracking with BoT-SORT, ROI/gate-based counting, backward-motion detection from
background optical flow, and an OpenCV overlay that matches the provided reference
visual.

## What Is Included

- Modular runtime under `src/chicken_counter/`
- Sample camera config in `configs/cameras/example_camera.yaml`
- BoT-SORT tracker settings in `configs/trackers/botsort_chicken.yaml`
- CLI entrypoint: `chicken-counter`

## Pipeline Stages

1. Capture frames from a video file or camera stream
2. Run `model.track(..., persist=True)` with class filtering for chickens only
3. Maintain per-track state, track live occupancy inside the counting box, and count unique box entries
4. Estimate backward motion from sparse optical flow on background features
5. Render an OpenCV overlay with ROI, white gates, IDs, trails, and both live/cumulative counts

## Project Layout

```text
configs/
  cameras/example_camera.yaml
  cycle7_batch.yaml
  trackers/botsort_chicken.yaml
src/chicken_counter/
  batch_discovery.py
  batch_runner.py
  capture.py
  cli.py
  compress.py
  config.py
  counting.py
  motion.py
  overlay.py
  pipeline.py
  report.py
  tracking.py
  types.py
  video_writer.py
```

## Install

```bash
python -m pip install -e .
```

For Jetson deployment you will usually want a Jetson-compatible OpenCV and PyTorch
stack already installed, then install the rest of the package around that environment.

## Run

Update `configs/cameras/example_camera.yaml` with:

- `source`: your input video path, RTSP URL, or camera index
- `detection.model_path`: your TensorRT `.engine` or `.pt` checkpoint
- ROI coordinates and gate lines calibrated for the real camera

Then run:

```bash
chicken-counter --config configs/cameras/example_camera.yaml
```

Press `q` to quit the preview window.

For headless Jetson MP4 runs, set `display.show_window: false` and keep
`display.output_path` enabled so the annotated video is written without opening a GUI.

The video writer tries a Jetson GStreamer hardware encoder first when
`display.encoder: auto` or `gstreamer`, then falls back to OpenCV codecs in
`display.codec_preference` order (default: `avc1`, `mp4v`, `H264`).

## Config Notes

### Detection

The sample config restricts inference to class `0` and keeps ignored classes explicit:

- `classes: [0]`
- `ignored_classes: [1, 2]`
- `conf` and `iou` are exposed for real-footage tuning
- `min_box_area_px` can be used to reject very small partial detections from validation
- `device: "0"` should be set explicitly on Jetson CUDA
- `imgsz` must match the size used when a TensorRT `.engine` was exported

For TensorRT deployments, point `detection.model_path` at your `.engine` file and keep
`performance.half: false` (precision is already baked into the engine build).

### Tracking

The supplied tracker config enables:

- `tracker_type: botsort`
- `gmc_method: none` for fixed-camera MP4 runs (avoids duplicate optical flow)
- `with_reid: false`

Re-enable `gmc_method: sparseOptFlow` in `configs/trackers/botsort_chicken.yaml` only if
the camera mount moves or footage is shaky enough that track IDs drift without GMC.

Starting thresholds match the prompt defaults and can be tuned in
`configs/trackers/botsort_chicken.yaml`.

### Periodic Runtime Feedback

You can enable checkpoint-style progress feedback every `N` frames with the `feedback`
config block:

```yaml
feedback:
  enabled: true
  every_n_frames: 300
  save_images: true
  image_output_dir: output/checkpoints
  log_to_terminal: true
```

When enabled, the pipeline will:

- print a periodic progress line with frame number, elapsed time, processing FPS, ETA, and total count
- save the current annotated frame as a checkpoint image (when `save_images: true`)

This is especially useful on Jetson when processing MP4 files headlessly, because you
can verify progress from the terminal and inspect saved snapshot images without needing
an on-device display.

### Counting ROI And Gates

The overlay is intended to resemble the reference image while staying easy to read:

- no outer green ROI outline
- one visible counting rectangle that is slightly smaller and cleaner than the previous broad region
- orange chicken bounding boxes that are visually distinct from the counting guides
- per-bird numeric labels based on count sequence, not raw tracker ID, using a non-white color
- short centroid trails
- one bold `TOTAL ENTERED` caption as the main count display, using a non-white color

The green ROI should be treated as the actual middle counting box. The current counting
semantics are:

- `Inside Box`: how many currently tracked chickens have their centroids inside the ROI
- `Total Entered`: how many unique tracked chickens have entered the ROI at least once
- a chicken is only valid for `Total Entered` if its bounding-box area meets `min_box_area_px`
- if backward motion is confirmed, the current frame is finalized and then the pipeline stops
- validated chickens receive a stable visible sequence number `1, 2, 3, ...` in entry order
- unvalidated chickens are tracked internally but do not show a visible sequence number yet

The implementation still assumes normal travel is `bottom_to_up`.

## Calibration Workflow

1. Start with a representative frame from the real camera.
2. Set `roi.points` so the counting rectangle spans the intended middle counting box only.
3. If the displayed rectangle feels too large or small, tighten or expand `roi.points` directly.
4. Run a short clip and compare `Inside Box` against the visible birds currently in that box.
5. Increase `min_box_area_px` if small partial chickens are being counted too early.
6. Verify `Total Entered` only increases when a new tracked bird enters the box during forward motion and is large enough to be valid.
6. Verify that once backward motion is confirmed, the output video ends at that point and the MP4 is finalized cleanly.
7. Verify that the highest displayed sequence number matches `Total Entered`.
8. Verify the final freeze frame stays on screen long enough to read the last total clearly.

## Backward-Motion Tuning

The stop trigger is separate from chicken tracks. It measures background motion while
masking detected chicken boxes.

Tune these values against real footage:

- `motion.forward_sign`
- `motion.ema_alpha`
- `motion.reverse_enter_threshold`
- `motion.reverse_exit_threshold`
- `motion.debounce_frames`
- `motion.min_features`
- `motion.stride_frames` (run flow every N frames; `2` is faster)
- `motion.flow_scale` (downscale ROI gray before flow; `0.5` is faster)
- `motion.max_corners` (fewer corners = faster; try `80`)

Important: confirm the actual sign convention from real cart footage before treating
the configured forward direction as final.

## Jetson Performance Speedups

For long batch runs, enable inference and motion stride in config:

```yaml
performance:
  inference_stride: 2   # run YOLO+BoT-SORT every 2nd frame; reuse tracks in between
motion:
  stride_frames: 2      # run optical flow every 2nd frame
  flow_scale: 0.5       # half-resolution flow inside ROI crop
  max_corners: 80
detection:
  imgsz: 640            # keep 640 while using existing TensorRT .engine
```

`configs/cycle7_batch.yaml` already uses these production defaults.

**Validation:** run a short clip with stride enabled, then compare `total_entered` against
`inference_stride: 1` and `motion.stride_frames: 1`. Watch checkpoint `fps=` logs for
speedup. Box positions may lag by up to one frame on skipped inference frames.

Set `inference_stride: 1` or `motion.stride_frames: 1` to restore full per-frame accuracy
for tuning.

## Known Limits In This First Pass

- No DeepStream integration yet
- No multi-process or multi-camera scheduler yet
- Counting currently assumes vertical motion and `bottom_to_up` travel
- The live box count depends on stable tracking centroids inside the ROI
- The optical-flow trigger is vision-first, though the config structure leaves room for
  a future controller/encoder integration path

## Headless Jetson MP4 Example

For a headless run that saves both output video and periodic checkpoint images, use a
config shaped like this:

```yaml
display:
  show_window: false
  output_path: output/coop_cam_03_overlay.mp4
  encoder: auto
  output_bitrate_kbps: 4000
feedback:
  enabled: true
  every_n_frames: 300
  save_images: true
  image_output_dir: output/checkpoints
  log_to_terminal: true
```

## 40-Minute Jetson Recipe

For long headless runs (~72,000 frames at 30 FPS), use the production-oriented settings
in `configs/cameras/example_camera.yaml`:

```yaml
detection:
  device: "0"
  imgsz: 640
  model_path: /path/to/your-model.engine
overlay:
  show_track_trails: false
  show_track_ring: false
motion:
  max_corners: 80
  stride_frames: 2
  flow_scale: 0.5
display:
  show_window: false
  output_path: /media/jetson/DATA/try-sukawarna-vis.mp4
  encoder: auto
  output_bitrate_kbps: 4000
performance:
  half: false
  overlay_buffer_reuse: true
  inference_stride: 2
feedback:
  enabled: true
  every_n_frames: 900
  log_to_terminal: true
  save_images: false
```

Tracker YAML should use `gmc_method: none` for fixed-camera footage.

Lower `display.output_bitrate_kbps` produces smaller MP4 files with more compression
artifacts. Start at `4000` and adjust after inspecting output quality.

Checkpoint logs look like:

```text
[checkpoint] frame=9000/72000 elapsed=18m12s fps=8.2 total_entered=142 eta=2h05m status=running
```

When backward motion is confirmed, the pipeline now:

- finishes the current annotated frame
- writes that frame to the output video
- logs the backward-stop event
- appends a short freeze frame so the final total is readable
- exits immediately afterward, so the output MP4 ends there

## Daily Cycle7 Multi-Camera Batch

For everyday processing of 4 cameras, use `configs/cycle7_batch.yaml`.

### Input folder layout

Place today's videos under:

```text
/media/jetson/DATA/chicken-sukawarna/cycle7/2026-07-09/
  kandang_1_camera_1_2026-07-09_120056.mp4
  kandang_1_camera_2_2026-07-09_120456.mp4
  kandang_1_camera_3_2026-07-09_121012.mp4
  kandang_1_camera_4_2026-07-09_121530.mp4
```

Date folders use `YYYY-MM-DD`. Camera files are matched by `camera_num` using the
pattern `kandang_*_camera_{num}_*.mp4`.

### Run commands

```bash
# Process today's folder
chicken-counter batch --config configs/cycle7_batch.yaml

# Process a specific date
chicken-counter batch --config configs/cycle7_batch.yaml --date 2026-07-09
```

Single-camera mode still works:

```bash
chicken-counter --config configs/cameras/example_camera.yaml
chicken-counter run --config configs/cameras/example_camera.yaml
```

### Output layout

```text
/media/jetson/DATA/chicken-sukawarna/cycle7/2026-07-09/output/
  CC1_vis.mp4
  CC1_compressed.mp4
  CC2_vis.mp4
  CC2_compressed.mp4
  ...
  checkpoints/CC1/frame_003000.jpg
  checkpoints/CC2/frame_006000.jpg
  counts_2026-07-09.json
```

After all 4 cameras finish counting, the batch runner compresses each annotated video
to under `batch.compress_max_mb` (default 200 MB) using `ffmpeg`.

### Per-camera counting boxes

| Camera | ROI points |
|--------|------------|
| CC1 | `[250,330], [1650,330], [1650,720], [250,720]` |
| CC2 | `[20,380], [1880,380], [1880,720], [20,720]` |
| CC3 | `[20,330], [1880,330], [1880,720], [20,720]` |
| CC4 | `[50,330], [1450,330], [1450,720], [50,720]` |

Tune these in `configs/cycle7_batch.yaml` if a lane drifts after camera maintenance.

### JSON report format

`counts_{date}.json` contains per-camera totals and the sum across all 4 cameras:

```json
{
  "date": "2026-07-09",
  "generated_at": "2026-07-09T11:45:00+00:00",
  "cameras": {
    "CC1": {
      "total_entered": 142,
      "source_video": "kandang_1_camera_1_2026-07-09_120056.mp4",
      "vis_video": "CC1_vis.mp4",
      "compressed_video": "CC1_compressed.mp4",
      "compressed_size_mb": 187.4,
      "frames_processed": 68432,
      "stopped_reason": "backward",
      "elapsed_seconds": 8234.5
    }
  },
  "total_entered_sum": 580
}
```

### Checkpoint images

Batch mode saves review images every `checkpoint_every_n_frames` (default 3000) per camera.
For a ~72k frame run that is about 24 images per camera.

### Cron example

```cron
0 7 * * * cd /media/jetson/DATA/chicken-sukawarna && /usr/bin/chicken-counter batch --config configs/cycle7_batch.yaml >> logs/cycle7-batch.log 2>&1
```

Requires `ffmpeg` on the Jetson PATH for post-run compression.

## Next Jetson-Focused Improvements

1. Add a hardware-aware video ingest path for CSI/GStreamer.
2. Export richer event logs for per-bird count timestamps.
3. Add a controller-signal adapter so encoder direction can override vision when available.

Recent work includes daily 4-camera batch processing, JSON count reports, post-run
compression under 200 MB, GStreamer hardware encoding, overlay buffer reuse,
duplicate optical-flow removal (`gmc_method: none`), and long-run ETA logging.
