Skip to main content

OpenSportsLib is the professional library, designed for advanced video understanding in sports. It provides state-of-the-art tools for action recognition, spotting, retrieval, and captioning, making it ideal for researchers, analysts, and developers working with sports video data.

Project description

OpenSportsLib

OpenSportsLib is the professional library, designed for advanced video understanding in soccer. It provides state-of-the-art tools for action recognition, spotting, retrieval, and captioning, making it ideal for researchers, analysts, and developers working with soccer video data.

Development

### Clone the github repo
git clone https://github.com/OpenSportsLab/opensportslib.git 

### Requirements and installation ###
conda create -n osl python=3.12 pip
conda activate osl
pip install -e .

or 

pip install -e .[localization]
or 
## Want to use "torch-geometric","torch-scatter", "torch-sparse", "torch-cluster", "torch-spline-conv"
pip install -e .[py-geometric] -f https://pytorch-geometric.com/whl/torch-2.10.0+cu128.html

### git branch and merge rules ###
1. Check and verify current branch is "dev" - git status

2. Create new branch from source "dev" - 
git pull
git checkout -b <new_feature/fix/bug>

3. Raise PR request to merge your branch <new_feature/fix/bug> to "dev" branch 

Installation

conda create -n osl python=3.12 pip
conda activate osl

## Release stable version
pip install opensportslib

## Pre-release version
pip install --pre opensportslib

🤝 Contributing & Developer Guide

We welcome contributions to OpenSportsLib.

These documents explain:

  • How to add models and datasets
  • Coding standards
  • Training pipeline structure
  • How to run and test the framework

Configuration Sample (.yaml) file

  1. Classification
TASK: classification

DATA:
  dataset_name: mvfouls
  data_dir: /home/vorajv/opensportslib/SoccerNet/mvfouls
  data_modality: video
  view_type: multi  # multi or single
  num_classes: 8 # mvfoul
  train: 
    type: annotations_train.json
    video_path: ${DATA.data_dir}/train
    path: ${DATA.train.video_path}/annotations-train.json
    dataloader:
      batch_size: 8
      shuffle: true
      num_workers: 4
      pin_memory: true
  valid:
    type: annotations_valid.json
    video_path: ${DATA.data_dir}/valid
    path: ${DATA.valid.video_path}/annotations-valid.json
    dataloader:
      batch_size: 1
      num_workers: 1
      shuffle: false
  test:
    type: annotations_test.json
    video_path: ${DATA.data_dir}/test
    path: ${DATA.test.video_path}/annotations-test.json
    dataloader:
      batch_size: 1
      num_workers: 1
      shuffle: false
  num_frames: 16               # 8 before + 8 after the foul
  input_fps: 25                # Original FPS of video
  target_fps: 17               # Temporal downsampling to 1s clip (approx)
  start_frame: 63            # Start frame of clip relative to foul frame
  end_frame: 87              # End frame of clip relative to foul frame
  frame_size: [224, 224]       # Spatial resolution (HxW)
  augmentations:
    random_affine: true
    translate: [0.1, 0.1]        
    affine_scale: [0.9, 1.0]     
    random_perspective: true
    distortion_scale: 0.3        
    perspective_prob: 0.5
    random_rotation: true
    rotation_degrees: 5          
    color_jitter: true
    jitter_params: [0.2, 0.2, 0.2, 0.1]   # brightness, contrast, saturation, hue
    random_horizontal_flip: true
    flip_prob: 0.5
    random_crop: false

MODEL:
  type: custom # huggingface, custom 
  backbone: 
    type: mvit_v2_s # video_mae, r3d_18, mc3_18, r2plus1d_18, s3d, mvit_v2_s
  neck:
    type: MV_Aggregate
    agr_type: max   # max, mean, attention
  head: 
    type: MV_LinearLayer
  pretrained_model: mvit_v2_s # MCG-NJU/videomae-base, OpenGVLab/VideoMAEv2-Base, r3d_18, mc3_18, r2plus1d_18, s3d, mvit_v2_s
  unfreeze_head: true  # for videomae backbone
  unfreeze_last_n_layers: 3 # for videomae backbone
    

