opensportslib 0.3.1.dev18__tar.gz → 0.3.1.dev20__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 (237) hide show
  1. {opensportslib-0.3.1.dev18/opensportslib.egg-info → opensportslib-0.3.1.dev20}/PKG-INFO +44 -1
  2. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/README.md +42 -0
  3. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/apis/classification.py +13 -0
  4. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/apis/localization.py +10 -0
  5. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/accessors.py +14 -0
  6. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/load_annotations.py +9 -2
  7. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/video_processing.py +7 -0
  8. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/datasets/classification_dataset.py +20 -0
  9. opensportslib-0.3.1.dev20/opensportslib/datasets/hf_json.py +169 -0
  10. opensportslib-0.3.1.dev20/opensportslib/datasets/hf_tracking.py +297 -0
  11. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/datasets/localization_dataset.py +9 -15
  12. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20/opensportslib.egg-info}/PKG-INFO +44 -1
  13. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib.egg-info/SOURCES.txt +6 -0
  14. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib.egg-info/requires.txt +1 -0
  15. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/pyproject.toml +2 -2
  16. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/conftest.py +2 -0
  17. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/contracts/test_dependencies.py +2 -1
  18. opensportslib-0.3.1.dev20/tests/unit/data/test_hf_json_spotting.py +233 -0
  19. opensportslib-0.3.1.dev20/tests/unit/data/test_hf_json_tracking_spotting.py +105 -0
  20. opensportslib-0.3.1.dev20/tests/unit/data/test_hf_tracking_dataset.py +246 -0
  21. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/data/test_localization_intervals.py +66 -0
  22. opensportslib-0.3.1.dev20/tools/train/train_config.py +32 -0
  23. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/LICENSE +0 -0
  24. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/LICENSE-COMMERCIAL +0 -0
  25. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/MANIFEST.in +0 -0
  26. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/examples/quickstart/basic_classification.py +0 -0
  27. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/examples/quickstart/basic_localization.py +0 -0
  28. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/examples/quickstart/basic_vqa.py +0 -0
  29. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/__init__.py +0 -0
  30. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/adaptation/__init__.py +0 -0
  31. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/adaptation/spotta.py +0 -0
  32. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/apis/__init__.py +0 -0
  33. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/apis/base_task_model.py +0 -0
  34. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/apis/config.py +0 -0
  35. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/apis/configuration.py +0 -0
  36. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/apis/vqa.py +0 -0
  37. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/cli.py +0 -0
  38. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/classification/default.yaml +0 -0
  39. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
  40. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
  41. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/classification/video.yaml +0 -0
  42. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/default.yaml +0 -0
  43. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
  44. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/localization/default.yaml +0 -0
  45. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/localization/e2e_spotta.yaml +0 -0
  46. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/localization/h5_header_distance.yaml +0 -0
  47. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/localization/h5_header_skeleton.yaml +0 -0
  48. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
  49. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/localization/tracking_action_spotting.yaml +0 -0
  50. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/localization/video_dali.yaml +0 -0
  51. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/localization/video_ocv.yaml +0 -0
  52. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/vqa/default.yaml +0 -0
  53. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/vqa/qwen.yaml +0 -0
  54. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/vqa/qwen3_vl_native.yaml +0 -0
  55. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/vqa/qwen_lora.yaml +0 -0
  56. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/vqa/qwen_sngar_frames.yaml +0 -0
  57. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/configs/vqa/xvars.yaml +0 -0
  58. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/__init__.py +0 -0
  59. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/__init__.py +0 -0
  60. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/conflicts.py +0 -0
  61. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/editable.py +0 -0
  62. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/loader.py +0 -0
  63. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/migrate.py +0 -0
  64. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/migrations/__init__.py +0 -0
  65. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  66. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/rule_variants.py +0 -0
  67. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/runtime_adapter.py +0 -0
  68. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/schema.py +0 -0
  69. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/schemas/__init__.py +0 -0
  70. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  71. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  72. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/config/validate.py +0 -0
  73. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/loss/__init__.py +0 -0
  74. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/loss/builder.py +0 -0
  75. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/loss/calf.py +0 -0
  76. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/loss/ce.py +0 -0
  77. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/loss/combine.py +0 -0
  78. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/loss/nll.py +0 -0
  79. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/optimizer/__init__.py +0 -0
  80. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/optimizer/builder.py +0 -0
  81. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  82. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/scheduler/__init__.py +0 -0
  83. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/scheduler/builder.py +0 -0
  84. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/trainer/__init__.py +0 -0
  85. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/trainer/classification_trainer.py +0 -0
  86. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/trainer/localization_trainer.py +0 -0
  87. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/trainer/vqa_trainer.py +0 -0
  88. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/checkpoint.py +0 -0
  89. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/config.py +0 -0
  90. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/config_normalize.py +0 -0
  91. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/data.py +0 -0
  92. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/ddp.py +0 -0
  93. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/default_args.py +0 -0
  94. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/direct_video.py +0 -0
  95. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/hf_runtime.py +0 -0
  96. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/lightning.py +0 -0
  97. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/seed.py +0 -0
  98. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/core/utils/wandb.py +0 -0
  99. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/datasets/__init__.py +0 -0
  100. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/datasets/builder.py +0 -0
  101. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/datasets/utils/__init__.py +0 -0
  102. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/datasets/utils/h5_tracking.py +0 -0
  103. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/datasets/utils/tracking.py +0 -0
  104. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/datasets/vqa_dataset.py +0 -0
  105. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/legacy_config/classification.yaml +0 -0
  106. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  107. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  108. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  109. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/legacy_config/localization.yaml +0 -0
  110. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  111. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  112. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/metrics/classification_metric.py +0 -0
  113. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/metrics/localization_metric.py +0 -0
  114. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/metrics/vqa_metric.py +0 -0
  115. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/__init__.py +0 -0
  116. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/backbones/builder.py +0 -0
  117. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/contextaware.py +0 -0
  118. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/e2e.py +0 -0
  119. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/learnablepooling.py +0 -0
  120. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/qwen_vl_native.py +0 -0
  121. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/qwen_xvars.py +0 -0
  122. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/rule_based.py +0 -0
  123. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/tracking.py +0 -0
  124. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/vars.py +0 -0
  125. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/video.py +0 -0
  126. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/video_chatgpt_compat.py +0 -0
  127. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/video_mae.py +0 -0
  128. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/base/xvars_videochatgpt.py +0 -0
  129. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/builder.py +0 -0
  130. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/heads/builder.py +0 -0
  131. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/neck/builder.py +0 -0
  132. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/common.py +0 -0
  133. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/impl/__init__.py +0 -0
  134. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/impl/asformer.py +0 -0
  135. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/impl/calf.py +0 -0
  136. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/impl/gsm.py +0 -0
  137. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/impl/gtad.py +0 -0
  138. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/impl/tsm.py +0 -0
  139. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/litebase.py +0 -0
  140. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/modules.py +0 -0
  141. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/shift.py +0 -0
  142. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/utils.py +0 -0
  143. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/vqa_prediction_priors.py +0 -0
  144. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/vqa_prompting.py +0 -0
  145. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/models/utils/xvars_clip_index.py +0 -0
  146. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/remote_registry.py +0 -0
  147. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/setup/setup.py +0 -0
  148. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/tools/__init__.py +0 -0
  149. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/tools/_common.py +0 -0
  150. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/tools/hf_transfer.py +0 -0
  151. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/tools/osl_json_to_parquet.py +0 -0
  152. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib/tools/parquet_to_osl_json.py +0 -0
  153. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib.egg-info/dependency_links.txt +0 -0
  154. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib.egg-info/entry_points.txt +0 -0
  155. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/opensportslib.egg-info/top_level.txt +0 -0
  156. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/scripts/redact_test_stream.py +0 -0
  157. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/scripts/release_versions.py +0 -0
  158. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/scripts/run_h5_header_rule_inference.py +0 -0
  159. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/scripts/summarize_test_report.py +0 -0
  160. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/setup.cfg +0 -0
  161. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/__init__.py +0 -0
  162. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/helpers/__init__.py +0 -0
  163. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/helpers/configs.py +0 -0
  164. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/helpers/pipeline_stubs.py +0 -0
  165. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/integration/classification/test_pipeline.py +0 -0
  166. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/integration/localization/test_pipeline.py +0 -0
  167. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/integration/localization/test_spotta.py +0 -0
  168. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/integration/vqa/test_pipeline.py +0 -0
  169. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/release/__init__.py +0 -0
  170. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/release/_release_common.py +0 -0
  171. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/release/test_00_environment.py +0 -0
  172. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/release/test_classification_release.py +0 -0
  173. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/release/test_localization_release.py +0 -0
  174. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/release/test_vqa_release.py +0 -0
  175. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/smoke/test_cli.py +0 -0
  176. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/smoke/test_config_utils.py +0 -0
  177. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/smoke/test_environment.py +0 -0
  178. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/smoke/test_package.py +0 -0
  179. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/smoke/test_public_apis.py +0 -0
  180. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/api/test_remote_registry.py +0 -0
  181. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/api/test_remote_sessions.py +0 -0
  182. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/api/test_server_jobs.py +0 -0
  183. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/api/test_server_registry.py +0 -0
  184. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/api/test_server_startup_scripts.py +0 -0
  185. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/api/test_task_model_contract.py +0 -0
  186. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/api/test_vqa.py +0 -0
  187. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/config/test_architecture.py +0 -0
  188. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/config/test_editable.py +0 -0
  189. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/config/test_optional_hf.py +0 -0
  190. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/config/test_pretrained_merge.py +0 -0
  191. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/config/test_split_overrides.py +0 -0
  192. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/contracts/test_architecture.py +0 -0
  193. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/contracts/test_package.py +0 -0
  194. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/contracts/test_release_versions.py +0 -0
  195. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/contracts/test_runner_contract.py +0 -0
  196. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/core/test_classification_trainer.py +0 -0
  197. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/core/test_localization_hf_backend.py +0 -0
  198. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/core/test_training_components.py +0 -0
  199. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/core/test_video_processing.py +0 -0
  200. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/data/test_classification_paths.py +0 -0
  201. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/data/test_h5_tracking.py +0 -0
  202. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/data/test_localization_dali.py +0 -0
  203. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/data/test_osl_formats.py +0 -0
  204. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/models/test_classification_metrics.py +0 -0
  205. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/models/test_header_rule_spotter.py +0 -0
  206. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/models/test_header_skeleton_spotter.py +0 -0
  207. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/models/test_localization_metrics.py +0 -0
  208. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/models/test_temporal_shift.py +0 -0
  209. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/models/test_vqa_metrics.py +0 -0
  210. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/models/test_vqa_qwen_xvars.py +0 -0
  211. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/models/test_vqa_training_lora.py +0 -0
  212. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/models/test_vqa_xvars_videochatgpt.py +0 -0
  213. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/tools/test_conversion.py +0 -0
  214. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/tools/test_extract_xvars_features.py +0 -0
  215. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/tools/test_hf_transfer.py +0 -0
  216. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/tools/test_report_summary.py +0 -0
  217. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tests/unit/tools/test_setup.py +0 -0
  218. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/build_sn_vqa_2026_vqa.py +0 -0
  219. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/build_sngar_spotting.py +0 -0
  220. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/build_soccernet_gar.py +0 -0
  221. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  222. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/build_soccernet_gar_vqa.py +0 -0
  223. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/build_xvars_indexes.py +0 -0
  224. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/extract_xvars_clip_features.py +0 -0
  225. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  226. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  227. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/sngar_dataset_card.py +0 -0
  228. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/sngar_events.py +0 -0
  229. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/convert/verify_sngar_spotting.py +0 -0
  230. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/download/download_hf_repo.py +0 -0
  231. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/download/download_osl_hf.py +0 -0
  232. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/download/push_sngar_spotting.py +0 -0
  233. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/download/upload_osl_hf.py +0 -0
  234. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/training/classification.py +0 -0
  235. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/training/localization.py +0 -0
  236. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/training/vqa.py +0 -0
  237. {opensportslib-0.3.1.dev18 → opensportslib-0.3.1.dev20}/tools/upload/upload_model_hf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.3.1.dev18
