opensportslib 0.2.0.dev1__tar.gz → 0.2.0.dev3__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 (157) hide show
  1. {opensportslib-0.2.0.dev1/opensportslib.egg-info → opensportslib-0.2.0.dev3}/PKG-INFO +58 -2
  2. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/README.md +57 -1
  3. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/apis/__init__.py +2 -0
  4. opensportslib-0.2.0.dev3/opensportslib/apis/vqa.py +313 -0
  5. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/cli.py +4 -0
  6. opensportslib-0.2.0.dev3/opensportslib/configs/vqa/qwen.yaml +49 -0
  7. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/accessors.py +234 -0
  8. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/loader.py +1 -1
  9. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/validate.py +15 -0
  10. opensportslib-0.2.0.dev3/opensportslib/core/trainer/vqa_trainer.py +797 -0
  11. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/config.py +6 -2
  12. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/ddp.py +3 -2
  13. opensportslib-0.2.0.dev3/opensportslib/core/utils/hf_runtime.py +524 -0
  14. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/datasets/builder.py +4 -0
  15. opensportslib-0.2.0.dev3/opensportslib/datasets/vqa_dataset.py +165 -0
  16. opensportslib-0.2.0.dev3/opensportslib/metrics/vqa_metric.py +217 -0
  17. opensportslib-0.2.0.dev3/opensportslib/models/base/qwen_xvars.py +476 -0
  18. opensportslib-0.2.0.dev3/opensportslib/models/base/video_chatgpt_compat.py +298 -0
  19. opensportslib-0.2.0.dev3/opensportslib/models/base/xvars_videochatgpt.py +1115 -0
  20. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/builder.py +34 -0
  21. opensportslib-0.2.0.dev3/opensportslib/models/utils/vqa_prediction_priors.py +105 -0
  22. opensportslib-0.2.0.dev3/opensportslib/models/utils/vqa_prompting.py +56 -0
  23. opensportslib-0.2.0.dev3/opensportslib/models/utils/xvars_clip_index.py +99 -0
  24. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/setup/setup.py +36 -2
  25. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3/opensportslib.egg-info}/PKG-INFO +58 -2
  26. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib.egg-info/SOURCES.txt +18 -0
  27. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/pyproject.toml +1 -1
  28. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/conftest.py +140 -1
  29. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_config_architecture.py +46 -11
  30. opensportslib-0.2.0.dev3/tests/test_config_utils_smoke.py +84 -0
  31. opensportslib-0.2.0.dev3/tests/test_extract_xvars_features.py +134 -0
  32. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_public_apis_smoke.py +15 -0
  33. opensportslib-0.2.0.dev3/tests/test_task_model_api_contract.py +919 -0
  34. opensportslib-0.2.0.dev3/tests/test_vqa_metrics_semantic.py +122 -0
  35. opensportslib-0.2.0.dev3/tests/test_vqa_training_lora.py +563 -0
  36. opensportslib-0.2.0.dev3/tests/test_vqa_xvars_videochatgpt.py +1387 -0
  37. opensportslib-0.2.0.dev3/tools/convert/build_xvars_indexes.py +200 -0
  38. opensportslib-0.2.0.dev3/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.dev3}/LICENSE +0 -0
  42. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/LICENSE-COMMERCIAL +0 -0
  43. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/MANIFEST.in +0 -0
  44. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/examples/quickstart/basic_classification.py +0 -0
  45. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/examples/quickstart/basic_localization.py +0 -0
  46. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/__init__.py +0 -0
  47. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/apis/base_task_model.py +0 -0
  48. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/apis/classification.py +0 -0
  49. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/apis/localization.py +0 -0
  50. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/classification/default.yaml +0 -0
  51. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
  52. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
  53. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/classification/video.yaml +0 -0
  54. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/default.yaml +0 -0
  55. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
  56. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/localization/default.yaml +0 -0
  57. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
  58. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/localization/video_dali.yaml +0 -0
  59. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/configs/localization/video_ocv.yaml +0 -0
  60. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/__init__.py +0 -0
  61. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/__init__.py +0 -0
  62. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/conflicts.py +0 -0
  63. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/migrate.py +0 -0
  64. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/migrations/__init__.py +0 -0
  65. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  66. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/runtime_adapter.py +0 -0
  67. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/schema.py +0 -0
  68. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/schemas/__init__.py +0 -0
  69. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  70. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  71. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/loss/__init__.py +0 -0
  72. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/loss/builder.py +0 -0
  73. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/loss/calf.py +0 -0
  74. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/loss/ce.py +0 -0
  75. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/loss/combine.py +0 -0
  76. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/loss/nll.py +0 -0
  77. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/optimizer/__init__.py +0 -0
  78. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/optimizer/builder.py +0 -0
  79. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  80. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/scheduler/__init__.py +0 -0
  81. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/scheduler/builder.py +0 -0
  82. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/trainer/__init__.py +0 -0
  83. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/trainer/classification_trainer.py +0 -0
  84. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/trainer/localization_trainer.py +0 -0
  85. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/checkpoint.py +0 -0
  86. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/config_normalize.py +0 -0
  87. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/data.py +0 -0
  88. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/default_args.py +0 -0
  89. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/lightning.py +0 -0
  90. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/load_annotations.py +0 -0
  91. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/seed.py +0 -0
  92. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/video_processing.py +0 -0
  93. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/core/utils/wandb.py +0 -0
  94. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/datasets/__init__.py +0 -0
  95. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/datasets/classification_dataset.py +0 -0
  96. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/datasets/localization_dataset.py +0 -0
  97. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/datasets/utils/__init__.py +0 -0
  98. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/datasets/utils/tracking.py +0 -0
  99. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/legacy_config/classification.yaml +0 -0
  100. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  101. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  102. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  103. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/legacy_config/localization.yaml +0 -0
  104. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  105. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  106. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/metrics/classification_metric.py +0 -0
  107. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/metrics/localization_metric.py +0 -0
  108. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/__init__.py +0 -0
  109. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/backbones/builder.py +0 -0
  110. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/base/contextaware.py +0 -0
  111. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/base/e2e.py +0 -0
  112. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/base/learnablepooling.py +0 -0
  113. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/base/tracking.py +0 -0
  114. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/base/vars.py +0 -0
  115. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/base/video.py +0 -0
  116. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/base/video_mae.py +0 -0
  117. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/heads/builder.py +0 -0
  118. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/neck/builder.py +0 -0
  119. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/common.py +0 -0
  120. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/impl/__init__.py +0 -0
  121. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/impl/asformer.py +0 -0
  122. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/impl/calf.py +0 -0
  123. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/impl/gsm.py +0 -0
  124. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/impl/gtad.py +0 -0
  125. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/impl/tsm.py +0 -0
  126. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/litebase.py +0 -0
  127. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/modules.py +0 -0
  128. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/shift.py +0 -0
  129. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/models/utils/utils.py +0 -0
  130. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/tools/__init__.py +0 -0
  131. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/tools/_common.py +0 -0
  132. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/tools/hf_transfer.py +0 -0
  133. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/tools/osl_json_to_parquet.py +0 -0
  134. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib/tools/parquet_to_osl_json.py +0 -0
  135. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib.egg-info/dependency_links.txt +0 -0
  136. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib.egg-info/entry_points.txt +0 -0
  137. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib.egg-info/requires.txt +0 -0
  138. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/opensportslib.egg-info/top_level.txt +0 -0
  139. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/setup.cfg +0 -0
  140. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_classification_dataset_paths.py +0 -0
  141. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_classification_trainer_dataloader.py +0 -0
  142. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_config_split_override_sync.py +0 -0
  143. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_conversion_tools.py +0 -0
  144. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_hf_transfer_tools.py +0 -0
  145. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_localization_dali_filenames.py +0 -0
  146. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_package_smoke.py +0 -0
  147. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_pretrained_config_merge_policy.py +0 -0
  148. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tests/test_subset_train_infer_integration.py +0 -0
  149. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tools/convert/build_soccernet_gar.py +0 -0
  150. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  151. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  152. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  153. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tools/download/download_hf_repo.py +0 -0
  154. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tools/download/download_osl_hf.py +0 -0
  155. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tools/download/upload_osl_hf.py +0 -0
  156. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/tools/training/classification.py +0 -0
  157. {opensportslib-0.2.0.dev1 → opensportslib-0.2.0.dev3}/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.dev3
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
 