TRAIN:
  monitor: balanced_accuracy # balanced_accuracy, loss
  mode: max # max or min
  enabled: true
  use_weighted_sampler: false
  use_weighted_loss: true
  epochs: 20 #20
  save_dir: ./checkpoints
  log_interval: 10
  save_every: 2 #5

  criterion:
    type: CrossEntropyLoss

  optimizer:
    type: AdamW
    lr: 0.0001  #0.001
    backbone_lr: 0.00005
    head_lr: 0.001
    betas: [0.9, 0.999]
    eps: 0.0000001
    weight_decay: 0.001 #0.01 - videomae, 0.001 - others
    amsgrad: false
  
  scheduler:
    type: StepLR
    step_size: 3
    gamma: 0.1

SYSTEM:
  log_dir: ./logs
  use_seed: false
  seed: 42
  GPU: 4
  device: cuda   # auto | cuda | cpu
  gpu_id: 0
  1. Classification (Tracking)
TASK: classification

DATA:
  dataset_name: sngar
  data_modality: tracking_parquet
  data_dir: /home/karkid/opensportslib/sngar-tracking
  preload_data: false
  train: 
    type: annotations_train.json
    video_path: ${DATA.data_dir}/train
    path: ${DATA.train.video_path}/train.json
    dataloader:
      batch_size: 32
      shuffle: true
      num_workers: 8
      pin_memory: true
  valid:
    type: annotations_valid.json
    video_path: ${DATA.data_dir}/valid
    path: ${DATA.valid.video_path}/valid.json
    dataloader:
      batch_size: 32
      num_workers: 8
      shuffle: false
  test:
    type: annotations_test.json
    video_path: ${DATA.data_dir}/test
    path: ${DATA.test.video_path}/test.json
    dataloader:
      batch_size: 32
      num_workers: 8
      shuffle: false
  num_frames: 16
  frame_interval: 9
  augmentations:
    vertical_flip: true
    horizontal_flip: true
    team_flip: true
  normalize: true
  num_objects: 23
  feature_dim: 8
  pitch_half_length: 85.0
  pitch_half_width: 50.0
  max_displacement: 110.0
  max_ball_height: 30.0

MODEL:
  type: custom
  backbone:
    type: graph_conv
    encoder: graphconv
    hidden_dim: 64
    num_layers: 20
    dropout: 0.1
  neck:
    type: TemporalAggregation
    agr_type: maxpool
    hidden_dim: 64
    dropout: 0.1
  head:
    type: TrackingClassifier
    hidden_dim: 64
    dropout: 0.1
    num_classes: 10
  edge: positional
  k: 8
  r: 15.0

TRAIN:
  monitor: loss # balanced_accuracy, loss
  mode: min # max or min
  enabled: true
  use_weighted_sampler: true
  use_weighted_loss: false
  samples_per_class: 4000
  epochs: 10
  patience: 10
  save_every: 20
  detailed_results: true

  optimizer:
    type: Adam
    lr: 0.001

  scheduler:
    type: ReduceLROnPlateau
    mode: ${TRAIN.mode}
    patience: 10
    factor: 0.1
    min_lr: 1e-8
  
  criterion:
    type: CrossEntropyLoss

  save_dir: ./checkpoints_tracking

SYSTEM:
 log_dir: ./logs
 use_seed: true
 seed: 42
 GPU: 4
 device: cuda   # auto | cuda | cpu
 gpu_id: 0
  1. Localization
TASK: localization

dali: True