3
+ Version: 0.3.1.dev20
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
@@ -28,6 +28,7 @@ Requires-Dist: pandas
28
28
  Requires-Dist: pyarrow
29
29
  Requires-Dist: h5py==3.16.0
30
30
  Requires-Dist: huggingface_hub
31
+ Requires-Dist: datasets<6,>=5.0.0
31
32
  Requires-Dist: easydict
32
33
  Provides-Extra: test
33
34
  Requires-Dist: pytest; extra == "test"
@@ -437,6 +438,44 @@ VQA setup workflow.
437
438
 
438
439
  OpenSportsLib provides APIs and scripts for downloading and uploading OSL datasets with Hugging Face.
439
440
 
441
+ For SN-GAR tracking classification, `sngar_tracking_hf.yaml` loads split metadata
442
+ through `datasets` and caches every referenced TAR shard before using each split.
443
+ It preserves the weighted replacement sampler without extracting individual clips. Install
444
+ with `python -m pip install -e .`, authenticate with `hf auth login`,
445
+ and see [SN-GAR-README.md](SN-GAR-README.md) for the training command and cache layout.
446
+
447
+ For SN-GAR video action spotting, `sngar_spotting_video_hf.yaml` uses
448
+ `DATA.inputs.video.source.format: hf_json` to stage OSL JSON manifests and MP4s
449
+ from the `multimodal` branch of `OpenSportsLab/SNGAR-Action-Spotting`. It reads
450
+ the JSON manifests and downloads only their selected video inputs, leaving the
451
+ tracking Parquet files alone. Run
452
+ `LocalizationModel(config="sngar_spotting_video_hf.yaml").train(use_wandb=False)`
453
+ after `hf auth login` and dataset access approval. The first train/validation
454
+ stage downloads all videos in those splits; subsequent runs reuse the cache.
455
+ If a cached video is missing, staging downloads it again before training starts.
456
+ The example keeps the 300-frame window but uses one clip per step, limited
457
+ DataLoader prefetch, and four gradient accumulation steps to control memory.
458
+ For the OpenCV loader, accumulation combines successive batches, so batch size
459
+ one with four accumulation steps is valid.
460
+ The 29.97-fps videos are sampled every sixth source frame for approximately
461
+ 5 fps and a 60-second window. Restart training after a sampling change so
462
+ decoded clips and event labels use the same frame rate.
463
+
464
+ For tracking action spotting, use
465
+ `LocalizationModel(config="sngar_spotting_tracking_hf.yaml").train(use_wandb=False)`.
466
+ This config selects `tracking_parquet` from the same `multimodal` JSON manifests,
467
+ downloads only the whole-game tracking tables in the requested splits,
468
+ and passes them to the tracking graph dataset. The staged manifest keeps the
469
+ `tracking_parquet` input type. Tracking files are cached under
470
+ `/home/giancos/OSLdata/sngar/hf_json_tracking_cache`; video MP4s are skipped.
471
+
472
+ To run the five example baselines sequentially with their full epoch settings,
473
+ see [tools/train/weekend_train.sh](tools/train/weekend_train.sh). Run it with
474
+ `bash tools/train/weekend_train.sh` after `hf auth login`. Each algorithm and
475
+ dataset has its own section in the script. Video spotting alone was measured at
476
+ about 10 days for 100 epochs on one user's machine, so the complete sequence
477
+ will extend beyond a weekend on similar hardware.
478
+
440
479
  ### Python API