@@ -92,13 +93,26 @@ opensportslib setup --pyg
92
93
 
93
94
  # Optional: install for DALI support
94
95
  opensportslib setup --dali
95
- ```
96
+
97
+ # Optional: install the X-VARS-compatible VQA dependency profile
98
+ opensportslib setup --vqa_xvars
99
+
100
+ # Optional: install the Qwen-compatible VQA dependency profile
101
+ opensportslib setup --vqa_qwen
102
+ ```
96
103
  ---
97
104
 
98
105
  **Note:**
99
106
  Run `opensportslib setup` to automatically configure dependencies.
100
107
  If issues occur, manually install compatible versions of `torch`, `torchvision`, and related libraries according to your CUDA version or system compatibility.
101
108
 
109
+ For VQA, use exactly one backend-specific dependency profile:
110
+
111
+ - `--vqa_xvars` installs the X-VARS-compatible Hugging Face stack from `XVARS_DEPENDENCY_PINS`
112
+ - `--vqa_qwen` installs the Qwen-compatible Hugging Face stack from `QWEN_DEPENDENCY_PINS`
113
+
114
+ The `vqa_qwen` config supports `Qwen/Qwen2.5-7B-Instruct` and `Qwen/Qwen3.5-9B-Base`.
115
+
102
116
  ---