DATA:
  dataset_name: SoccerNet
  data_dir: /home/vorajv/opensportslib/SoccerNet/annotations/
  classes:
    - PASS
    - DRIVE
    - HEADER
    - HIGH PASS
    - OUT
    - CROSS
    - THROW IN
    - SHOT
    - BALL PLAYER BLOCK
    - PLAYER SUCCESSFUL TACKLE
    - FREE KICK
    - GOAL
    
  epoch_num_frames: 500000
  mixup: true
  modality: rgb
  crop_dim: -1
  dilate_len: 0        # Dilate ground truth labels
  clip_len: 100
  input_fps: 25
  extract_fps: 2
  imagenet_mean: [0.485, 0.456, 0.406]
  imagenet_std: [0.229, 0.224, 0.225]
  target_height: 224
  target_width: 398

  train:
    type: VideoGameWithDali
    classes: ${DATA.classes}
    output_map: [data, label]
    video_path: ${DATA.data_dir}/train/
    path: ${DATA.train.video_path}/annotations-2024-224p-train.json
    dataloader:
      batch_size: 8
      shuffle: true
      num_workers: 4
      pin_memory: true

  valid:
    type: VideoGameWithDali
    classes: ${DATA.classes}
    output_map: [data, label]
    video_path: ${DATA.data_dir}/valid/
    path: ${DATA.valid.video_path}/annotations-2024-224p-valid.json
    dataloader:
      batch_size: 8
      shuffle: true

  valid_data_frames:
    type: VideoGameWithDaliVideo
    classes: ${DATA.classes}
    output_map: [data, label]
    video_path: ${DATA.valid.video_path}
    path: ${DATA.valid.path}
    overlap_len: 0
    dataloader:
      batch_size: 4
      shuffle: false

  test:
    type: VideoGameWithDaliVideo
    classes: ${DATA.classes}
    output_map: [data, label]
    video_path: ${DATA.data_dir}/test/
    path: ${DATA.test.video_path}/annotations-2024-224p-test.json
    results: results_spotting_test
    nms_window: 2 
    metric: tight
    overlap_len: 50
    dataloader:
      batch_size: 4
      shuffle: false

  challenge:
    type: VideoGameWithDaliVideo
    overlap_len: 50
    output_map: [data, label]
    path: ${DATA.data_dir}/challenge/annotations.json
    dataloader:
      batch_size: 4
      shuffle: false

MODEL:
    type: E2E
    runner:
      type: runner_e2e
    backbone:
      type: rny008_gsm
    head:
      type: gru
    multi_gpu: true
    load_weights: null
    save_dir: ./checkpoints
    work_dir: ${MODEL.save_dir}

TRAIN:
  type: trainer_e2e
  num_epochs: 10
  acc_grad_iter: 1
  base_num_valid_epochs: 30
  start_valid_epoch: 4
  valid_map_every: 1
  criterion_valid: map

  criterion:
    type: CrossEntropyLoss

  optimizer:
    type: AdamWithScaler
    lr: 0.01

  scheduler:
    type: ChainedSchedulerE2E
    acc_grad_iter: 1
    num_epochs: ${TRAIN.num_epochs}
    warm_up_epochs: 3

SYSTEM:
  log_dir: ./logs
  seed: 42
  GPU: 4         # number of gpus to use
  device: cuda   # auto | cuda | cpu
  gpu_id: 0      # device id for single gpu training

Annotations (train/valid/test) (.json) format

Download annotations file from below links

  1. Classification mvfouls = https://huggingface.co/datasets/OpenSportsLab/opensportslib-classification-vars/tree/mvfouls svfouls = https://huggingface.co/datasets/OpenSportsLab/opensportslib-classification-vars/tree/svfouls

  2. Localization ball-action-spotting = https://huggingface.co/datasets/OpenSportsLab/opensportslib-localization-snbas/tree/main

Download weights from HF

  1. classification (mvit) (MVFoul classification) https://huggingface.co/jeetv/snpro-classification-mvit/tree/main

  2. Localization (E2E spot)

Usage:

### Load weights from HF ###

