Files
oko_public/bots_readme.md
T
2026-09-09 21:44:05 -05:00

177 lines
7.2 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.
# Bots Readme — Motion Detection (mofin) Pipeline Addition
## Overview
Added a motion detection stage (`mofin`) between `runCam` and `terp` in the video surveillance pipeline.
### Old Pipeline
```
runCam ──TCP──▶ terp ──TCP──▶ coordinator
```
### New Pipeline
```
runCam ──TCP──▶ mofin ──TCP──▶ terp ──TCP──▶ coordinator
└──TCP──▶ coordinator (no-motion clips)
```
`mofin` runs pixel-level motion detection on each clip. Clips with motion are forwarded to `terp` for classification (Haar cascade object detection). Clips without motion are sent directly to `coordinator` for storage, bypassing classification.
---
## Files Created
### `frame/motion.go`
Contains motion detection types and methods in the `frame` package.
**`Comparison` struct:**
```go
type Comparison struct {
Guid1, Guid2 []byte
PixelsChanged int
Threshold uint8
}
```
Stores the result of comparing two adjacent frames in a clip — their GUIDs, how many pixels changed, and the pixel-value threshold used.
**`(*Frame).compareTo(next *Frame, threshold uint8) (int, error)`:**
- Converts both frames to `gocv.Mat` via `ToMat()`
- Computes `AbsDiff` between the two Mats
- Converts to grayscale if multi-channel (`CvtColor`)
- Applies `Threshold` (pixel values above `threshold` count as changed)
- Returns `CountNonZero` (number of changed pixels) or `(0, error)` on failure
**`(*Clip).CountChangedPixels(threshold uint8)`:**
- Iterates over adjacent frame pairs in `c.PixelMats`
- For each pair, creates lightweight `Frame` objects from the clip's metadata and calls `compareTo`
- Logs any error returned by `compareTo` (avoiding writing to ephemeral Frame copies)
- Appends a `Comparison` entry to `c.Comparisons`
### `mofin/main.go`
A new pipeline binary that:
| Flag | Default | Purpose |
|---|---|---|
| `-listen` | `:8083` | TCP address to receive clips from `runCam` |
| `-terp` | `localhost:8081` | Forward address for clips with motion |
| `-coordinator` | `localhost:8082` | Forward address for clips without motion |
| `-threshold` | `5000` | Minimum `PixelsChanged` to consider motion present |
| `-passphrase` | `""` | Decryption/re-encryption passphrase (mirrors `terp` pattern) |
**Concurrency:** Clips are processed in parallel — each incoming clip gets its own goroutine (tracked by `procWg`). On shutdown, the listener is closed, in-flight decode goroutines drain via `acceptWg`, `clipChan` is closed, and `procWg.Wait()` blocks until all processing finishes before exiting.
**`processClip` flow:**
1. If `passphrase` is set, decrypt each frame's `PixelBytes` in-place via `Sublimate` + `Decrypt`
2. Call `clip.CountChangedPixels(30)` — pixel-value sensitivity of 30
3. Check if any `Comparison.PixelsChanged >= threshold` flag
4. Re-encrypt if passphrase was set
5. Forward to `terp` address (motion) or `coordinator` address (no motion)
### `mofin/go.mod`
Standard module setup with `replace frame => ../frame` (same pattern as `runCam`, `terp`, `coordinator`).
---
## Files Modified
### `frame/frame.go`
- Added `Errors []string` field to `Frame` — stores descriptive pipeline errors for backend review
- Added `Comparisons []Comparison` field to `Clip` — stores pairwise motion comparison results
- Removed a stale duplicate `Detect` method declaration at end of file
### `runCam/main.go`
- Renamed `-terp` flag to `-mofin` (default `localhost:8083`)
- Updated all internal references from `terpAddr``mofinAddr` in `main()`, `rebalance()`, and `runCamera()`
### `config.yaml`
Added `mofin1` node between `cam1` and `terp1`:
```yaml
- name: "mofin1"
binary: "mofin"
flags:
listen: ":8083"
terp: "localhost:8081"
coordinator: "localhost:8082"
threshold: 5000
```
Also updated `cam1` to point `mofin: "localhost:8083"` instead of `terp: "localhost:8081"`.
---
## Tests
### `frame/frame_test.go` — 19 tests
| Test | What it covers |
|---|---|
| `TestToMat_Roundtrip` | `ToMat()` returns an identical Mat from Frame data |
| `TestToMat_NonNilResult` | `ToMat()` succeeds with valid 2×2 grayscale data |
| `TestToMat_EmptyBytes` | `ToMat()` errors on zero-size buffer |
| `TestEncryptDecrypt_Roundtrip` | Encrypt then decrypt with same passphrase restores original bytes |
| `TestEncryptDecrypt_WrongPassphrase` | Decrypt with wrong passphrase fails |
| `TestEncryptDecrypt_EmptyBytes` | Encrypt/Decrypt on nil `PixelBytes` is a no-op |
| `TestDecrypt_TooShort` | Decrypt on truncated data fails |
| `TestEncrypt_UniqueSaltPerCall` | Two encryptions of same data produce different ciphertexts |
| `TestSublimate_Basic` | `Clip.Sublimate()` produces correct Frame count, metadata, GUIDs, timestamps |
| `TestSublimate_EmptyClip` | Empty clip produces zero frames |
| `TestSublimate_DetectionsCarryOver` | Frame detections are correctly mapped from Clip |
| `TestCheckLenCorrelations_Match` | Equal-length slices return true |
| `TestCheckLenCorrelations_PixelMatsGuidsMismatch` | Mismatched PixelMats/Guids returns false |
| `TestCheckLenCorrelations_TimestampsMismatch` | Mismatched Timestamps returns false |
| `TestCheckLenCorrelations_Empty` | Empty clip returns true |
| `TestClipSend` | Send/Receive roundtrip over TCP preserves Clip data |
| `TestClipSend_InvalidAddress` | Send to unreachable address returns error |
| `TestFrameFields_ZeroValues` | Zero-value Frame has nil Errors, nil Detections |
| `TestClipFields_ZeroValues` | Zero-value Clip has nil Comparisons |
### `frame/motion_test.go` — 12 tests
| Test | What it covers |
|---|---|
| `TestCompareTo_IdenticalFrames` | Two identical frames → 0 changed pixels |
| `TestCompareTo_AllPixelsChanged` | 0→255 → all 16 pixels of 4×4 detected as changed |
| `TestCompareTo_PartialChange` | 3 of 16 pixels differing → exactly 3 counted |
| `TestCompareTo_ThresholdFiltersSmallDiffs` | Pixels with diff > threshold counted; diff ≤ threshold ignored |
| `TestCompareTo_MultiChannel` | Multi-channel (BGR) → grayscale conversion works, changed pixels detected |
| `TestCompareTo_DimensionMismatch` | Different sizes return error |
| `TestCompareTo_ChannelMismatch` | Different channel counts return error |
| `TestCompareTo_IdenticalPixelData` | Same pixel bytes → 0 changed (no crash with equal data) |
| `TestCountChangedPixels_Basic` | 3 frames → 2 comparisons, correct pixel counts |
| `TestCountChangedPixels_SingleFrame` | 1 frame → 0 comparisons |
| `TestCountChangedPixels_EmptyClip` | 0 frames → 0 comparisons |
| `TestCountChangedPixels_ResetsComparisons` | Pre-existing Comparisons are cleared before run |
| `TestCountChangedPixels_ThresholdParameter` | Pixel-value threshold correctly filters small diffs |
| `TestComparison_Fields` | Comparison struct fields store/retrieve correctly |
### `frame/utils_test.go` — 16 tests
Camera-dependent tests skip with `-short` flag. Pure-logic tests (empty range, negative index, struct fields, Sscanf) always run.
### Running
```bash
go test -short ./... # fast (skips camera hardware scans)
go test ./... # full suite (attempts V4L device probes)
```
All 42+ tests pass. Pre-existing `ScanCams` tests preserved and corrected.
## Build Verification
All five modules pass `go build ./...` and `go vet ./...`:
- `frame/`
- `runCam/`
- `mofin/`
- `terp/`
- `coordinator/`