opensportslib 0.2.0.dev1__tar.gz → 0.2.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. {opensportslib-0.2.0.dev1/opensportslib.egg-info → opensportslib-0.2.0.dev2}/PKG-INFO +31 -1
  2. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/README.md +30 -0
  3. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/apis/__init__.py +2 -0
  4. opensportslib-0.2.0.dev2/opensportslib/apis/vqa.py +313 -0
  5. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/cli.py +4 -0
  6. opensportslib-0.2.0.dev2/opensportslib/configs/vqa/qwen.yaml +228 -0
  7. opensportslib-0.2.0.dev2/opensportslib/configs/vqa/xvars_lora.yaml +243 -0
  8. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/accessors.py +234 -0
  9. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/validate.py +15 -0
  10. opensportslib-0.2.0.dev2/opensportslib/core/trainer/vqa_trainer.py +797 -0
  11. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/config.py +6 -2
  12. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/ddp.py +3 -2
  13. opensportslib-0.2.0.dev2/opensportslib/core/utils/hf_runtime.py +524 -0
  14. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/datasets/builder.py +4 -0
  15. opensportslib-0.2.0.dev2/opensportslib/datasets/vqa_dataset.py +165 -0
  16. opensportslib-0.2.0.dev2/opensportslib/metrics/vqa_metric.py +217 -0
  17. opensportslib-0.2.0.dev2/opensportslib/models/base/qwen_xvars.py +476 -0
  18. opensportslib-0.2.0.dev2/opensportslib/models/base/video_chatgpt_compat.py +298 -0
  19. opensportslib-0.2.0.dev2/opensportslib/models/base/xvars_videochatgpt.py +1115 -0
  20. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/builder.py +34 -0
  21. opensportslib-0.2.0.dev2/opensportslib/models/utils/vqa_prediction_priors.py +105 -0
  22. opensportslib-0.2.0.dev2/opensportslib/models/utils/vqa_prompting.py +56 -0
  23. opensportslib-0.2.0.dev2/opensportslib/models/utils/xvars_clip_index.py +99 -0
  24. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/setup/setup.py +36 -2
  25. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2/opensportslib.egg-info}/PKG-INFO +31 -1
  26. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/SOURCES.txt +19 -0
  27. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/pyproject.toml +1 -1
  28. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/conftest.py +140 -1
  29. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_config_architecture.py +6 -0
  30. opensportslib-0.2.0.dev2/tests/test_config_utils_smoke.py +84 -0
  31. opensportslib-0.2.0.dev2/tests/test_extract_xvars_features.py +134 -0
  32. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_public_apis_smoke.py +15 -0
  33. opensportslib-0.2.0.dev2/tests/test_task_model_api_contract.py +919 -0
  34. opensportslib-0.2.0.dev2/tests/test_vqa_metrics_semantic.py +122 -0
  35. opensportslib-0.2.0.dev2/tests/test_vqa_training_lora.py +563 -0
  36. opensportslib-0.2.0.dev2/tests/test_vqa_xvars_videochatgpt.py +1387 -0
  37. opensportslib-0.2.0.dev2/tools/convert/build_xvars_indexes.py +200 -0
  38. opensportslib-0.2.0.dev2/tools/convert/extract_xvars_clip_features.py +374 -0
  39. opensportslib-0.2.0.dev1/tests/test_config_utils_smoke.py +0 -46
  40. opensportslib-0.2.0.dev1/tests/test_task_model_api_contract.py +0 -402
  41. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/LICENSE +0 -0
  42. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/LICENSE-COMMERCIAL +0 -0
  43. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/MANIFEST.in +0 -0
  44. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/examples/quickstart/basic_classification.py +0 -0
  45. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/examples/quickstart/basic_localization.py +0 -0
  46. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/__init__.py +0 -0
  47. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/apis/base_task_model.py +0 -0
  48. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/apis/classification.py +0 -0
  49. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/apis/localization.py +0 -0
  50. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/classification/default.yaml +0 -0
  51. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
  52. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
  53. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/classification/video.yaml +0 -0
  54. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/default.yaml +0 -0
  55. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
  56. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/localization/default.yaml +0 -0
  57. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
  58. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/localization/video_dali.yaml +0 -0
  59. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/configs/localization/video_ocv.yaml +0 -0
  60. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/__init__.py +0 -0
  61. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/__init__.py +0 -0
  62. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/conflicts.py +0 -0
  63. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/loader.py +0 -0
  64. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/migrate.py +0 -0
  65. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/migrations/__init__.py +0 -0
  66. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  67. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/runtime_adapter.py +0 -0
  68. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/schema.py +0 -0
  69. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/schemas/__init__.py +0 -0
  70. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  71. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  72. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/__init__.py +0 -0
  73. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/builder.py +0 -0
  74. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/calf.py +0 -0
  75. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/ce.py +0 -0
  76. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/combine.py +0 -0
  77. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/nll.py +0 -0
  78. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/optimizer/__init__.py +0 -0
  79. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/optimizer/builder.py +0 -0
  80. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  81. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/scheduler/__init__.py +0 -0
  82. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/scheduler/builder.py +0 -0
  83. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/trainer/__init__.py +0 -0
  84. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/trainer/classification_trainer.py +0 -0
  85. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/trainer/localization_trainer.py +0 -0
  86. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/checkpoint.py +0 -0
  87. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/config_normalize.py +0 -0
  88. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/data.py +0 -0
  89. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/default_args.py +0 -0
  90. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/lightning.py +0 -0
  91. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/load_annotations.py +0 -0
  92. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/seed.py +0 -0
  93. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/video_processing.py +0 -0
  94. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/wandb.py +0 -0
  95. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/datasets/__init__.py +0 -0
  96. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/datasets/classification_dataset.py +0 -0
  97. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/datasets/localization_dataset.py +0 -0
  98. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/datasets/utils/__init__.py +0 -0
  99. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/datasets/utils/tracking.py +0 -0
  100. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/classification.yaml +0 -0
  101. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  102. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  103. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  104. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/localization.yaml +0 -0
  105. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  106. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  107. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/metrics/classification_metric.py +0 -0
  108. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/metrics/localization_metric.py +0 -0
  109. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/__init__.py +0 -0
  110. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/backbones/builder.py +0 -0
  111. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/base/contextaware.py +0 -0
  112. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/base/e2e.py +0 -0
  113. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/base/learnablepooling.py +0 -0
  114. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/base/tracking.py +0 -0
  115. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/base/vars.py +0 -0
  116. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/base/video.py +0 -0
  117. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/base/video_mae.py +0 -0
  118. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/heads/builder.py +0 -0
  119. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/neck/builder.py +0 -0
  120. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/common.py +0 -0
  121. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/__init__.py +0 -0
  122. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/asformer.py +0 -0
  123. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/calf.py +0 -0
  124. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/gsm.py +0 -0
  125. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/gtad.py +0 -0
  126. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/tsm.py +0 -0
  127. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/litebase.py +0 -0
  128. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/modules.py +0 -0
  129. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/shift.py +0 -0
  130. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/utils.py +0 -0
  131. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/tools/__init__.py +0 -0
  132. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/tools/_common.py +0 -0
  133. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/tools/hf_transfer.py +0 -0
  134. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/tools/osl_json_to_parquet.py +0 -0
  135. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib/tools/parquet_to_osl_json.py +0 -0
  136. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/dependency_links.txt +0 -0
  137. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/entry_points.txt +0 -0
  138. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/requires.txt +0 -0
  139. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/top_level.txt +0 -0
  140. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/setup.cfg +0 -0
  141. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_classification_dataset_paths.py +0 -0
  142. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_classification_trainer_dataloader.py +0 -0
  143. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_config_split_override_sync.py +0 -0
  144. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_conversion_tools.py +0 -0
  145. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_hf_transfer_tools.py +0 -0
  146. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_localization_dali_filenames.py +0 -0
  147. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_package_smoke.py +0 -0
  148. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_pretrained_config_merge_policy.py +0 -0
  149. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tests/test_subset_train_infer_integration.py +0 -0
  150. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tools/convert/build_soccernet_gar.py +0 -0
  151. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  152. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  153. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  154. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tools/download/download_hf_repo.py +0 -0
  155. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tools/download/download_osl_hf.py +0 -0
  156. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tools/download/upload_osl_hf.py +0 -0
  157. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tools/training/classification.py +0 -0
  158. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev2}/tools/training/localization.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.2.0.dev1