#### For Classification ####
myModel.infer(
    test_set="/path/to/annotations.json",
    pretrained="jeetv/snpro-classification-mvit", # classification (MViT)
)

#### For Localization ####
pretrained = "jeetv/snpro-snbas-2023" # SNBAS - 2 classes (E2E spot)
pretrained = "jeetv/snpro-snbas-2024" # SNBAS - 12 classes (E2E spot)

Train on SINGLE GPU

from opensportslib import model
import wandb

# Initialize model with config
myModel = model.classification(
    config="/path/to/classification.yaml"
)

## Localization ##
# myModel = model.localization(
#     config="/path/to/classification.yaml"
# )

# Train on your dataset
myModel.train(
    train_set="/path/to/train_annotations.json",
    valid_set="/path/to/valid_annotations.json",
    pretrained=/path/to/  # or path to pretrained checkpoint
)

Train on Multiple GPU (DDP)

from opensportslib import model

def main():
    myModel = model.classification(
        config="/path/to/classification.yaml",
        data_dir="/path/to/dataset_root"
    )

    ## Localization ##
    # myModel = model.localization(
    #     config="/path/to/classification.yaml"
    # )

    myModel.train(
        train_set="/path/to/train_annotations.json",
        valid_set="/path/to/valid_annotations.json",
        pretrained="/path/to/pretrained.pt",  # optional
        use_ddp=True,  # IMPORTANT
    )

if __name__ == "__main__":
    main()

Test / Inference on SINGLE GPU

from opensportslib import model

# Load trained model
myModel = model.classification(
    config="/path/to/classification.yaml"
)

## Localization ##
# myModel = model.localization(
#     config="/path/to/classification.yaml"
# )

# Run inference on test set
metrics = myModel.infer(
    test_set="/path/to/test_annotations.json",
    pretrained="/path/to/checkpoints/final_model",
    predictions="/path/to/predictions.json"
)

Test / Inference on Multiple GPU (DDP)

from opensportslib import model

def main():
    myModel = model.classification(
        config="/path/to/classification.yaml",
        data_dir="/path/to/dataset_root"
    )

    ## Localization ##
    # myModel = model.localization(
    #     config="/path/to/classification.yaml"
    # )

    metrics = myModel.infer(
        test_set="/path/to/test_annotations.json",
        pretrained="/path/to/checkpoints/best.pt",
        predictions="/path/to/predictions.json",
        use_ddp=True,   # optional (usually not needed)
    )

    print(metrics)

if __name__ == "__main__":
    main()

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

opensportslib-0.0.1.dev3.tar.gz (129.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

opensportslib-0.0.1.dev3-py3-none-any.whl (157.4 kB view details)

Uploaded Python 3

File details

Details for the file opensportslib-0.0.1.dev3.tar.gz.

File metadata

  • Download URL: opensportslib-0.0.1.dev3.tar.gz
  • Upload date:
  • Size: 129.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for opensportslib-0.0.1.dev3.tar.gz
Algorithm Hash digest
SHA256 6c40a6ec5ddb097dad7578aac52c803a7030f9e7909984926874ece324ae5b2e
MD5 bcba9e3c11bf4ad6abea6e8d3dc1d3bb
BLAKE2b-256 6316f566590c56035fbc01f8703f6bc154c090e582b1710145af022c491fcb69

See more details on using hashes here.

File details

Details for the file opensportslib-0.0.1.dev3-py3-none-any.whl.

File metadata

File hashes

Hashes for opensportslib-0.0.1.dev3-py3-none-any.whl
Algorithm Hash digest
SHA256 91a5db72983a5bc863a13b5648b8599c6242e797b36624a2a108cc63aaec106e
MD5 5e37a51a5f67ccbcc98a21b7d3eeb9a9
BLAKE2b-256 f99248dcd664162bab5ac9d907cf8f72acea7b5f1ba49004111af1fc65a4f429

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page