Last Updated: February 12, 2026 Current Phase: ML pipeline functional, classifier tuned, needs retest + decide next milestone
mobile/ # Expo React Native app (partially built)
worker/ # Python ML pipeline (functional)
app/ml/
detector.py # YOLODetector - object detection + tracking
tracker.py # TrajectoryTracker - ball path + velocity + interpolation
classifier.py # TrajectoryClassifier - state machine + hybrid 4-signal scoring
pipeline.py # Main pipeline - wires everything together
backend/ # FastAPI backend (scaffolding)
supabase/ # Supabase config (auth + DB)
Model: runs/detect/basketball_small_1280_v2/weights/best.pt
- YOLO v26s, 1280px input resolution
- 5 classes: ball, human, rim, made, shoot
- Trained 20 epochs on ~27,200 images (original 23k + Roboflow 9.4k merged)
- mAP50: 0.846 | mAP50-95: 0.590
Detection + Tracking:
- BoT-SORT tracking via
basketball_tracker.yaml - Rim-crop detection: 2x zoom inference at 640px around rim area
- YOLO "made" class detection with 0.25 min confidence threshold
- Retroactive "made" correction (30-frame window after shot resolution)
- Ball trajectory interpolation for lost frames (physics-based prediction)
Shot Classification (State Machine):
IDLE -> APPROACH -> RIM_CONTACT -> RESOLUTION -> (make/miss)
Key features:
- Pose-triggered + ball-triggered shot detection
- Pose override for stale approaches
- Ball departure confirmation (prevents false rim contact from shooter overlap)
- Rim zone entry gate (3+ consecutive frames required)
- Minimum flight time for ball-triggered approaches (30 frames)
- Airball detection (pose-triggered approach that times out)
4-Signal Hybrid Classification:
| Signal | Side Weight | Behind Weight | What It Measures |
|---|---|---|---|
| S1: Line Intersection | 0.30 | 0.15 | Does ball path cross through rim? |
| S2: Scoring Zone | 0.25 | 0.30 | Ball in net area moving downward? |
| S3: Trajectory Prediction | 0.15 | 0.30 | Predicted arc + occlusion detection |
| S4: Net Interaction | 0.30 | 0.25 | Horizontal drift/velocity after exit |
Additional classification logic:
- S1 veto: if ball clearly misses rim, cap score below threshold
- No-occlusion penalty: ball visible entire time near rim = likely miss
- Re-entry penalty: ball bouncing back into rim zone = miss signal
- Exit penalties: left/right exit = -0.30, above exit = auto-miss
- YOLO "made" class override (if detected during shot = make)
- Threshold: 0.45
- Expo React Native with NativeWind (Tailwind CSS)
- Some screens/features functional, others TODO
- FastAPI structure exists
- Supabase integration configured (auth + DB)
Tested on 4 videos recorded Feb 9, 2026. Results measured BEFORE latest classifier tuning (S1 veto, no-occlusion penalty, rim zone gate, etc.):
| Video | Camera Setup | Shots | Detected | Correct | Accuracy |
|---|---|---|---|---|---|
| Feb9SideAngle1 | Side, tripod, ~25ft | 7 (2M/5m) | 6/7 | 6/6 | 86% |
| Feb9SideAngle2 | Side, tripod, ~25ft | 7 (2M/5m) | 7/7 | 6/7 | 86% |
| Feb9SideGround1 | Side, ground level | 5 (2M/3m) | 4/5 | 3/4 | 60% |
| Feb9FrontAngle1 | Front/behind, elevated | 11 (3M/8m) | 10/11 | 7/10 | 64% |
| Total | 30 | 27/30 | 22/27 | 81% on detected |
Overall: 22/30 = 73% (including undetected shots)
- 3 undetected shots - state machine never triggered (shot detection problem)
- 2 heuristic false positives - misses classified as makes (FrontAngle1)
- 2 heuristic false negatives - makes classified as misses (SideAngle2, FrontAngle1)
- 1 timing issue - "made" class fired after shot already resolved (fixed by retroactive correction)
| Setup | Quality | Notes |
|---|---|---|
| Side angle, tripod, 20-30ft | Works well | Best accuracy, clear ball trajectory |
| Behind player, halfcourt | Works well | Good for different perspective |
| Side angle, ground level | OK | Tricky - shooter body overlaps rim zone |
| Front angle, ground level | Tricky | Ball trajectory harder to track |
Note: Significant classifier improvements were made AFTER these tests (S1 veto, no-occlusion penalty, rim zone entry gate, departure confirmation, stricter ball trigger, airball detection). Accuracy likely improved but needs retesting.
| Split | Images |
|---|---|
| Train | ~21,800 |
| Valid | ~3,800 |
| Test | ~2,200 |
| Total | ~27,200 |
Sources: original basketball dataset (23k) + Roboflow basketball-bs0zc (9.4k, with "made" and "shoot" classes)
worker/
app/ml/
detector.py # YOLODetector (5 classes, rim-crop, BoT-SORT)
tracker.py # TrajectoryTracker (interpolation, velocity)
classifier.py # TrajectoryClassifier (state machine + 4-signal hybrid)
pipeline.py # Main processing pipeline
runs/detect/
basketball_small_1280_v2/weights/best.pt # Current model (v26s, 5 classes)
data/combined/ # Merged training data (~27k images)
data/basketball_made/ # Roboflow "made" dataset (source)
tools/
remap_roboflow_classes.py # Class ID remapping script
basketball_tracker.yaml # BoT-SORT config
test_classifier_debug.py # Main testing script
- Evaluated CNN rim-crop classifier approach (decided to defer)
- Updated project status documentation
- Planning next milestone
- Added "made" + "shoot" classes to YOLO model via Roboflow dataset merge
- Trained YOLO v26s v2 model (20 epochs, ~27k images, RTX 4070)
- Integrated YOLO "made" class into classifier with confidence threshold (0.25)
- Added retroactive "made" correction (30-frame window)
- Tested on 4 self-recorded videos: 81% accuracy on detected shots
- Extensive classifier tuning:
- S1 veto (line intersection can veto false makes)
- No-occlusion penalty (ball visible = likely not a make)
- Rim zone entry gate (3+ consecutive frames)
- Ball departure confirmation (prevents shooter overlap triggers)
- Minimum flight time for ball-triggered approaches
- Stricter ball trigger (5 consecutive upward frames + bottom half)
- Airball detection for pose-triggered timeouts
- Pose override for stale approaches
- Reduced cooldown from 90 to 35 frames
- Started training yolo26n on combined dataset (23k images)
- Added trajectory interpolation for lost ball frames
- Lowered detection thresholds
- Completed pipeline integration with real components
- Created deployment plan
- Tried retraining with yolo26l - didn't improve ball tracking
- Ball detection rate: ~42% during shots
- Built YOLODetector, TrajectoryTracker, TrajectoryClassifier
- Trained initial model on 9,522 images
- Rim detection: 97%, Ball detection: 42%
- Use $200 student credits
- $6-12/mo droplet for FastAPI + Redis
- yolo26s for video processing
- Free for 16-33 months
- TensorDock GPU ($0.10/hr)
- Processing time drops significantly
- Cost: ~$1-5/mo
| Service | Cost |
|---|---|
| DigitalOcean | $0 (credits) |
| Supabase | $0 (free tier) |
| Apple Developer | $99/yr |
| Total MVP | ~$100/yr |
CPU: DigitalOcean ($200 student credits), Azure credits
GPU: Vast.ai, TensorDock, Runpod