441
480
 
442
481
  ```python
@@ -583,6 +622,10 @@ opensportslib setup --vqa_qwen
583
622
 
584
623
  We welcome contributions to OpenSportsLib.
585
624
 
625
+ All PRs must target `dev`. Before a PR can merge, every GitHub-linked commit
626
+ author must accept the [Individual Contributor License Agreement](.github/CLA.md)
627
+ when prompted by the `CLA check`.
628
+
586
629
  Please check:
587
630
 
588
631
  - [CONTRIBUTING.md](CONTRIBUTING.md)
@@ -399,6 +399,44 @@ VQA setup workflow.
399
399
 
400
400
  OpenSportsLib provides APIs and scripts for downloading and uploading OSL datasets with Hugging Face.
401
401
 
402
+ For SN-GAR tracking classification, `sngar_tracking_hf.yaml` loads split metadata
403
+ through `datasets` and caches every referenced TAR shard before using each split.
404
+ It preserves the weighted replacement sampler without extracting individual clips. Install
405
+ with `python -m pip install -e .`, authenticate with `hf auth login`,
406
+ and see [SN-GAR-README.md](SN-GAR-README.md) for the training command and cache layout.
407
+
408
+ For SN-GAR video action spotting, `sngar_spotting_video_hf.yaml` uses
409
+ `DATA.inputs.video.source.format: hf_json` to stage OSL JSON manifests and MP4s
410
+ from the `multimodal` branch of `OpenSportsLab/SNGAR-Action-Spotting`. It reads
411
+ the JSON manifests and downloads only their selected video inputs, leaving the
412
+ tracking Parquet files alone. Run
413
+ `LocalizationModel(config="sngar_spotting_video_hf.yaml").train(use_wandb=False)`
414
+ after `hf auth login` and dataset access approval. The first train/validation
415
+ stage downloads all videos in those splits; subsequent runs reuse the cache.
416
+ If a cached video is missing, staging downloads it again before training starts.
417
+ The example keeps the 300-frame window but uses one clip per step, limited
418
+ DataLoader prefetch, and four gradient accumulation steps to control memory.
419
+ For the OpenCV loader, accumulation combines successive batches, so batch size
420
+ one with four accumulation steps is valid.
421
+ The 29.97-fps videos are sampled every sixth source frame for approximately
422
+ 5 fps and a 60-second window. Restart training after a sampling change so
423
+ decoded clips and event labels use the same frame rate.
424
+
425
+ For tracking action spotting, use
426
+ `LocalizationModel(config="sngar_spotting_tracking_hf.yaml").train(use_wandb=False)`.
427
+ This config selects `tracking_parquet` from the same `multimodal` JSON manifests,
428
+ downloads only the whole-game tracking tables in the requested splits,
429
+ and passes them to the tracking graph dataset. The staged manifest keeps the
430
+ `tracking_parquet` input type. Tracking files are cached under
431
+ `/home/giancos/OSLdata/sngar/hf_json_tracking_cache`; video MP4s are skipped.
432
+
433
+ To run the five example baselines sequentially with their full epoch settings,
434
+ see [tools/train/weekend_train.sh](tools/train/weekend_train.sh). Run it with
435
+ `bash tools/train/weekend_train.sh` after `hf auth login`. Each algorithm and
436
+ dataset has its own section in the script. Video spotting alone was measured at
437
+ about 10 days for 100 epochs on one user's machine, so the complete sequence
438
+ will extend beyond a weekend on similar hardware.
439
+
402
440
  ### Python API
403
441
 
404
442
  ```python