103
117
 
104
118
  ## Data and pretrained models
@@ -280,6 +294,39 @@ metrics_from_file = my_model.evaluate(
280
294
  )
281
295
  ```
282
296
 
297
+ ### VQA example
298
+
299
+ ```python
300
+ from opensportslib.apis import VQAModel
301
+
302
+ my_model = VQAModel(
303
+ config="opensportslib/configs/vqa/qwen.yaml",
304
+ weights=None, # optional: path or Hugging Face model ID
305
+ )
306
+
307
+ predictions = my_model.infer(
308
+ test_set="/path/to/test_annotations.json",
309
+ )
310
+
311
+ # Headless single-video VQA uses the same prediction payload shape.
312
+ single_prediction = my_model.infer(
313
+ video_path="/path/to/video.mp4",
314
+ question="What card would you give? Why?",
315
+ )
316
+ ```
317
+
318
+ Use `opensportslib/configs/vqa/xvars.yaml` with `opensportslib setup --vqa_xvars`
319
+ for the X-VARS-compatible backend, or `opensportslib/configs/vqa/qwen.yaml` with
320
+ `opensportslib setup --vqa_qwen` for the Qwen-compatible backend. The Qwen
321
+ backend currently supports `Qwen/Qwen2.5-7B-Instruct` and
322
+ `Qwen/Qwen3.5-9B-Base`.
323
+
324
+ For X-VARS, `feature_source: indexed_or_raw_clip` prefers indexed CLIP features
325
+ when available and falls back to extracting CLIP features from raw video during
326
+ `infer()`. Pre-extracted features remain the preferred path for parity, speed,
327
+ and reproducibility. See [docs/xvars_integration_phases.md](docs/xvars_integration_phases.md)
328
+ for the full X-VARS setup workflow.
329
+
283
330
 
284
331
  ---
285
332
 
@@ -316,6 +363,9 @@ Classify clips or event centered samples into predefined categories.
316
363
  ### Action Localization / Spotting
317
364
  Predict when key events happen in long untrimmed sports videos.
318
365
 
366
+ ### Visual Question Answering (VQA)
367
+ Answer natural-language questions about sports video clips.
368
+
319
369
  ### Action Retrieval
320
370
  Search and retrieve relevant clips or moments from a collection of sports videos.
321
371
  This is part of the roadmap and OSL data model, not a first-class OpenSportsLib
@@ -384,6 +434,12 @@ opensportslib setup --pyg
384
434
 
385
435
  # Optional: install for DALI support
386
436
  opensportslib setup --dali
437
+
438
+ # Optional: install the X-VARS-compatible VQA dependency profile
439
+ opensportslib setup --vqa_xvars
440
+
441
+ # Optional: install the Qwen-compatible VQA dependency profile
442
+ opensportslib setup --vqa_qwen
387
443
  ```
388
444
 
389
445
  ### Git workflow
@@ -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
 
@@ -57,13 +58,26 @@ opensportslib setup --pyg
57
58
 
58
59
  # Optional: install for DALI support
59
60
  opensportslib setup --dali
60
- ```
61
+
62
+ # Optional: install the X-VARS-compatible VQA dependency profile
63
+ opensportslib setup --vqa_xvars
64
+
65
+ # Optional: install the Qwen-compatible VQA dependency profile
66
+ opensportslib setup --vqa_qwen
67
+ ```
61
68
  ---
62
69
 
63
70
  **Note:**
64
71
  Run `opensportslib setup` to automatically configure dependencies.
65
72
  If issues occur, manually install compatible versions of `torch`, `torchvision`, and related libraries according to your CUDA version or system compatibility.
66
73
 
74
+ For VQA, use exactly one backend-specific dependency profile:
75
+
76
+ - `--vqa_xvars` installs the X-VARS-compatible Hugging Face stack from `XVARS_DEPENDENCY_PINS`
77
+ - `--vqa_qwen` installs the Qwen-compatible Hugging Face stack from `QWEN_DEPENDENCY_PINS`
78
+
79
+ The `vqa_qwen` config supports `Qwen/Qwen2.5-7B-Instruct` and `Qwen/Qwen3.5-9B-Base`.
80
+
67
81
  ---
68
82
 
69
83
  ## Data and pretrained models
@@ -245,6 +259,39 @@ metrics_from_file = my_model.evaluate(
245
259
  )