3
+ Version: 0.2.0.dev2
4
4
  Summary: 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.
5
5
  Author: Jeet Vora
6
6
  Requires-Python: >=3.12
@@ -42,6 +42,7 @@ It provides a unified framework to **train, evaluate, and run inference** for ke
42
42
 
43
43
  - **Action classification**
44
44
  - **Action localization / spotting**
45
+ - **Visual Question Answering (VQA)**
45
46
  - **Action retrieval**
46
47
  - **Action description / captioning**
47
48
 
@@ -280,6 +281,32 @@ metrics_from_file = my_model.evaluate(
280
281
  )
281
282
  ```
282
283
 
284
+ ### VQA example
285
+
286
+ ```python
287
+ from opensportslib.apis import VQAModel
288
+
289
+ my_model = VQAModel(
290
+ config="/path/to/vqa.yaml",
291
+ weights=None, # optional: path or Hugging Face model ID
292
+ )
293
+
294
+ predictions = my_model.infer(
295
+ test_set="/path/to/test_annotations.json",
296
+ )
297
+
298
+ # Headless single-video VQA uses the same prediction payload shape.
299
+ single_prediction = my_model.infer(
300
+ video_path="/path/to/video.mp4",
301
+ question="What card would you give? Why?",
302
+ )
303
+
304
+ metrics = my_model.evaluate(
305
+ test_set="/path/to/test_annotations.json",
306
+ predictions=predictions,
307
+ )
308
+ ```
309
+
283
310
 
284
311
  ---
285
312
 
@@ -316,6 +343,9 @@ Classify clips or event centered samples into predefined categories.
316
343
  ### Action Localization / Spotting
317
344
  Predict when key events happen in long untrimmed sports videos.
318
345
 
346
+ ### Visual Question Answering (VQA)
347
+ Answer natural-language questions about sports video clips.
348
+
319
349
  ### Action Retrieval
320
350
  Search and retrieve relevant clips or moments from a collection of sports videos.
321
351
  This is part of the roadmap and OSL data model, not a first-class OpenSportsLib
@@ -7,6 +7,7 @@ It provides a unified framework to **train, evaluate, and run inference** for ke
7
7
 
8
8
  - **Action classification**
9
9
  - **Action localization / spotting**
10
+ - **Visual Question Answering (VQA)**
10
11
  - **Action retrieval**
11
12
  - **Action description / captioning**
12
13
 
@@ -245,6 +246,32 @@ metrics_from_file = my_model.evaluate(
245
246
  )
246
247
  ```