@@ -545,6 +583,10 @@ opensportslib setup --vqa_qwen
545
583
 
546
584
  We welcome contributions to OpenSportsLib.
547
585
 
586
+ All PRs must target `dev`. Before a PR can merge, every GitHub-linked commit
587
+ author must accept the [Individual Contributor License Agreement](.github/CLA.md)
588
+ when prompted by the `CLA check`.
589
+
548
590
  Please check:
549
591
 
550
592
  - [CONTRIBUTING.md](CONTRIBUTING.md)
@@ -33,6 +33,19 @@ class ClassificationModel(BaseTaskModel):
33
33
  """Top-level task wrapper for classification."""
34
34
 
35
35
  def _resolve_split_path(self, split: str, override: str | None = None) -> str:
36
+ if not self.is_remote:
37
+ from opensportslib.datasets.hf_tracking import (
38
+ hf_tracking_source,
39
+ prepare_hf_tracking_split,
40
+ )
41
+
42
+ if hf_tracking_source(self.config):
43
+ if override is not None:
44
+ raise ValueError(
45
+ "hf_webdataset uses its configured Hub splits; "
46
+ "train_set, valid_set, and test_set overrides are unsupported."
47
+ )
48
+ return str(prepare_hf_tracking_split(self.config, split).annotations_path)
36
49
  if override is not None:
