# Basketball Object Detection — Production Handover

This folder is the result of a multi-day R&D session optimizing YOLO-family models for basketball + basketball_hoop detection on Android (Samsung Galaxy A36 5G as reference device). It contains the production-ready tflite models, the Android-side requirements for consuming them, and a protocol for verifying quality in the kima app before shipping.

## ⚠️ Library version: bump to `posedetection-compose:4.11.0`

The R&D work shipped a new **`posedetection-compose:4.11.0`** library release containing the letterbox-aware detector + camera config + delegate logging that the rect models depend on. The kima app currently uses an older version (probably 4.10.0). **Bump the dependency before integrating any of these models.**

The new version is available in two places:

### Option A — Maven Local (immediate, no remote publish needed)

The library is already published to maven local on the dev machine:
`~/.m2/repository/com/performancecoachlab/posedetection/posedetection-compose/4.11.0/`

To pull it from kima, the kima app's root `build.gradle.kts` (or `settings.gradle.kts`'s `dependencyResolutionManagement`) must include `mavenLocal()` in its repositories list:

```kotlin
repositories {
    mavenLocal()        // ← add this line if not already present
    google()
    mavenCentral()
}
```

Then bump the dependency in whichever module consumes posedetection (typically the kima Android app's `build.gradle.kts`):

```kotlin
implementation("com.performancecoachlab.posedetection:posedetection-compose:4.11.0")
```

This will resolve from maven local without any network calls.

### Option B — Remote (after publishing)

The 4.11.0 library code lives on the **`release-4.11.0`** branch in the PoseDetection repo. Once it's merged + published to maven central via the existing `vanniktech.maven.publish` flow, kima can drop the `mavenLocal()` line and just consume it from central. Until then, use Option A.

### What's actually in 4.11.0

The full commit message on `release-4.11.0` lists everything. The headline:
- **`ImageDetector.android.kt`** (NEW class) — letterbox-aware detector with full un-letterbox of output bboxes. The detector all the rect models in `models/` were tested against.
- **`CameraView.android.kt`** — pins `ImageAnalysis` to `AspectRatio.RATIO_4_3` so the camera frame distribution matches the rect models' expected geometry.
- **`CustomObjectModel.android.kt`** — explicit GPU/NNAPI/CPU delegate logging at INFO level. Surfaces silent CPU fallbacks via `adb logcat -s TFLite:I`.
- **No breaking API changes** — only additive. Existing kima usage of the library should keep working unchanged after the version bump.

## TL;DR — Use this model

**`models/yolo26n_v11_rect_512x384.tflite`** is the recommended production default.

| Property | Value |
|---|---|
| Architecture | YOLO26n (2026 generation, ~5.2 GFLOPs, ~2.4M params after fusion) |
| Training | 50 epochs, AdamW, `imgsz=512`, `rect=True`, Roboflow `dataset_v11` (basketball court footage, letterboxed preprocessing) |
| Input tensor | `[1, 384, 512, 3]` float32 (NHWC), pixel values normalized to `[0, 1]` |
| Output tensor | `[1, 300, 6]` float32 = `[x1, y1, x2, y2, conf, cls]` per row, **NMS already applied**, coords normalized to `[0, 1]` over the input tensor |
| Classes | `0 = basketball`, `1 = basketball_hoop` |
| File size | 9.3 MB (fp32) |
| Measured speed | ~6.7 Hz on the A36 5G via TFLite GPU delegate |
| Measured quality | mean confidence ~0.78–0.83 across the 60s basketball test clip |
| val mAP50 | 0.7632 |

This model is **rectangular** (4:3 landscape) and was trained with `rect=True`, which means it learned features tuned to a 384×512 tensor. It expects the camera to deliver landscape 4:3 frames, which the kima app must enforce — see `ANDROID_INTEGRATION.md`.

## Why fp32 not fp16

Tested both. fp16 saves ~50% file size but on the A36 5G's TFLite GPU delegate they ran at the same speed and fp16 cost ~0.02 mean confidence. Since 4 MB of model file is negligible vs the production app's overall size, **stick with fp32** — same speed, better quality.

## What's in this folder

```
handover/
├── README.md                    ← you are here
├── MODELS.md                    ← detailed comparison of all 6 candidates
├── ANDROID_INTEGRATION.md       ← what the kima app's Android side needs
├── TESTING_PROTOCOL.md          ← how to A/B test models in the kima app
├── LESSONS_LEARNED.md           ← key insights from the R&D session
├── models/
│   ├── yolo26n_v11_rect_512x384.tflite     ← RECOMMENDED PRODUCTION DEFAULT (rect=True)
│   ├── yolo26n_v11_rect_384x288.tflite     ← speed-critical alternative (>10 Hz)
│   ├── yolo26n_v11_square_512.tflite       ← square 512², slightly higher quality at lower speed
│   ├── yolo26n_v11_square_416.tflite       ← square 416² baseline reference
│   ├── yolo11n_dataset640.tflite           ← historical quality ceiling, KNOWN LOOSE BBOXES
│   └── yolo11n_noise_floor_baseline.tflite ← stable model for noise-floor sanity checks
└── reference_code/
    ├── ImageDetector.android.kt            ← working letterbox-aware detector
    ├── CustomObjectModel.android.kt        ← TFLite interpreter setup with delegate logging
    ├── CameraView_snippet.kt               ← CameraX 4:3 + landscape pin
    ├── AndroidManifest_snippet.xml         ← orientation lock
    └── compare_logs.py                     ← portable Python report generator for A/B tests
```

## Quick start

1. **Read `MODELS.md`** to understand the candidates and pick a starting model. The recommended default is `yolo26n_v11_rect_512x384.tflite`.
2. **Read `ANDROID_INTEGRATION.md`** to see what camera config and detector code the kima app needs. **Critical**: the model expects a 4:3 landscape camera frame; the kima app must pin CameraX accordingly.
3. **Drop the chosen tflite** into the kima app's assets folder.
4. **Implement the camera + detector** per `ANDROID_INTEGRATION.md`. The reference code in `reference_code/` is the working version from this project.
5. **Verify with `TESTING_PROTOCOL.md`** before shipping. Even a single 60-second comparison run against a known video can catch integration mistakes early.

## Source notes

- All models are trained on the `kima-rbjnn/dataset-3k5b6` Roboflow project, version 11 (preprocessing = "Fit (black padding)"; the v8 stretched dataset was the source of weeks of accuracy issues — **never train on stretched data again**, see `LESSONS_LEARNED.md`).
- The pipeline is closed-loop: edit `collab/Model Training.ipynb` cell 5, run training in Colab, drive-sync the tflite to the Mac, sync to assets via `tools/sync_models.py`, build, install, run a 60s back-to-back comparison via `tools/run_auto_experiment.sh`, generate the report via `tools/compare_logs.py`. The full source pipeline lives in the parent project repo.
- **iOS is not covered here.** This handover is Android-only. iOS uses CoreML exports of the same `.pt` weights, and the user has confirmed the iOS pipeline already works at 10+ Hz with high accuracy because CoreML bakes preprocessing into the model graph. If kima ships iOS too, the iOS side likely doesn't need any of this — just the same Roboflow dataset versions and a separate CoreML export.