246
260
  ```
247
261
 
262
+ ### VQA example
263
+
264
+ ```python
265
+ from opensportslib.apis import VQAModel
266
+
267
+ my_model = VQAModel(
268
+ config="opensportslib/configs/vqa/qwen.yaml",
269
+ weights=None, # optional: path or Hugging Face model ID
270
+ )
271
+
272
+ predictions = my_model.infer(
273
+ test_set="/path/to/test_annotations.json",
274
+ )
275
+
276
+ # Headless single-video VQA uses the same prediction payload shape.
277
+ single_prediction = my_model.infer(
278
+ video_path="/path/to/video.mp4",
279
+ question="What card would you give? Why?",
280
+ )
281
+ ```
282
+
283
+ Use `opensportslib/configs/vqa/xvars.yaml` with `opensportslib setup --vqa_xvars`
284
+ for the X-VARS-compatible backend, or `opensportslib/configs/vqa/qwen.yaml` with
285
+ `opensportslib setup --vqa_qwen` for the Qwen-compatible backend. The Qwen
286
+ backend currently supports `Qwen/Qwen2.5-7B-Instruct` and
287
+ `Qwen/Qwen3.5-9B-Base`.
288
+
289
+ For X-VARS, `feature_source: indexed_or_raw_clip` prefers indexed CLIP features
290
+ when available and falls back to extracting CLIP features from raw video during
291
+ `infer()`. Pre-extracted features remain the preferred path for parity, speed,
292
+ and reproducibility. See [docs/xvars_integration_phases.md](docs/xvars_integration_phases.md)
293
+ for the full X-VARS setup workflow.
294
+
248
295
 
249
296
  ---
250
297
 
@@ -281,6 +328,9 @@ Classify clips or event centered samples into predefined categories.
281
328
  ### Action Localization / Spotting
282
329
  Predict when key events happen in long untrimmed sports videos.
283
330
 
331
+ ### Visual Question Answering (VQA)
332
+ Answer natural-language questions about sports video clips.
333
+
284
334
  ### Action Retrieval
285
335
  Search and retrieve relevant clips or moments from a collection of sports videos.
286
336
  This is part of the roadmap and OSL data model, not a first-class OpenSportsLib
@@ -349,6 +399,12 @@ opensportslib setup --pyg
349
399
 
350
400
  # Optional: install for DALI support
351
401
  opensportslib setup --dali
402
+
403
+ # Optional: install the X-VARS-compatible VQA dependency profile
404
+ opensportslib setup --vqa_xvars
405
+
406
+ # Optional: install the Qwen-compatible VQA dependency profile
407
+ opensportslib setup --vqa_qwen
352
408
  ```
353
409
 
354
410
  ### Git workflow
@@ -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("--vqa_xvars", action="store_true")
15
+ parser.add_argument("--vqa_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
+ vqa_xvars=args.vqa_xvars,
24
+ vqa_qwen=args.vqa_qwen
21
25
  )
22
26
  return 0
23
27
 
@@ -0,0 +1,49 @@
1
+ SYSTEM:
2
+ paths:
3
+ save_dir: ./checkpoints_vqa_qwen
4
+
5
+ MODEL:
6
+ components:
7
+ video_encoder:
8
+ kind: encoder
9
+ source:
10
+ provider: opensportslib
11
+ name: xvars_clip_features
12
+ load:
13
+ weights_path: /home/vorajv/X-VARS/weights/14_model.pth.tar
14
+ params:
15
+ feature_source: indexed_or_raw_clip
16
+ vision_tower: openai/clip-vit-large-patch14
17
+ feature_dim: 1024
18
+ overrides: {}
19
+ mm_projector:
20
+ kind: projector
21
+ source:
22
+ provider: opensportslib
23
+ params:
24
+ input_dim: 1024
25
+ overrides: {}
26
+ llm_decoder:
27
+ kind: decoder
28
+ source:
29
+ provider: huggingface
30
+ # Supported models: Qwen/Qwen2.5-7B-Instruct or Qwen/Qwen3.5-9B-Base
31
+ name: Qwen/Qwen3.5-9B-Base
32
+ params:
33
+ # Supported models: Qwen/Qwen2.5-7B-Instruct or Qwen/Qwen3.5-9B-Base
34
+ repo_id: Qwen/Qwen3.5-9B-Base
35
+ overrides: {}
36
+ metadata:
37
+ backend: qwen_xvars_infer
38
+
39
+ TRAIN:
40
+ execution:
41
+ prompt:
42
+ system_prompt: You are a football video assistant. Answer the VQA question using the provided video context and referee priors.
43
+
44
+ hf:
45
+ tokenizer_id: ${MODEL.components.llm_decoder.params.repo_id}
46
+ prefer_cuda: true
47
+ local_files_only: false
48
+ device_map: auto
49
+ offload_folder: ./hf_offload_qwen