37
50
  return expand(override)
38
51
 
@@ -16,6 +16,7 @@ from opensportslib.core.config.accessors import (
16
16
  get_split_annotation_path,
17
17
  get_split_cfg,
18
18
  set_split_annotation_path,
19
+ set_split_source_path,
19
20
  set_loader_backend,
20
21
  get_model_family,
21
22
  )
@@ -49,6 +50,15 @@ class LocalizationModel(BaseTaskModel):
49
50
  if override is not None:
50
51
  return expand(override)
51
52
 
53
+ from opensportslib.datasets.hf_json import hf_json_source, prepare_hf_json_split
54
+
55
+ if hf_json_source(self.config):
56
+ prepared = prepare_hf_json_split(self.config, split)
57
+ set_split_source_path(self.config, split, str(prepared.source_path))
58
+ if split == "valid":
59
+ set_split_source_path(self.config, "valid_data_frames", str(prepared.source_path))
60
+ return str(prepared.annotation_path)
61
+
52
62
  path = get_split_annotation_path(self.config, split)
53
63
  if path:
54
64
  return expand(path)
@@ -182,6 +182,20 @@ def get_split_source_path(cfg: Any, split: str) -> str | None:
182
182
  return getattr(split_cfg, "source_path", None)
183
183
 
184
184
 