247
248
 
249
+ ### VQA example
250
+
251
+ ```python
252
+ from opensportslib.apis import VQAModel
253
+
254
+ my_model = VQAModel(
255
+ config="/path/to/vqa.yaml",
256
+ weights=None, # optional: path or Hugging Face model ID
257
+ )
258
+
259
+ predictions = my_model.infer(
260
+ test_set="/path/to/test_annotations.json",
261
+ )
262
+
263
+ # Headless single-video VQA uses the same prediction payload shape.
264
+ single_prediction = my_model.infer(
265
+ video_path="/path/to/video.mp4",
266
+ question="What card would you give? Why?",
267
+ )
268
+
269
+ metrics = my_model.evaluate(
270
+ test_set="/path/to/test_annotations.json",
271
+ predictions=predictions,
272
+ )
273
+ ```
274
+
248
275
 
249
276
  ---
250
277
 
@@ -281,6 +308,9 @@ Classify clips or event centered samples into predefined categories.
281
308
  ### Action Localization / Spotting
282
309
  Predict when key events happen in long untrimmed sports videos.
283
310
 
311
+ ### Visual Question Answering (VQA)
312
+ Answer natural-language questions about sports video clips.
313
+
284
314
  ### Action Retrieval
285
315
  Search and retrieve relevant clips or moments from a collection of sports videos.
286
316
  This is part of the roadmap and OSL data model, not a first-class OpenSportsLib
@@ -4,6 +4,7 @@
4
4
  from opensportslib.apis.base_task_model import BaseTaskModel
5
5
  from opensportslib.apis.classification import ClassificationModel
6
6
  from opensportslib.apis.localization import LocalizationModel
7
+ from opensportslib.apis.vqa import VQAModel
7
8
  import warnings
8
9
  warnings.filterwarnings("ignore")
9
10
 
