opensportslib 0.2.0__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 (163) hide show
  1. {opensportslib-0.2.0/opensportslib.egg-info → opensportslib-0.2.0.dev2}/PKG-INFO +32 -2
  2. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/README.md +31 -1
  3. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/examples/quickstart/basic_localization.py +1 -1
  4. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/apis/__init__.py +2 -0
  5. opensportslib-0.2.0.dev2/opensportslib/apis/vqa.py +313 -0
  6. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/cli.py +4 -0
  7. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/configs/classification/default.yaml +11 -22
  8. opensportslib-0.2.0/opensportslib/configs/classification/frames_npy/sngar-frames.yaml → opensportslib-0.2.0.dev2/opensportslib/configs/classification/sngar_frames.yaml +0 -50
  9. opensportslib-0.2.0/opensportslib/configs/classification/tracking/sngar-tracking.yaml → opensportslib-0.2.0.dev2/opensportslib/configs/classification/sngar_tracking.yaml +3 -53
  10. opensportslib-0.2.0/opensportslib/configs/classification/video/classification.yaml → opensportslib-0.2.0.dev2/opensportslib/configs/classification/video.yaml +1 -60
  11. opensportslib-0.2.0.dev2/opensportslib/configs/default.yaml +30 -0
  12. opensportslib-0.2.0/opensportslib/configs/localization/video_features/localization-calf-resnetpca512.yaml → opensportslib-0.2.0.dev2/opensportslib/configs/localization/calf_resnetpca512.yaml +5 -55
  13. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/configs/localization/default.yaml +14 -21
  14. opensportslib-0.2.0/opensportslib/configs/localization/video_features/localization-netvladpp-resnetpca512.yaml → opensportslib-0.2.0.dev2/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +5 -58
  15. opensportslib-0.2.0/opensportslib/configs/localization/video/localization-dali.yaml → opensportslib-0.2.0.dev2/opensportslib/configs/localization/video_dali.yaml +2 -69
  16. opensportslib-0.2.0/opensportslib/configs/localization/video/localization-ocv.yaml → opensportslib-0.2.0.dev2/opensportslib/configs/localization/video_ocv.yaml +2 -71
  17. opensportslib-0.2.0.dev2/opensportslib/configs/vqa/qwen.yaml +228 -0
  18. opensportslib-0.2.0.dev2/opensportslib/configs/vqa/xvars_lora.yaml +243 -0
  19. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/accessors.py +234 -0
  20. opensportslib-0.2.0.dev2/opensportslib/core/config/loader.py +184 -0
  21. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/validate.py +15 -0
  22. opensportslib-0.2.0.dev2/opensportslib/core/trainer/vqa_trainer.py +797 -0
  23. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/config.py +6 -2
  24. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/ddp.py +3 -2
  25. opensportslib-0.2.0.dev2/opensportslib/core/utils/hf_runtime.py +524 -0
  26. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/datasets/builder.py +4 -0
  27. opensportslib-0.2.0.dev2/opensportslib/datasets/vqa_dataset.py +165 -0
  28. opensportslib-0.2.0.dev2/opensportslib/metrics/vqa_metric.py +217 -0
  29. opensportslib-0.2.0.dev2/opensportslib/models/base/qwen_xvars.py +476 -0
  30. opensportslib-0.2.0.dev2/opensportslib/models/base/video_chatgpt_compat.py +298 -0
  31. opensportslib-0.2.0.dev2/opensportslib/models/base/xvars_videochatgpt.py +1115 -0
  32. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/builder.py +34 -0
  33. opensportslib-0.2.0.dev2/opensportslib/models/utils/vqa_prediction_priors.py +105 -0
  34. opensportslib-0.2.0.dev2/opensportslib/models/utils/vqa_prompting.py +56 -0
  35. opensportslib-0.2.0.dev2/opensportslib/models/utils/xvars_clip_index.py +99 -0
  36. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/setup/setup.py +36 -2
  37. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2/opensportslib.egg-info}/PKG-INFO +32 -2
  38. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/SOURCES.txt +27 -11
  39. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/pyproject.toml +1 -1
  40. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/conftest.py +140 -1
  41. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_config_architecture.py +43 -8
  42. opensportslib-0.2.0.dev2/tests/test_config_utils_smoke.py +84 -0
  43. opensportslib-0.2.0.dev2/tests/test_extract_xvars_features.py +134 -0
  44. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_public_apis_smoke.py +15 -0
  45. opensportslib-0.2.0.dev2/tests/test_task_model_api_contract.py +919 -0
  46. opensportslib-0.2.0.dev2/tests/test_vqa_metrics_semantic.py +122 -0
  47. opensportslib-0.2.0.dev2/tests/test_vqa_training_lora.py +563 -0
  48. opensportslib-0.2.0.dev2/tests/test_vqa_xvars_videochatgpt.py +1387 -0
  49. opensportslib-0.2.0.dev2/tools/convert/build_xvars_indexes.py +200 -0
  50. opensportslib-0.2.0.dev2/tools/convert/extract_xvars_clip_features.py +374 -0
  51. opensportslib-0.2.0/opensportslib/configs/captioning/encoder_decoder/video_captioning.yaml +0 -136
  52. opensportslib-0.2.0/opensportslib/configs/captioning/llava/llava_style.yaml +0 -148
  53. opensportslib-0.2.0/opensportslib/configs/reasoning/multimodal/video_text_fusion.yaml +0 -161
  54. opensportslib-0.2.0/opensportslib/configs/retrieval/two_tower/video_text_retrieval.yaml +0 -150
  55. opensportslib-0.2.0/opensportslib/core/config/loader.py +0 -77
  56. opensportslib-0.2.0/tests/test_config_utils_smoke.py +0 -46
  57. opensportslib-0.2.0/tests/test_task_model_api_contract.py +0 -402
  58. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/LICENSE +0 -0
  59. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/LICENSE-COMMERCIAL +0 -0
  60. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/MANIFEST.in +0 -0
  61. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/examples/quickstart/basic_classification.py +0 -0
  62. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/__init__.py +0 -0
  63. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/apis/base_task_model.py +0 -0
  64. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/apis/classification.py +0 -0
  65. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/apis/localization.py +0 -0
  66. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/__init__.py +0 -0
  67. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/__init__.py +0 -0
  68. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/conflicts.py +0 -0
  69. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/migrate.py +0 -0
  70. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/migrations/__init__.py +0 -0
  71. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  72. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/runtime_adapter.py +0 -0
  73. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/schema.py +0 -0
  74. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/schemas/__init__.py +0 -0
  75. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  76. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  77. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/__init__.py +0 -0
  78. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/builder.py +0 -0
  79. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/calf.py +0 -0
  80. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/ce.py +0 -0
  81. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/combine.py +0 -0
  82. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/loss/nll.py +0 -0
  83. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/optimizer/__init__.py +0 -0
  84. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/optimizer/builder.py +0 -0
  85. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  86. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/scheduler/__init__.py +0 -0
  87. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/scheduler/builder.py +0 -0
  88. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/trainer/__init__.py +0 -0
  89. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/trainer/classification_trainer.py +0 -0
  90. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/trainer/localization_trainer.py +0 -0
  91. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/checkpoint.py +0 -0
  92. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/config_normalize.py +0 -0
  93. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/data.py +0 -0
  94. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/default_args.py +0 -0
  95. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/lightning.py +0 -0
  96. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/load_annotations.py +0 -0
  97. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/seed.py +0 -0
  98. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/video_processing.py +0 -0
  99. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/core/utils/wandb.py +0 -0
  100. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/datasets/__init__.py +0 -0
  101. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/datasets/classification_dataset.py +0 -0
  102. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/datasets/localization_dataset.py +0 -0
  103. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/datasets/utils/__init__.py +0 -0
  104. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/datasets/utils/tracking.py +0 -0
  105. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/classification.yaml +0 -0
  106. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  107. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  108. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  109. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/localization.yaml +0 -0
  110. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  111. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  112. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/metrics/classification_metric.py +0 -0
  113. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/metrics/localization_metric.py +0 -0
  114. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/__init__.py +0 -0
  115. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/backbones/builder.py +0 -0
  116. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/base/contextaware.py +0 -0
  117. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/base/e2e.py +0 -0
  118. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/base/learnablepooling.py +0 -0
  119. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/base/tracking.py +0 -0
  120. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/base/vars.py +0 -0
  121. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/base/video.py +0 -0
  122. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/base/video_mae.py +0 -0
  123. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/heads/builder.py +0 -0
  124. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/neck/builder.py +0 -0
  125. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/common.py +0 -0
  126. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/__init__.py +0 -0
  127. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/asformer.py +0 -0
  128. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/calf.py +0 -0
  129. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/gsm.py +0 -0
  130. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/gtad.py +0 -0
  131. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/impl/tsm.py +0 -0
  132. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/litebase.py +0 -0
  133. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/modules.py +0 -0
  134. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/shift.py +0 -0
  135. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/models/utils/utils.py +0 -0
  136. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/tools/__init__.py +0 -0
  137. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/tools/_common.py +0 -0
  138. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/tools/hf_transfer.py +0 -0
  139. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/tools/osl_json_to_parquet.py +0 -0
  140. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib/tools/parquet_to_osl_json.py +0 -0
  141. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/dependency_links.txt +0 -0
  142. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/entry_points.txt +0 -0
  143. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/requires.txt +0 -0
  144. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/opensportslib.egg-info/top_level.txt +0 -0
  145. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/setup.cfg +0 -0
  146. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_classification_dataset_paths.py +0 -0
  147. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_classification_trainer_dataloader.py +0 -0
  148. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_config_split_override_sync.py +0 -0
  149. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_conversion_tools.py +0 -0
  150. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_hf_transfer_tools.py +0 -0
  151. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_localization_dali_filenames.py +0 -0
  152. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_package_smoke.py +0 -0
  153. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_pretrained_config_merge_policy.py +0 -0
  154. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tests/test_subset_train_infer_integration.py +0 -0
  155. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tools/convert/build_soccernet_gar.py +0 -0
  156. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  157. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  158. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  159. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tools/download/download_hf_repo.py +0 -0
  160. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tools/download/download_osl_hf.py +0 -0
  161. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tools/download/upload_osl_hf.py +0 -0
  162. {opensportslib-0.2.0 → opensportslib-0.2.0.dev2}/tools/training/classification.py +0 -0
  163. {opensportslib-0.2.0 → 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
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
 
@@ -257,7 +258,7 @@ print(metrics)
257
258
  from opensportslib.apis import LocalizationModel
258
259
 
259
260
  my_model = LocalizationModel(
260
- config="/path/to/localization.yaml",
261
+ config="/path/to/localization_video_dali.yaml",
261
262
  weights=None, # optional: path or Hugging Face model ID
262
263
  )
263
264
 
@@ -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
 
@@ -222,7 +223,7 @@ print(metrics)
222
223
  from opensportslib.apis import LocalizationModel
223
224
 
224
225
  my_model = LocalizationModel(
225
- config="/path/to/localization.yaml",
226
+ config="/path/to/localization_video_dali.yaml",
226
227
  weights=None, # optional: path or Hugging Face model ID
227
228
  )
228
229
 
@@ -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
@@ -8,7 +8,7 @@ def main():
8
8
  """
9
9
 
10
10
  my_model = LocalizationModel(
11
- config="examples/configs/localization.yaml",
11
+ config="examples/configs/localization_video_dali.yaml",
12
12
  weights=None, # optional: path or Hugging Face model ID
13
13
  )
14
14
 
@@ -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
 
@@ -2,23 +2,13 @@ TASK: classification
2
2
  VERSION: 2
3
3
 
4
4
  SYSTEM:
5
- paths:
6
- log_dir: ./logs
7
- save_dir: ./checkpoints
8
- device: cuda
9
5
  gpu:
10
- count: 1
11
- id: 0
12
- reproducibility:
13
- use_seed: true
14
- seed: 42
6
+ count: 4
15
7
 
16
8
  DATA:
17
9
  common:
18
10
  dataset_name: dataset_name
19
11
  data_root: /path/to/data
20
- runtime:
21
- loader_backend: opencv
22
12
  splits:
23
13
  train:
24
14
  annotation_path: /path/to/train.json
@@ -61,17 +51,6 @@ DATA:
61
51
  color_mode: rgb
62
52
 
63
53
  MODEL:
64
- runtime:
65
- dtype: fp32
66
- device: auto
67
- compile: false
68
- freeze: false
69
- load:
70
- checkpoint_path: null
71
- pretrained: false
72
- strict: true
73
- map_location: null
74
- format: auto
75
54
  components:
76
55
  video_encoder:
77
56
  kind: encoder
@@ -81,6 +60,14 @@ MODEL:
81
60
  name: null
82
61
  params: {}
83
62
  overrides: {}
63
+ video_adapter:
64
+ kind: adapter
65
+ source:
66
+ provider: opensportslib
67
+ registry: neck
68
+ name: null
69
+ params: {}
70
+ overrides: {}
84
71
  task_head:
85
72
  kind: head
86
73
  source:
@@ -91,6 +78,8 @@ MODEL:
91
78
  overrides: {}
92
79
  topology:
93
80
  - from: video_encoder
81
+ to: video_adapter
82
+ - from: video_adapter
94
83
  to: task_head
95
84
 
96
85
  IO:
@@ -1,24 +1,11 @@
1
- TASK: classification
2
- VERSION: 2
3
-
4
1
  SYSTEM:
5
2
  paths:
6
- log_dir: ./logs
7
3
  save_dir: ./checkpoints_video
8
- device: cuda
9
- gpu:
10
- count: 4
11
- id: 0
12
- reproducibility:
13
- use_seed: true
14
- seed: 42
15
4
 
16
5
  DATA:
17
6
  common:
18
7
  dataset_name: sngar
19
8
  data_root: /home/vorajv/dataset/sngar-frames
20
- runtime:
21
- loader_backend: opencv
22
9
  splits:
23
10
  train:
24
11
  annotation_path: ${DATA.common.data_root}/train.json
@@ -69,20 +56,8 @@ DATA:
69
56
  training_matches: 45
70
57
 
71
58
  MODEL:
72
- runtime:
73
- dtype: fp32
74
- device: auto
75
- compile: false
76
- freeze: false
77
- load:
78
- checkpoint_path: null
79
- pretrained: false
80
- strict: true
81
- map_location: null
82
- format: auto
83
59
  components:
84
60
  video_encoder:
85
- kind: encoder
86
61
  source:
87
62
  provider: opensportslib
88
63
  registry: backbone
@@ -97,7 +72,6 @@ MODEL:
97
72
  overrides:
98
73
  unfreeze_last_n_layers: 0
99
74
  video_adapter:
100
- kind: adapter
101
75
  source:
102
76
  provider: opensportslib
103
77
  registry: neck
@@ -108,9 +82,7 @@ MODEL:
108
82
  dropout: 0.1
109
83
  lstm_dropout: 0.3
110
84
  num_attention_heads: 8
111
- overrides: {}
112
85
  task_head:
113
- kind: head
114
86
  source:
115
87
  provider: opensportslib
116
88
  registry: head
@@ -119,26 +91,9 @@ MODEL:
119
91
  hidden_dim: 64
120
92
  num_classes: 10
121
93
  dropout: 0.1
122
- overrides: {}
123
- topology:
124
- - from: video_encoder
125
- to: video_adapter
126
- - from: video_adapter
127
- to: task_head
128
-
129
- IO:
130
- inputs:
131
- pixel_values: video_encoder
132
- label: task_head
133
- outputs:
134
- logits: task_head
135
94
 
136
95
  TRAIN:
137
- trainer:
138
- type: classification
139
96
  epochs: 100
140
- criterion:
141
- type: CrossEntropyLoss
142
97
  optimizer:
143
98
  type: AdamW
144
99
  lr: 0.00005
@@ -153,19 +108,14 @@ TRAIN:
153
108
  factor: 0.1
154
109
  min_lr: 1e-8
155
110
  execution:
156
- enabled: true
157
111
  use_amp: true
158
112
  mixup_alpha: 0.2
159
- log_interval: 10
160
113
  detailed_results: true
161
114
  sampling:
162
115
  use_weighted_sampler: true
163
- use_weighted_loss: false
164
116
  samples_per_class: 4000
165
117
  selection:
166
118
  monitor: loss
167
- mode: min
168
119
  patience: 5
169
120
  checkpoint:
170
121
  save_every: 5
171
- save_best: true