|
|
|
|
|
""" |
|
|
Push soccer ball detection project to Hugging Face Hub. |
|
|
Includes code, configs, and trained model checkpoints. |
|
|
""" |
|
|
|
|
|
import argparse |
|
|
import os |
|
|
import shutil |
|
|
import sys |
|
|
from pathlib import Path |
|
|
from typing import List, Optional |
|
|
|
|
|
try: |
|
|
from huggingface_hub import HfApi, create_repo, login |
|
|
from huggingface_hub.utils import HfHubHTTPError |
|
|
HF_AVAILABLE = True |
|
|
except ImportError: |
|
|
HF_AVAILABLE = False |
|
|
print("Error: huggingface_hub not installed. Install with: pip install huggingface_hub") |
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
def get_files_to_upload(project_root: Path, exclude_patterns: List[str] = None) -> List[Path]: |
|
|
"""Get list of files to upload, excluding patterns.""" |
|
|
if exclude_patterns is None: |
|
|
exclude_patterns = [ |
|
|
'__pycache__', |
|
|
'.git', |
|
|
'.gitignore', |
|
|
'venv', |
|
|
'env', |
|
|
'.venv', |
|
|
'.env', |
|
|
'.env.local', |
|
|
'mlruns', |
|
|
'logs', |
|
|
'*.log', |
|
|
'.DS_Store', |
|
|
'Thumbs.db', |
|
|
'data/raw', |
|
|
'data/output', |
|
|
'datasets', |
|
|
'*.tar.gz', |
|
|
'*.tar', |
|
|
'*.zip', |
|
|
] |
|
|
|
|
|
files_to_upload = [] |
|
|
|
|
|
|
|
|
include_patterns = [ |
|
|
'*.py', |
|
|
'*.yaml', |
|
|
'*.yml', |
|
|
'*.md', |
|
|
'*.txt', |
|
|
'*.json', |
|
|
'*.html', |
|
|
'*.sh', |
|
|
'*.dockerfile', |
|
|
'Dockerfile', |
|
|
'docker-compose*.yml', |
|
|
'configs/', |
|
|
'src/', |
|
|
'scripts/', |
|
|
'models/checkpoints/*.pth', |
|
|
] |
|
|
|
|
|
def should_include(file_path: Path) -> bool: |
|
|
"""Check if file should be included.""" |
|
|
rel_path = file_path.relative_to(project_root) |
|
|
rel_str = str(rel_path) |
|
|
|
|
|
|
|
|
for pattern in exclude_patterns: |
|
|
if pattern in rel_str or file_path.name.startswith('.'): |
|
|
|
|
|
if file_path.name == '.gitkeep': |
|
|
return True |
|
|
return False |
|
|
|
|
|
|
|
|
for pattern in include_patterns: |
|
|
if pattern.endswith('/'): |
|
|
|
|
|
if rel_str.startswith(pattern[:-1]): |
|
|
return True |
|
|
elif '*' in pattern: |
|
|
|
|
|
import fnmatch |
|
|
if fnmatch.fnmatch(file_path.name, pattern): |
|
|
return True |
|
|
else: |
|
|
|
|
|
if file_path.name == pattern or rel_str == pattern: |
|
|
return True |
|
|
|
|
|
return False |
|
|
|
|
|
|
|
|
for root, dirs, files in os.walk(project_root): |
|
|
root_path = Path(root) |
|
|
|
|
|
|
|
|
dirs[:] = [d for d in dirs if not any(pattern in d for pattern in exclude_patterns)] |
|
|
|
|
|
|
|
|
for file in files: |
|
|
file_path = root_path / file |
|
|
if should_include(file_path): |
|
|
files_to_upload.append(file_path) |
|
|
|
|
|
return sorted(files_to_upload) |
|
|
|
|
|
|
|
|
def create_hf_readme(project_root: Path, repo_id: str) -> str: |
|
|
"""Create README content for Hugging Face.""" |
|
|
readme_content = f"""--- |
|
|
license: mit |
|
|
tags: |
|
|
- computer-vision |
|
|
- object-detection |
|
|
- soccer |
|
|
- ball-detection |
|
|
- detr |
|
|
- rf-detr |
|
|
- pytorch |
|
|
datasets: |
|
|
- custom |
|
|
metrics: |
|
|
- mAP |
|
|
- precision |
|
|
- recall |
|
|
--- |
|
|
|
|
|
# Soccer Ball Detection with RF-DETR |
|
|
|
|
|
Automated soccer ball detection pipeline using RF-DETR (Roboflow DETR) optimized for tiny object detection (<15 pixels). |
|
|
|
|
|
## Model Details |
|
|
|
|
|
### Architecture |
|
|
- **Model**: RF-DETR Base |
|
|
- **Backbone**: ResNet-50 |
|
|
- **Classes**: Ball (single class detection) |
|
|
- **Input Resolution**: 1120x1120 (optimized for memory) |
|
|
- **Precision**: Mixed Precision (FP16/FP32) training, FP16 inference |
|
|
|
|
|
### Performance |
|
|
Based on training evaluation report (Epoch 39): |
|
|
- **mAP@0.5:0.95**: 0.682 (68.2%) |
|
|
- **mAP@0.5**: 0.990 (99.0%) |
|
|
- **Small Objects mAP**: 0.598 (59.8%) |
|
|
- **Training Loss**: 3.073 |
|
|
- **Validation Loss**: 3.658 |
|
|
|
|
|
## Quick Start |
|
|
|
|
|
### Installation |
|
|
|
|
|
```bash |
|
|
pip install -r requirements.txt |
|
|
``` |
|
|
|
|
|
### Usage |
|
|
|
|
|
```python |
|
|
from src.perception.local_detector import LocalDetector |
|
|
from pathlib import Path |
|
|
|
|
|
# Initialize detector |
|
|
detector = LocalDetector( |
|
|
model_path="models/checkpoints/latest_checkpoint.pth", |
|
|
config_path="configs/default.yaml" |
|
|
) |
|
|
|
|
|
# Detect ball in image |
|
|
results = detector.detect(image_path) |
|
|
``` |
|
|
|
|
|
### Process Video |
|
|
|
|
|
```bash |
|
|
python main.py --video path/to/video.mp4 --config configs/default.yaml --output data/output |
|
|
``` |
|
|
|
|
|
## Training |
|
|
|
|
|
### Train from Scratch |
|
|
|
|
|
```bash |
|
|
python scripts/train_ball.py \\ |
|
|
--config configs/training.yaml \\ |
|
|
--dataset-dir datasets/combined \\ |
|
|
--output-dir models \\ |
|
|
--epochs 50 |
|
|
``` |
|
|
|
|
|
### Resume Training |
|
|
|
|
|
```bash |
|
|
python scripts/train_ball.py \\ |
|
|
--config configs/resume_20_epochs.yaml \\ |
|
|
--dataset-dir datasets/combined \\ |
|
|
--output-dir models \\ |
|
|
--resume models/checkpoints/latest_checkpoint.pth \\ |
|
|
--epochs 50 |
|
|
``` |
|
|
|
|
|
## Project Structure |
|
|
|
|
|
``` |
|
|
soccer_cv_ball/ |
|
|
├── main.py # Main orchestrator |
|
|
├── configs/ # Configuration files |
|
|
├── src/ |
|
|
│ ├── perception/ # Detection, tracking |
|
|
│ ├── analysis/ # Event detection |
|
|
│ ├── visualization/ # Dashboard |
|
|
│ └── training/ # Training utilities |
|
|
├── scripts/ # Training and utility scripts |
|
|
├── models/ # Model checkpoints |
|
|
└── data/ # Dataset (not included) |
|
|
``` |
|
|
|
|
|
## Configuration |
|
|
|
|
|
Key configuration files: |
|
|
- `configs/training.yaml` - Main training configuration |
|
|
- `configs/default.yaml` - Inference configuration |
|
|
- `configs/resume_*.yaml` - Resume training configurations |
|
|
|
|
|
## Datasets |
|
|
|
|
|
This model was trained on: |
|
|
- SoccerSynth-Detection (synthetic data) |
|
|
- Open Soccer Ball Dataset |
|
|
- Custom validation sets |
|
|
|
|
|
## Precision Strategy |
|
|
|
|
|
- **Training**: Mixed Precision (FP16/FP32) - RF-DETR default |
|
|
- **Inference**: FP16 (half precision) for ~3x speedup |
|
|
- **Future**: INT8 via QAT (Quantization-Aware Training) for edge devices |
|
|
|
|
|
## Citation |
|
|
|
|
|
If you use this model, please cite: |
|
|
|
|
|
```bibtex |
|
|
@software{{soccer_ball_detection, |
|
|
title={{Soccer Ball Detection with RF-DETR}}, |
|
|
author={{Your Name}}, |
|
|
year={{2026}}, |
|
|
url={{https://huggingface.co/{repo_id}}} |
|
|
}} |
|
|
``` |
|
|
|
|
|
## License |
|
|
|
|
|
MIT License - See LICENSE file for details. |
|
|
|
|
|
## Acknowledgments |
|
|
|
|
|
- RF-DETR by Roboflow |
|
|
- SoccerSynth-Detection dataset |
|
|
- Open Soccer Ball Dataset |
|
|
""" |
|
|
return readme_content |
|
|
|
|
|
|
|
|
def push_to_huggingface( |
|
|
repo_id: str, |
|
|
project_root: Path, |
|
|
token: Optional[str] = None, |
|
|
private: bool = False, |
|
|
include_models: bool = True |
|
|
): |
|
|
"""Push project to Hugging Face Hub using HTTP API.""" |
|
|
|
|
|
project_root = Path(project_root).resolve() |
|
|
|
|
|
print(f"📦 Preparing to push to Hugging Face: {repo_id}") |
|
|
print(f" Project root: {project_root}") |
|
|
|
|
|
|
|
|
api = HfApi(token=token) |
|
|
|
|
|
|
|
|
try: |
|
|
user_info = api.whoami() |
|
|
print(f"🔐 Logged in as: {user_info.get('name', 'unknown')}") |
|
|
except Exception as e: |
|
|
print(f"❌ Authentication failed: {e}") |
|
|
print(" Please provide a valid token with --token argument") |
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
print(f"📁 Creating repository: {repo_id}") |
|
|
try: |
|
|
api.create_repo(repo_id, private=private, exist_ok=True, repo_type="model") |
|
|
print("✅ Repository created/exists") |
|
|
except HfHubHTTPError as e: |
|
|
if "already exists" in str(e).lower(): |
|
|
print("✅ Repository already exists") |
|
|
else: |
|
|
print(f"⚠️ Repository creation warning: {e}") |
|
|
|
|
|
|
|
|
print("🔍 Finding files to upload...") |
|
|
files_to_upload = get_files_to_upload(project_root) |
|
|
print(f" Found {len(files_to_upload)} files") |
|
|
|
|
|
|
|
|
if not include_models: |
|
|
files_to_upload = [f for f in files_to_upload if not str(f).endswith('.pth')] |
|
|
print(f" Excluding model files: {len(files_to_upload)} files remaining") |
|
|
|
|
|
|
|
|
print("📝 Creating README.md...") |
|
|
readme_path = project_root / "README.md" |
|
|
readme_content = create_hf_readme(project_root, repo_id) |
|
|
with open(readme_path, 'w') as f: |
|
|
f.write(readme_content) |
|
|
print("✅ README.md created") |
|
|
|
|
|
|
|
|
print("📤 Uploading files to Hugging Face Hub...") |
|
|
uploaded = 0 |
|
|
failed = 0 |
|
|
|
|
|
for file_path in files_to_upload: |
|
|
try: |
|
|
rel_path = file_path.relative_to(project_root) |
|
|
|
|
|
path_in_repo = str(rel_path).replace('\\', '/') |
|
|
|
|
|
|
|
|
if path_in_repo == "README.md" and file_path != readme_path: |
|
|
continue |
|
|
|
|
|
|
|
|
api.upload_file( |
|
|
path_or_fileobj=str(file_path), |
|
|
path_in_repo=path_in_repo, |
|
|
repo_id=repo_id, |
|
|
repo_type="model", |
|
|
token=token |
|
|
) |
|
|
uploaded += 1 |
|
|
if uploaded % 10 == 0: |
|
|
print(f" Uploaded {uploaded}/{len(files_to_upload)} files...") |
|
|
except Exception as e: |
|
|
failed += 1 |
|
|
print(f" ⚠️ Failed to upload {file_path.name}: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
api.upload_file( |
|
|
path_or_fileobj=str(readme_path), |
|
|
path_in_repo="README.md", |
|
|
repo_id=repo_id, |
|
|
repo_type="model", |
|
|
token=token |
|
|
) |
|
|
print("✅ README.md uploaded") |
|
|
except Exception as e: |
|
|
print(f"⚠️ Failed to upload README.md: {e}") |
|
|
|
|
|
print(f"\n✅ Upload complete!") |
|
|
print(f" Uploaded: {uploaded} files") |
|
|
if failed > 0: |
|
|
print(f" Failed: {failed} files") |
|
|
print(f" View at: https://huggingface.co/{repo_id}") |
|
|
|
|
|
|
|
|
def main(): |
|
|
parser = argparse.ArgumentParser( |
|
|
description="Push soccer ball detection project to Hugging Face Hub" |
|
|
) |
|
|
parser.add_argument( |
|
|
"--repo-id", |
|
|
type=str, |
|
|
required=True, |
|
|
help="Hugging Face repository ID (e.g., 'username/soccer-ball-detection')" |
|
|
) |
|
|
parser.add_argument( |
|
|
"--project-root", |
|
|
type=str, |
|
|
default=".", |
|
|
help="Project root directory (default: current directory)" |
|
|
) |
|
|
parser.add_argument( |
|
|
"--token", |
|
|
type=str, |
|
|
default=None, |
|
|
help="Hugging Face API token (or use huggingface-cli login)" |
|
|
) |
|
|
parser.add_argument( |
|
|
"--private", |
|
|
action="store_true", |
|
|
help="Create private repository" |
|
|
) |
|
|
parser.add_argument( |
|
|
"--no-models", |
|
|
action="store_true", |
|
|
help="Exclude model checkpoint files (.pth)" |
|
|
) |
|
|
|
|
|
args = parser.parse_args() |
|
|
|
|
|
push_to_huggingface( |
|
|
repo_id=args.repo_id, |
|
|
project_root=Path(args.project_root), |
|
|
token=args.token, |
|
|
private=args.private, |
|
|
include_models=not args.no_models |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |
|
|
|