@@ -12,4 +13,5 @@ __all__ = [
12
13
  "BaseTaskModel",
13
14
  "ClassificationModel",
14
15
  "LocalizationModel",
16
+ "VQAModel",
15
17
  ]
@@ -0,0 +1,313 @@
1
+ """Public API for VQA tasks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ from typing import Any
8
+
9
+ from opensportslib.apis.base_task_model import BaseTaskModel
10
+ from opensportslib.core.config.accessors import get_split_annotation_path, get_system_gpu_count, get_train_execution, get_vqa_backend
11
+ from opensportslib.core.utils.config import expand, resolve_config_omega
12
+
13
+
14
+ def _set_model_checkpoint_path(config, weights: str | None) -> None:
15
+ if weights is None:
16
+ return
17
+ model = getattr(config, "MODEL", None)
18
+ if model is None:
19
+ return
20
+ load = getattr(model, "load", None)
21
+ if load is None:
22
+ from types import SimpleNamespace
23
+
24
+ load = SimpleNamespace()
25
+ setattr(model, "load", load)
26
+ if isinstance(load, dict):
27
+ load["checkpoint_path"] = weights
28
+ else:
29
+ setattr(load, "checkpoint_path", weights)
30
+
31
+
32
+ class VQAModel(BaseTaskModel):
33
+ """Top-level task wrapper for VQA."""
34
+
35
+ def _init_wandb(self, use_wandb: bool) -> None:
36
+ from opensportslib.core.utils.wandb import init_wandb
37
+
38
+ init_wandb(
39
+ self.config_path,
40
+ self.config,
41
+ run_id=os.environ["RUN_ID"],
42
+ use_wandb=use_wandb,
43
+ )
44
+
45
+ @staticmethod
46
+ def _worker_ddp(
47
+ rank,
48
+ world_size,
49
+ config_path,
50
+ config,
51
+ return_queue=None,
52
+ train_set=None,
53
+ valid_set=None,
54
+ use_wandb=False,
55
+ ):
56
+ import torch
57
+ from opensportslib.core.trainer.vqa_trainer import Trainer_VQA
58
+ from opensportslib.core.utils.ddp import ddp_cleanup, ddp_setup
59
+ from opensportslib.core.utils.wandb import init_wandb
60
+ from opensportslib.datasets.builder import build_dataset
61
+
62
+ logging.basicConfig(
63
+ level=logging.INFO,
64
+ format=f"[RANK {rank}] %(asctime)s | %(levelname)s | %(message)s",
65
+ force=True,
66
+ )
67
+ if rank != 0:
68
+ logging.getLogger().setLevel(logging.ERROR)
69
+ # Keep INFO on all ranks while debugging multi-GPU startup/hangs.
70
+ # This makes DDP failures visible instead of appearing as a silent stall.
71
+ #logging.getLogger().setLevel(logging.INFO)
72
+
73
+ is_ddp = world_size > 1
74
+ if is_ddp:
75
+ os.environ["RANK"] = str(rank)
76
+ os.environ["WORLD_SIZE"] = str(world_size)
77
+ os.environ["LOCAL_RANK"] = str(rank)
78
+ if "TORCH_DISTRIBUTED_DEBUG" not in os.environ:
79
+ os.environ["TORCH_DISTRIBUTED_DEBUG"] = "INFO"
80
+ torch.cuda.set_device(rank)
81
+ ddp_setup(rank, world_size)
82
+ logging.info(
83
+ "Initialized VQA DDP worker | rank=%s | world_size=%s | torch_distributed_debug=%s",
84
+ rank,
85
+ world_size,
86
+ os.environ.get("TORCH_DISTRIBUTED_DEBUG"),
87
+ )
88
+
89
+ try:
90
+ if rank == 0:
91
+ init_wandb(
92
+ config_path,
93
+ config,
94
+ run_id=os.environ["RUN_ID"],
95
+ use_wandb=use_wandb,
96
+ )
97
+ train_data = build_dataset(config, train_set, None, split="train")
98
+ valid_data = build_dataset(config, valid_set, None, split="valid")
99
+ trainer = Trainer_VQA(config)
100
+ ckpt = trainer.train(
101
+ None,
102
+ train_data,
103
+ valid_data,
104
+ rank=rank,
105
+ world_size=world_size,
106
+ use_wandb=use_wandb,
107
+ )
108
+ if rank == 0 and return_queue is not None:
109
+ return_queue.put(ckpt)
110
+ finally:
111
+ if is_ddp:
112
+ ddp_cleanup()
113
+
114
+ def _resolve_split_path(self, split: str, override: str | None = None) -> str:
115
+ if override is not None:
116
+ return expand(override)
117
+ path = get_split_annotation_path(self.config, split)
118
+ if not path:
119
+ raise ValueError(
120
+ f"Could not resolve path for split '{split}'. "
121
+ f"Expected DATA.common.splits.{split}.annotation_path."
122
+ )
123
+ return expand(path)
124
+
125
+ def load_weights(self, weights: str | None = None, **kwargs) -> None:
126
+ del kwargs
127
+ if weights is None:
128
+ raise ValueError("`weights` must be provided to load_weights().")
129
+ from opensportslib.core.trainer.vqa_trainer import Trainer_VQA
130
+
131
+ _set_model_checkpoint_path(self.config, weights)
132
+ self.trainer = Trainer_VQA(self.config)
133
+ self.trainer.load(weights)
134
+ self.last_loaded_weights = weights
135
+ self.best_checkpoint = weights
136
+
137
+ def train(
138
+ self,
139
+ train_set: str | None = None,
140
+ valid_set: str | None = None,
141
+ weights: str | None = None,
142
+ use_wandb: bool = True,
143
+ **kwargs,
144
+ ) -> str | None:
145
+ del kwargs
146
+ import torch
147
+ import torch.multiprocessing as mp
148
+
149
+ self.config = resolve_config_omega(self.config, weights=weights)
150
+ if get_vqa_backend(self.config) == "qwen_xvars_infer":
151
+ raise ValueError("The 'qwen_xvars_infer' backend is inference-only and does not support train().")
152
+ train_set = self._resolve_split_path("train", train_set)
153
+ valid_set = self._resolve_split_path("valid", valid_set)
154
+ execution = get_train_execution(self.config)
155
+ backend = str(execution.get("training_backend", "placeholder")).lower()
156
+ if backend == "xvars_videochatgpt_lora":
157
+ world_size = torch.cuda.device_count() or get_system_gpu_count(self.config)
158
+ requested_gpus = get_system_gpu_count(self.config)
159
+ use_ddp = world_size > 1 and int(requested_gpus) > 1
160
+ logging.info(
161
+ "VQA train launch | mode=%s | world_size=%s",
162
+ "ddp" if use_ddp else "single",
163
+ world_size if use_ddp else 1,
164
+ )
165
+
166
+ ctx = mp.get_context("spawn")
167
+ queue = ctx.SimpleQueue()
168
+ if use_ddp:
169
+ mp.spawn(
170
+ VQAModel._worker_ddp,
171
+ args=(world_size, self.config_path, self.config, queue, train_set, valid_set, use_wandb),
172
+ nprocs=world_size,
173
+ )
174
+ else:
175
+ VQAModel._worker_ddp(
176
+ rank=0,
177
+ world_size=1,
178
+ config_path=self.config_path,
179
+ config=self.config,
180
+ return_queue=queue,
181
+ train_set=train_set,
182
+ valid_set=valid_set,
183
+ use_wandb=use_wandb,
184
+ )
185
+ ckpt = queue.get()
186
+ self.best_checkpoint = ckpt
187
+ self.last_loaded_weights = ckpt
188
+ return ckpt
189
+
190
+ raise ValueError(
191
+ f"Unsupported VQA training backend '{backend}'. "
192
+ "Only 'xvars_videochatgpt_lora' is supported."
193
+ )
194
+
195
+ def infer(
196
+ self,
197
+ test_set: str | None = None,
198
+ weights: str | None = None,
199
+ use_wandb: bool = True,
200
+ video_path: str | None = None,
201
+ question: str | None = None,
202
+ **kwargs,
203
+ ) -> dict:
204
+ del kwargs
205
+ from opensportslib.core.trainer.vqa_trainer import Trainer_VQA
206
+ from opensportslib.datasets.builder import build_dataset
207
+ from opensportslib.models.builder import build_model
208
+ from opensportslib.core.utils.config import select_device
209
+
210
+ direct_requested = video_path is not None or question is not None
211
+ if direct_requested and test_set is not None:
212
+ raise ValueError("Provide either `test_set` or `video_path`/`question`, not both.")
213
+ if direct_requested and (not video_path or not str(question or "").strip()):
214
+ raise ValueError("Direct VQA inference requires both `video_path` and a non-empty `question`.")
215
+
216
+ self.config = resolve_config_omega(self.config, weights=weights)
217
+ backend = get_vqa_backend(self.config)
218
+ effective_weights = weights if weights is not None else self.last_loaded_weights
219
+ if backend == "qwen_xvars_infer" and effective_weights is not None:
220
+ raise ValueError("The 'qwen_xvars_infer' backend does not support adapter weights for infer().")
221
+ _set_model_checkpoint_path(self.config, effective_weights)
222
+ self.trainer = Trainer_VQA(self.config)
223
+ if effective_weights is not None:
224
+ # Validate OpenSportsLib adapter metadata before allocating the base model.
225
+ self.trainer.load(effective_weights)
226
+ resolved_video_path = None
227
+ if direct_requested:
228
+ resolved_video_path = expand(str(video_path))
229
+ if not os.path.isfile(resolved_video_path):
230
+ raise FileNotFoundError(f"Video file not found: {resolved_video_path}")
231
+ device = select_device(self.config.SYSTEM)
232
+ model, _ = build_model(self.config, device)
233
+ if direct_requested:
234
+ test_data = [
235
+ {
236
+ "id": os.path.splitext(os.path.basename(resolved_video_path))[0],
237
+ "question": str(question).strip(),
238
+ "references": [],
239
+ "video_path": resolved_video_path,
240
+ "video_spatio_temporal_features": None,
241
+ "prior_prediction_text": "",
242
+ "labels": {},
243
+ "metadata": {},
244
+ "_xvars_demo_parity_direct_infer": True,
245
+ }
246
+ ]
247
+ else:
248
+ test_set = self._resolve_split_path("test", test_set)
249
+ test_data = build_dataset(self.config, test_set, None, split="test")
250
+ self._init_wandb(use_wandb=use_wandb)
251
+ return self.trainer.infer(model, test_data, use_wandb=use_wandb)
252
+
253
+ def evaluate(
254
+ self,
255
+ test_set: str | None = None,
256
+ weights: str | None = None,
257
+ predictions: str | dict | None = None,
258
+ use_wandb: bool = True,
259
+ **kwargs,
260
+ ) -> dict | str | None:
261
+ del kwargs
262
+ from opensportslib.core.trainer.vqa_trainer import Trainer_VQA
263
+ from opensportslib.datasets.builder import build_dataset
264
+
265
+ self.config = resolve_config_omega(self.config, weights=weights)
266
+ test_set = self._resolve_split_path("test", test_set)
267
+ test_data = build_dataset(self.config, test_set, None, split="test")
268
+ self._init_wandb(use_wandb=use_wandb)
269
+ if predictions is None:
270
+ predictions = self.infer(test_set=test_set, weights=weights, use_wandb=use_wandb)
271
+ elif isinstance(predictions, str):
272
+ import json
273
+
274
+ with open(expand(predictions), encoding="utf-8") as f:
275
+ predictions = json.load(f)
276
+ self.trainer = self.trainer or Trainer_VQA(self.config)
277
+ return self.trainer.evaluate(predictions, test_data, use_wandb=use_wandb)
278
+
279
+ def save_predictions(
280
+ self,
281
+ output_path: str,
282
+ predictions: dict,
283
+ output_format: str = "osl",
284
+ ) -> str:
285
+ """Persist VQA predictions, optionally as X-VARS-compatible rows."""
286
+
287
+ if str(output_format).lower() != "xvars":
288
+ return super().save_predictions(output_path, predictions)
289
+
290
+ import json
291
+
292
+ payload = self._to_xvars_prediction_rows(predictions)
293
+ dst = expand(output_path)
294
+ os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
295
+ with open(dst, "w", encoding="utf-8") as f:
296
+ json.dump(payload, f)
297
+ return dst
298
+
299
+ @staticmethod
300
+ def _to_xvars_prediction_rows(predictions: dict[str, Any]) -> list[dict[str, Any]]:
301
+ rows = []
302
+ for item in predictions.get("data", []) if isinstance(predictions, dict) else []:
303
+ video_path = str(item.get("video_path") or "")
304
+ video_name = os.path.splitext(os.path.basename(video_path))[0] if video_path else str(item.get("id"))
305
+ rows.append(
306
+ {
307
+ "id": item.get("id"),
308
+ "video_name": video_name,
309
+ "Q": item.get("question"),
310
+ "pred": item.get("answer_text"),
311
+ }
312
+ )
313
+ return rows
@@ -11,6 +11,8 @@ def main(argv: Optional[list[str]] = None) -> int:
11
11
  parser.add_argument("command", choices=["setup"])
12
12
  parser.add_argument("--pyg", action="store_true")
13
13
  parser.add_argument("--dali", action="store_true")
14
+ parser.add_argument("--xvars", action="store_true")
15
+ parser.add_argument("--qwen", action="store_true")
14
16
 
15
17
  args = parser.parse_args(argv)
16
18
 
@@ -18,6 +20,8 @@ def main(argv: Optional[list[str]] = None) -> int:
18
20
  setup(
19
21
  pyg=args.pyg,
20
22
  dali=args.dali,
23
+ xvars=args.xvars,
24
+ qwen=args.qwen
21
25
  )
22
26
  return 0
23
27
 
@@ -0,0 +1,228 @@
1
+ TASK: VQA
2
+ VERSION: 2
3
+
4
+ SYSTEM:
5
+ paths:
6
+ log_dir: ./logs
7
+ save_dir: ./checkpoints_vqa_qwen
8
+ work_dir: ./checkpoints_vqa_qwen
9
+ device: cuda
10
+ gpu:
11
+ count: 1
12
+ id: 0
13
+ reproducibility:
14
+ use_seed: true
15
+ seed: 42
16
+
17
+ DATA:
18
+ common:
19
+ dataset_name: OSL-XFoul
20
+ data_root: /home/vorajv/dataset/OSL-XFoul
21
+ feature_index: /home/vorajv/dataset/OSL-XFoul/feature_index.json
22
+ prediction_index: /home/vorajv/dataset/OSL-XFoul/prediction_index.json
23
+ runtime:
24
+ loader_backend: opencv
25
+ splits:
26
+ train:
27
+ annotation_path: /home/vorajv/dataset/OSL-XFoul/train.json
28
+ source_path: /home/vorajv/dataset/OSL-XFoul
29
+ dataloader:
30
+ batch_size: 1
31
+ shuffle: true
32
+ num_workers: 0
33
+ pin_memory: false
34
+ mp_context: spawn
35
+ persistent_workers: false
36
+ valid:
37
+ annotation_path: /home/vorajv/dataset/OSL-XFoul/valid.json
38
+ source_path: /home/vorajv/dataset/OSL-XFoul
39
+ dataloader:
40
+ batch_size: 1
41
+ shuffle: false
42
+ num_workers: 0
43
+ pin_memory: false
44
+ mp_context: spawn
45
+ persistent_workers: false
46
+ test:
47
+ annotation_path: /home/vorajv/dataset/OSL-XFoul/test.json
48
+ source_path: /home/vorajv/dataset/OSL-XFoul
49
+ dataloader:
50
+ batch_size: 1
51
+ shuffle: false
52
+ num_workers: 0
53
+ pin_memory: false
54
+ mp_context: spawn
55
+ persistent_workers: false
56
+ inputs:
57
+ video:
58
+ modality: video
59
+ representation: raw
60
+ source:
61
+ format: mp4
62
+ sampling:
63
+ num_frames: 100
64
+ input_fps: 25
65
+ target_fps: 17
66
+ start_frame: 63
67
+ end_frame: 87
68
+ transform: {}
69
+ augmentations: {}
70
+ params: {}
71
+ question:
72
+ modality: text
73
+ representation: raw
74
+ source:
75
+ format: json
76
+ sampling: {}
77
+ transform: {}
78
+ augmentations: {}
79
+ params: {}
80
+
81
+ MODEL:
82
+ runtime:
83
+ dtype: fp16
84
+ device: auto
85
+ compile: false
86
+ freeze: false
87
+ load:
88
+ checkpoint_path: null
89
+ pretrained: false
90
+ strict: true
91
+ map_location: null
92
+ format: auto
93
+ components:
94
+ video_encoder:
95
+ kind: encoder
96
+ source:
97
+ provider: opensportslib
98
+ name: xvars_clip_features
99
+ load:
100
+ weights_path: /home/vorajv/X-VARS/weights/14_model.pth.tar
101
+ params:
102
+ feature_source: indexed_or_raw_clip
103
+ vision_tower: openai/clip-vit-large-patch14
104
+ feature_dim: 1024
105
+ overrides: {}
106
+ mm_projector:
107
+ kind: projector
108
+ source:
109
+ provider: opensportslib
110
+ params:
111
+ input_dim: 1024
112
+ overrides: {}
113
+ llm_decoder:
114
+ kind: decoder
115
+ source:
116
+ provider: huggingface
117
+ # Qwen/Qwen2.5-7B-Instruct or Qwen/Qwen3.5-9B-Base
118
+ name: Qwen/Qwen3.5-9B-Base
119
+ params:
120
+ # Qwen/Qwen2.5-7B-Instruct or Qwen/Qwen3.5-9B-Base
121
+ repo_id: Qwen/Qwen3.5-9B-Base
122
+ overrides: {}
123
+ topology:
124
+ - from: video_encoder
125
+ to: mm_projector
126
+ - from: mm_projector
127
+ to: llm_decoder
128
+ metadata:
129
+ backend: qwen_xvars_infer
130
+
131
+ IO:
132
+ inputs:
133
+ video: video_encoder
134
+ question: llm_decoder
135
+ outputs:
136
+ answer_text: llm_decoder
137
+ explanation_text: llm_decoder
138
+
139
+ TRAIN:
140
+ trainer:
141
+ type: vqa
142
+
143
+ epochs: 3
144
+
145
+ criterion:
146
+ type: CrossEntropyLoss
147
+
148
+ optimizer:
149
+ type: AdamW
150
+ lr: 0.0002
151
+ weight_decay: 0.001
152
+
153
+ scheduler:
154
+ type: constant
155
+
156
+ execution:
157
+ enabled: true
158
+ training_backend: xvars_videochatgpt_lora
159
+ feature_backend: xvars_clip
160
+ view_sampling_policy: random_train_deterministic_eval
161
+ acc_grad_iter: 8
162
+ log_interval: 1
163
+ dry_run: false
164
+
165
+ xvars:
166
+ feature_mode: strict_xvars
167
+ projection_path: null
168
+
169
+ prompt:
170
+ style: detailed
171
+ system_prompt: You are a football video assistant. Answer the VQA question using the provided video context and referee priors.
172
+ include_priors: true
173
+ prediction_prior_adapter: xvars_referee
174
+ prior_fields: [action, offence, contact, bodypart]
175
+ video_token_len: 300
176
+
177
+ generation:
178
+ max_new_tokens: 128
179
+ temperature: 0.0
180
+
181
+ eval_profile:
182
+ metric_set: [exact_match, contains_match, token_f1, referee_semantic]
183
+ aggregation: mean
184
+ exclusions: []
185
+
186
+ sft:
187
+ max_seq_length: 480
188
+ include_video_tokens: true
189
+ disable_tqdm: false
190
+ reference_mode: all
191
+ append_eos_token: true
192
+ gradient_checkpointing: true
193
+ save_strategy: epoch
194
+
195
+ hf:
196
+ tokenizer_id: ${MODEL.components.llm_decoder.params.repo_id}
197
+ prefer_cuda: true
198
+ local_files_only: false
199
+ device_map: auto
200
+ offload_folder: ./hf_offload_qwen
201
+
202
+ lora:
203
+ r: 16
204
+ alpha: 32
205
+ dropout: 0.05
206
+ bias: none
207
+ prepare_kbit: true
208
+ target_modules: [mm_projector, upsample_features, up_proj, down_proj, gate_proj, k_proj, q_proj, v_proj, o_proj]
209
+ exclude_modules: '^base_lm\.model\.mm_projector$'
210
+
211
+ quantization:
212
+ enabled: false
213
+ load_in_4bit: true
214
+ bnb_4bit_quant_type: nf4
215
+ compute_dtype: float16
216
+ bnb_4bit_use_double_quant: true
217
+
218
+ checkpoint:
219
+ save_adapter: true
220
+ merge_and_save: false
221
+
222
+ selection:
223
+ monitor: loss
224
+ mode: min
225
+
226
+ checkpoint:
227
+ save_every: 1
228
+ save_best: true