185
+ def set_split_source_path(cfg: Any, split: str, path: str) -> None:
186
+ data = getattr(cfg, "DATA", None)
187
+ if data is None:
188
+ data = SimpleNamespace()
189
+ setattr(cfg, "DATA", data)
190
+ common = _ensure_child(data, "common")
191
+ splits = _ensure_child(common, "splits")
192
+ split_cfg = _ensure_child(splits, split)
193
+ if isinstance(split_cfg, dict):
194
+ split_cfg["source_path"] = path
195
+ else:
196
+ setattr(split_cfg, "source_path", path)
197
+
198
+
185
199
  def classes_to_ordered_list(classes: Any) -> list[str]:
186
200
  """Normalise a classes spec to a list ordered by class index.
187
201
 
@@ -860,9 +860,16 @@ def check_config(cfg, split="train"):
860
860
  ]
861
861
  head_type = get_component_name_by_kind(cfg, "head")
862
862
  assert head_type in ["", "gru", "deeper_gru", "mstcn", "asformer"]
863
- # assert cfg.dataset.batch_size % cfg.training.acc_grad_iter == 0
864
863
  train_bs = cfg.DATA.common.splits.train.dataloader.batch_size
865
- assert train_bs % cfg.TRAIN.execution.acc_grad_iter == 0
864
+ acc_grad_iter = cfg.TRAIN.execution.acc_grad_iter
865
+ if acc_grad_iter < 1:
866
+ raise ValueError("TRAIN.execution.acc_grad_iter must be at least 1")
867
+ # DALI divides its configured batch into accumulation microbatches.
868
+ # OpenCV accumulates gradients across independent DataLoader batches.
869
+ if get_loader_backend(cfg) == "dali" and train_bs % acc_grad_iter != 0:
870
+ raise ValueError(
871
+ "DALI train batch_size must be divisible by TRAIN.execution.acc_grad_iter"
872
+ )
866
873
  assert cfg.TRAIN.execution.criterion_valid in ["map", "loss"]
867
874
  assert get_train_epochs(cfg) == cfg.TRAIN.scheduler.num_epochs
868
875
  assert cfg.TRAIN.execution.acc_grad_iter == cfg.TRAIN.scheduler.acc_grad_iter
@@ -99,6 +99,13 @@ def get_stride(src_fps, sample_fps):
99
99
  if sample_fps <= 0:
100
100
  stride = 1
101
101
  else:
102
+ # MP4s commonly report 30000/1001 rather than the nominal 30 fps.
103
+ # Truncating 29.97 / 5 gives 5, which samples at almost 6 fps.
104
+ nominal_fps = round(src_fps)
105
+ if nominal_fps > 0 and math.isclose(
106
+ src_fps, nominal_fps, rel_tol=0.0011
107
+ ):
108
+ src_fps = nominal_fps
102
109
  stride = int(src_fps / sample_fps)
103
110
  return stride
104
111
 
@@ -65,6 +65,10 @@ def build(config, annotations_path, processor=None, split="train"):
65
65
  modality = get_data_modality(config).lower()
66
66
 
67
67
  if modality in ("tracking", "tracking_parquet"):
68
+ from opensportslib.datasets.hf_tracking import hf_tracking_source
69
+
70
+ if hf_tracking_source(config):
71
+ return HFTrackingDataset(config, split=split)
68
72
  return TrackingDataset(config, annotations_path, split)
69
73
  elif modality in H5_TRACKING_MODALITIES:
70
74
  return H5TrackingDataset(config, annotations_path, split)
@@ -749,6 +753,22 @@ class TrackingDataset(ClassificationDataset):
749
753
  return out
750
754
 
751
755
 
756
+ class HFTrackingDataset(TrackingDataset):
757
+ """Tracking graphs backed by indexed Hugging Face WebDataset TAR shards."""
758
+
759
+ def __init__(self, config, split="train"):
760
+ from opensportslib.datasets.hf_tracking import HFTarTrackingReader, prepare_hf_tracking_split
761
+
762
+ if get_data_params(config).get("preload_data", False):
763
+ raise ValueError("hf_webdataset requires DATA.inputs.tracking.params.preload_data=false")
764
+ prepared = prepare_hf_tracking_split(config, split)
765
+ self._hf_reader = HFTarTrackingReader(prepared)
766
+ super().__init__(config, str(prepared.annotations_path), split)
767
+
768
+ def _load_tracking_clip(self, path):
769
+ return self._hf_reader.read_parquet(path)
770
+
771
+
752
772
  class H5TrackingDataset(ClassificationDataset):
753
773
  """Graph-based classification dataset for UTC-indexed player/ball H5 files."""
754
774
 
@@ -0,0 +1,169 @@
1
+ """Stage OSL JSON manifests and their selected media from a Hugging Face dataset."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import fcntl
6
+ import json
7
+ import os
8
+ import re
9
+ from concurrent.futures import ThreadPoolExecutor
10
+ from dataclasses import dataclass
11
+ from pathlib import Path, PurePosixPath
12
+
13
+ from opensportslib.core.config.accessors import get_input_cfg
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class PreparedHFJsonSplit:
18
+ annotation_path: Path
19
+ source_path: Path
20
+ revision: str
21
+
22
+
23
+ def hf_json_source(config) -> dict:
24
+ source = get_input_cfg(config).get("source") or {}
25
+ return source if str(source.get("format", "")).lower() == "hf_json" else {}
26
+
27
+
28
+ def _safe_path(value: str) -> str:
29
+ path = PurePosixPath(value)
30
+ if not value or path.is_absolute() or ".." in path.parts or "\\" in value:
31
+ raise ValueError(f"Unsafe path in Hugging Face JSON manifest: {value!r}")
32
+ return path.as_posix()
33
+
34
+
35
+ def _set_resolved_revision(config, revision: str) -> None:
36
+ inputs = config.DATA.inputs
37
+ values = inputs.values() if isinstance(inputs, dict) else vars(inputs).values()
38
+ for input_cfg in values:
39
+ source = input_cfg.get("source") if isinstance(input_cfg, dict) else input_cfg.source
40
+ if str(source.get("format") if isinstance(source, dict) else source.format).lower() == "hf_json":
41
+ if isinstance(source, dict):
42
+ source["resolved_revision"] = revision
43
+ else:
44
+ source.resolved_revision = revision
45
+ return
46
+
47
+
48
+ def prepare_hf_json_split(config, split: str) -> PreparedHFJsonSplit:
49
+ """Download one split's selected media, then publish its local manifest.
50
+
51
+ The localization video and tracking loaders inspect each file during
52
+ construction. Stage only the configured input type while keeping its
53
+ manifest type and other metadata intact. Hub blobs are linked into a
54
+ small split directory.
55
+ """
56
+ source = hf_json_source(config)
57
+ if not source:
58
+ raise ValueError("Source format must be 'hf_json'.")
59
+ if split not in {"train", "valid", "test"}:
60
+ raise ValueError(f"Unsupported Hugging Face JSON split: {split!r}")
61
+
62
+ repo_id = str(source.get("repo_id") or "").strip()
63
+ branch = str(source.get("revision") or "main").strip()
64
+ cache_dir = Path(str(source.get("cache_dir") or "~/.cache/opensportslib/hf_json")).expanduser()
65
+ media_type = str(source.get("input_type") or "video_mp4")
66
+ if not repo_id or not branch:
67
+ raise ValueError("hf_json requires source.repo_id and source.revision.")
68
+
69
+ try:
70
+ from huggingface_hub import HfApi, hf_hub_download
71
+
72
+ revision = str(source.get("resolved_revision") or "").strip()
73
+ if not revision:
74
+ revision = HfApi(token=True).repo_info(
75
+ repo_id=repo_id, repo_type="dataset", revision=branch
76
+ ).sha
77
+ if not revision:
78
+ raise ValueError("The Hub did not return a commit SHA.")
79
+ except Exception as exc:
80
+ raise RuntimeError(
81
+ f"Cannot access Hugging Face dataset {repo_id!r} at {branch!r}. "
82
+ "Request access and run `hf auth login`."
83
+ ) from exc
84
+
85
+ _set_resolved_revision(config, revision)
86
+ safe_repo = re.sub(r"[^A-Za-z0-9_.-]+", "--", repo_id)
87
+ root = cache_dir / safe_repo / revision
88
+ root.mkdir(parents=True, exist_ok=True)
89
+ manifest_name = _safe_path(str(source.get("annotation_pattern") or "annotations_{split}.json").format(split=split))
90
+ annotation_path = root / f"selected_{media_type}_{split}.json"
91
+ prepared = PreparedHFJsonSplit(annotation_path, root, revision)
92
+ hub_cache = cache_dir / "hub"
93
+
94
+ with (root / f".{split}.lock").open("w") as lock:
95
+ fcntl.flock(lock, fcntl.LOCK_EX)
96
+ try:
97
+ cached_manifest = annotation_path.is_file()
98
+ if cached_manifest:
99
+ payload = json.loads(annotation_path.read_text(encoding="utf-8"))
100
+ # Older caches could contain an input relabeled to ``video``.
101
+ # Refresh those manifests so the selected source type is kept.
102
+ valid_cache = isinstance(payload, dict) and isinstance(payload.get("data"), list)
103
+ if valid_cache:
104
+ valid_cache = all(
105
+ isinstance(sample, dict)
106
+ and len([
107
+ item for item in sample.get("inputs", [])
108
+ if isinstance(item, dict) and item.get("type") == media_type
109
+ ]) == 1
110
+ for sample in payload["data"]
111
+ )
112
+ if not valid_cache:
113
+ cached_manifest = False
114
+ if not cached_manifest:
115
+ remote_manifest = hf_hub_download(
116
+ repo_id=repo_id, filename=manifest_name, repo_type="dataset",
117
+ revision=revision, token=True, cache_dir=str(hub_cache),
118
+ )
119
+ payload = json.loads(Path(remote_manifest).read_text(encoding="utf-8"))
120
+ if not isinstance(payload, dict) or not isinstance(payload.get("data"), list):
121
+ raise ValueError(f"{manifest_name} is not an OSL JSON manifest")
122
+ if not payload["data"]:
123
+ raise ValueError(f"{manifest_name} contains no samples")
124
+
125
+ filenames = set()
126
+ for sample in payload["data"]:
127
+ selected = [item for item in sample.get("inputs", []) if item.get("type") == media_type]
128
+ if len(selected) != 1:
129
+ raise ValueError(
130
+ f"Expected one {media_type!r} input for sample {sample.get('game_id', sample.get('id'))!r}"
131
+ )
132
+ item = dict(selected[0])
133
+ filename = _safe_path(str(item.get("path") or ""))
134
+ if not filename.startswith(f"{split}/"):
135
+ raise ValueError(f"Media path {filename!r} is outside split {split!r}")
136
+ filenames.add(filename)
137
+ sample["inputs"] = [item]
138
+
139
+ def stage_media(filename: str) -> None:
140
+ destination = root / filename
141
+ if destination.is_file():
142
+ return
143
+ downloaded = Path(hf_hub_download(
144
+ repo_id=repo_id, filename=filename, repo_type="dataset",
145
+ revision=revision, token=True, cache_dir=str(hub_cache),
146
+ ))
147
+ destination.parent.mkdir(parents=True, exist_ok=True)
148
+ temporary = destination.with_name(f".{destination.name}.{os.getpid()}.tmp")
149
+ try:
150
+ temporary.symlink_to(downloaded)
151
+ os.replace(temporary, destination)
152
+ finally:
153
+ temporary.unlink(missing_ok=True)
154
+
155
+ with ThreadPoolExecutor(max_workers=min(4, len(filenames))) as executor:
156
+ list(executor.map(stage_media, sorted(filenames)))
157
+
158
+ if not cached_manifest:
159
+ temporary = annotation_path.with_name(f".{annotation_path.name}.{os.getpid()}.tmp")
160
+ try:
161
+ temporary.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
162
+ os.replace(temporary, annotation_path)
163
+ finally:
164
+ temporary.unlink(missing_ok=True)
165
+ except Exception as exc:
166
+ raise RuntimeError(
167
+ f"Failed to stage {split!r} from {repo_id}@{revision}: {exc}"
168
+ ) from exc
169
+ return prepared