opensportslib 0.3.0.dev19__tar.gz → 0.3.0.dev21__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 (194) hide show
  1. {opensportslib-0.3.0.dev19/opensportslib.egg-info → opensportslib-0.3.0.dev21}/PKG-INFO +1 -1
  2. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/setup/setup.py +0 -9
  3. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/tools/hf_transfer.py +160 -22
  4. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/tools/osl_json_to_parquet.py +74 -12
  5. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/tools/parquet_to_osl_json.py +33 -17
  6. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21/opensportslib.egg-info}/PKG-INFO +1 -1
  7. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/pyproject.toml +1 -1
  8. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_hf_transfer_tools.py +161 -0
  9. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/LICENSE +0 -0
  10. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/LICENSE-COMMERCIAL +0 -0
  11. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/MANIFEST.in +0 -0
  12. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/README.md +0 -0
  13. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/examples/quickstart/basic_classification.py +0 -0
  14. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/examples/quickstart/basic_localization.py +0 -0
  15. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/examples/quickstart/basic_vqa.py +0 -0
  16. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/__init__.py +0 -0
  17. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/adaptation/__init__.py +0 -0
  18. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/adaptation/spotta.py +0 -0
  19. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/apis/__init__.py +0 -0
  20. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/apis/base_task_model.py +0 -0
  21. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/apis/classification.py +0 -0
  22. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/apis/localization.py +0 -0
  23. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/apis/vqa.py +0 -0
  24. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/cli.py +0 -0
  25. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/classification/default.yaml +0 -0
  26. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
  27. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
  28. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/classification/video.yaml +0 -0
  29. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/default.yaml +0 -0
  30. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
  31. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/localization/default.yaml +0 -0
  32. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/localization/e2e_spotta.yaml +0 -0
  33. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/localization/h5_header_distance.yaml +0 -0
  34. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/localization/h5_header_skeleton.yaml +0 -0
  35. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
  36. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/localization/tracking_action_spotting.yaml +0 -0
  37. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/localization/video_dali.yaml +0 -0
  38. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/localization/video_ocv.yaml +0 -0
  39. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/vqa/default.yaml +0 -0
  40. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/vqa/qwen.yaml +0 -0
  41. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/vqa/qwen3_vl_native.yaml +0 -0
  42. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/vqa/qwen_lora.yaml +0 -0
  43. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/vqa/qwen_sngar_frames.yaml +0 -0
  44. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/configs/vqa/xvars.yaml +0 -0
  45. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/__init__.py +0 -0
  46. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/__init__.py +0 -0
  47. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/accessors.py +0 -0
  48. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/conflicts.py +0 -0
  49. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/loader.py +0 -0
  50. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/migrate.py +0 -0
  51. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/migrations/__init__.py +0 -0
  52. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  53. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/runtime_adapter.py +0 -0
  54. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/schema.py +0 -0
  55. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/schemas/__init__.py +0 -0
  56. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  57. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  58. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/config/validate.py +0 -0
  59. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/loss/__init__.py +0 -0
  60. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/loss/builder.py +0 -0
  61. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/loss/calf.py +0 -0
  62. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/loss/ce.py +0 -0
  63. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/loss/combine.py +0 -0
  64. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/loss/nll.py +0 -0
  65. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/optimizer/__init__.py +0 -0
  66. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/optimizer/builder.py +0 -0
  67. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  68. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/scheduler/__init__.py +0 -0
  69. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/scheduler/builder.py +0 -0
  70. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/trainer/__init__.py +0 -0
  71. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/trainer/classification_trainer.py +0 -0
  72. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/trainer/localization_trainer.py +0 -0
  73. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/trainer/vqa_trainer.py +0 -0
  74. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/checkpoint.py +0 -0
  75. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/config.py +0 -0
  76. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/config_normalize.py +0 -0
  77. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/data.py +0 -0
  78. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/ddp.py +0 -0
  79. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/default_args.py +0 -0
  80. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/hf_runtime.py +0 -0
  81. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/lightning.py +0 -0
  82. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/load_annotations.py +0 -0
  83. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/seed.py +0 -0
  84. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/video_processing.py +0 -0
  85. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/core/utils/wandb.py +0 -0
  86. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/datasets/__init__.py +0 -0
  87. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/datasets/builder.py +0 -0
  88. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/datasets/classification_dataset.py +0 -0
  89. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/datasets/localization_dataset.py +0 -0
  90. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/datasets/utils/__init__.py +0 -0
  91. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/datasets/utils/h5_tracking.py +0 -0
  92. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/datasets/utils/tracking.py +0 -0
  93. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/datasets/vqa_dataset.py +0 -0
  94. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/legacy_config/classification.yaml +0 -0
  95. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  96. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  97. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  98. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/legacy_config/localization.yaml +0 -0
  99. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  100. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  101. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/metrics/classification_metric.py +0 -0
  102. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/metrics/localization_metric.py +0 -0
  103. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/metrics/vqa_metric.py +0 -0
  104. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/__init__.py +0 -0
  105. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/backbones/builder.py +0 -0
  106. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/contextaware.py +0 -0
  107. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/e2e.py +0 -0
  108. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/learnablepooling.py +0 -0
  109. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/qwen_vl_native.py +0 -0
  110. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/qwen_xvars.py +0 -0
  111. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/rule_based.py +0 -0
  112. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/tracking.py +0 -0
  113. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/vars.py +0 -0
  114. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/video.py +0 -0
  115. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/video_chatgpt_compat.py +0 -0
  116. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/video_mae.py +0 -0
  117. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/base/xvars_videochatgpt.py +0 -0
  118. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/builder.py +0 -0
  119. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/heads/builder.py +0 -0
  120. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/neck/builder.py +0 -0
  121. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/common.py +0 -0
  122. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/impl/__init__.py +0 -0
  123. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/impl/asformer.py +0 -0
  124. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/impl/calf.py +0 -0
  125. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/impl/gsm.py +0 -0
  126. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/impl/gtad.py +0 -0
  127. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/impl/tsm.py +0 -0
  128. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/litebase.py +0 -0
  129. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/modules.py +0 -0
  130. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/shift.py +0 -0
  131. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/utils.py +0 -0
  132. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/vqa_prediction_priors.py +0 -0
  133. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/vqa_prompting.py +0 -0
  134. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/models/utils/xvars_clip_index.py +0 -0
  135. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/tools/__init__.py +0 -0
  136. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib/tools/_common.py +0 -0
  137. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib.egg-info/SOURCES.txt +0 -0
  138. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib.egg-info/dependency_links.txt +0 -0
  139. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib.egg-info/entry_points.txt +0 -0
  140. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib.egg-info/requires.txt +0 -0
  141. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/opensportslib.egg-info/top_level.txt +0 -0
  142. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/scripts/run_h5_header_rule_inference.py +0 -0
  143. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/setup.cfg +0 -0
  144. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/conftest.py +0 -0
  145. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/release/__init__.py +0 -0
  146. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/release/_release_common.py +0 -0
  147. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/release/test_classification_release.py +0 -0
  148. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/release/test_localization_release.py +0 -0
  149. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/release/test_vqa_release.py +0 -0
  150. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_classification_dataset_paths.py +0 -0
  151. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_classification_trainer_dataloader.py +0 -0
  152. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_config_architecture.py +0 -0
  153. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_config_split_override_sync.py +0 -0
  154. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_config_utils_smoke.py +0 -0
  155. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_conversion_tools.py +0 -0
  156. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_extract_xvars_features.py +0 -0
  157. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_h5_header_rule_spotter.py +0 -0
  158. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_h5_header_skeleton_spotter.py +0 -0
  159. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_h5_tracking_dataset.py +0 -0
  160. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_localization_dali_filenames.py +0 -0
  161. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_localization_hf_backend_override.py +0 -0
  162. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_localization_intervals.py +0 -0
  163. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_package_smoke.py +0 -0
  164. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_pretrained_config_merge_policy.py +0 -0
  165. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_public_apis_smoke.py +0 -0
  166. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_setup_cli.py +0 -0
  167. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_spotta_e2e.py +0 -0
  168. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_subset_train_infer_integration.py +0 -0
  169. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_task_model_api_contract.py +0 -0
  170. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_vqa_api.py +0 -0
  171. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_vqa_metrics_semantic.py +0 -0
  172. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_vqa_qwen_xvars.py +0 -0
  173. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_vqa_training_lora.py +0 -0
  174. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tests/test_vqa_xvars_videochatgpt.py +0 -0
  175. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/build_sn_vqa_2026_vqa.py +0 -0
  176. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/build_sngar_spotting.py +0 -0
  177. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/build_soccernet_gar.py +0 -0
  178. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  179. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/build_soccernet_gar_vqa.py +0 -0
  180. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/build_xvars_indexes.py +0 -0
  181. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/extract_xvars_clip_features.py +0 -0
  182. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  183. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  184. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/sngar_dataset_card.py +0 -0
  185. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/sngar_events.py +0 -0
  186. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/convert/verify_sngar_spotting.py +0 -0
  187. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/download/download_hf_repo.py +0 -0
  188. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/download/download_osl_hf.py +0 -0
  189. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/download/push_sngar_spotting.py +0 -0
  190. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/download/upload_osl_hf.py +0 -0
  191. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/training/classification.py +0 -0
  192. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/training/localization.py +0 -0
  193. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/tools/training/vqa.py +0 -0
  194. {opensportslib-0.3.0.dev19 → opensportslib-0.3.0.dev21}/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.0.dev19
3
+ Version: 0.3.0.dev21
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
@@ -13,11 +13,6 @@ LEGACY_GPU_MAX_COMPUTE_CAPABILITY = (7, 4)
13
13
  LEGACY_GPU_CUDA_WHEEL = "cu126"
14
14
  LEGACY_GPU_CUDA_WHEEL_MAX_COMPUTE_CAPABILITY = (9, 0)
15
15
  CUDA13_REQUIRED_MIN_COMPUTE_CAPABILITY = (10, 0)
16
- LEGACY_GPU_TORCH_PACKAGES = (
17
- "torch==2.10.0",
18
- "torchvision==0.25.0",
19
- "torchaudio==2.10.0",
20
- )
21
16
 
22
17
  XVARS_DEPENDENCY_PINS = {
23
18
  "transformers": "4.38.2",
@@ -140,10 +135,6 @@ def select_cuda_wheel(cuda_version, compute_capabilities):
140
135
 
141
136
 
142
137
  def select_torch_packages(compute_capabilities):
143
- # if compute_capabilities and any(
144
- # capability <= LEGACY_GPU_MAX_COMPUTE_CAPABILITY for capability in compute_capabilities
145
- # ):
146
- # return LEGACY_GPU_TORCH_PACKAGES
147
138
  return ("torch", "torchvision", "torchaudio")
148
139
 
149
140
 
@@ -117,9 +117,13 @@ def extract_repo_paths_from_json(
117
117
  if not isinstance(inp, dict):
118
118
  continue
119
119
  path = inp.get("path")
120
- if not path:
121
- continue
122
- repo_paths.append(str(path).lstrip("/"))
120
+ if path:
121
+ repo_paths.append(str(path).lstrip("/"))
122
+ # player_joints_h5 / player_centroids_h5 inputs may carry a
123
+ # sidecar ball_path; download it alongside the primary input.
124
+ ball_path = inp.get("ball_path")
125
+ if ball_path:
126
+ repo_paths.append(str(ball_path).lstrip("/"))
123
127
 
124
128
  if not repo_paths:
125
129
  raise ValueError("No file paths found in the provided JSON (no inputs with 'path').")
@@ -435,6 +439,131 @@ def _download_json_path_from_hf(
435
439
  return result
436
440
 
437
441
 
442
+ _PREFERRED_SPLIT_ORDER = ["train", "valid", "val", "validation", "test", "challenge"]
443
+ _NON_SPLIT_JSON_FILES = {"dataset_infos.json", "dataset_dict.json"}
444
+
445
+
446
+ def _sort_splits(splits: set[str]) -> list[str]:
447
+ def _sort_key(name: str) -> tuple[int, str]:
448
+ try:
449
+ rank = _PREFERRED_SPLIT_ORDER.index(name.lower())
450
+ except ValueError:
451
+ rank = len(_PREFERRED_SPLIT_ORDER)
452
+ return (rank, name.lower())
453
+
454
+ return sorted(splits, key=_sort_key)
455
+
456
+
457
+ def list_dataset_branches_on_hf(
458
+ repo_id: str,
459
+ *,
460
+ token: str | None = None,
461
+ ) -> list[str]:
462
+ cleaned_repo_id = str(repo_id or "").strip()
463
+ if not cleaned_repo_id:
464
+ raise ValueError("repo_id is required.")
465
+
466
+ HfApi, _, _ = _import_hf_hub()
467
+ api = HfApi(token=token or None)
468
+ refs = api.list_repo_refs(cleaned_repo_id, repo_type="dataset")
469
+ branch_names = [str(branch.name) for branch in getattr(refs, "branches", [])]
470
+
471
+ unique_names = sorted(set(branch_names))
472
+ if "main" in unique_names:
473
+ unique_names.remove("main")
474
+ return ["main"] + unique_names
475
+ return unique_names
476
+
477
+
478
+ def list_dataset_splits_on_hf(
479
+ repo_id: str,
480
+ revision: str,
481
+ *,
482
+ token: str | None = None,
483
+ ) -> dict[str, Any]:
484
+ cleaned_repo_id = str(repo_id or "").strip()
485
+ cleaned_revision = str(revision or "").strip() or "main"
486
+ if not cleaned_repo_id:
487
+ raise ValueError("repo_id is required.")
488
+
489
+ HfApi, _, _ = _import_hf_hub()
490
+ api = HfApi(token=token or None)
491
+ repo_files = api.list_repo_files(
492
+ cleaned_repo_id,
493
+ revision=cleaned_revision,
494
+ repo_type="dataset",
495
+ )
496
+
497
+ parquet_splits: set[str] = set()
498
+ json_splits: set[str] = set()
499
+ for path in repo_files:
500
+ normalized = _normalize_repo_path(path)
501
+ if "/" in normalized:
502
+ folder, filename = normalized.split("/", 1)
503
+ # Only the canonical Parquet+WebDataset export layout counts as a
504
+ # parquet split (produced by convert_json_to_parquet / expected by
505
+ # convert_parquet_to_json): `{split}/metadata.parquet` plus TAR
506
+ # shards under `{split}/shards/`. A JSON-format dataset can also
507
+ # reference arbitrary `.parquet` media files (e.g. tensor-encoded
508
+ # videos) under a folder that happens to share the split's name,
509
+ # so a loose "any .parquet/.tar anywhere under this folder" check
510
+ # would misclassify those as Parquet+WebDataset splits.
511
+ if folder and (
512
+ filename == "metadata.parquet"
513
+ or (filename.startswith("shards/") and filename.lower().endswith(".tar"))
514
+ ):
515
+ parquet_splits.add(folder)
516
+ elif normalized.lower().endswith(".json") and normalized not in _NON_SPLIT_JSON_FILES:
517
+ json_splits.add(normalized[: -len(".json")])
518
+
519
+ if parquet_splits:
520
+ return {"format": "parquet", "splits": _sort_splits(parquet_splits)}
521
+ if json_splits:
522
+ return {"format": "json", "splits": _sort_splits(json_splits)}
523
+ return {"format": None, "splits": []}
524
+
525
+
526
+ def download_dataset_splits_from_hf(
527
+ repo_id: str,
528
+ revision: str,
529
+ splits: list[str],
530
+ output_dir: str,
531
+ *,
532
+ download_format: str = "parquet",
533
+ dry_run: bool = False,
534
+ token: str | None = None,
535
+ progress_cb: ProgressCallback | None = None,
536
+ is_cancelled: CancelCheck | None = None,
537
+ ) -> list[dict[str, Any]]:
538
+ cleaned_splits = [str(split or "").strip() for split in (splits or [])]
539
+ cleaned_splits = [split for split in cleaned_splits if split]
540
+ if not cleaned_splits:
541
+ raise ValueError("At least one split is required.")
542
+
543
+ total = len(cleaned_splits)
544
+ results: list[dict[str, Any]] = []
545
+ for idx, split in enumerate(cleaned_splits, start=1):
546
+ _ensure_not_cancelled(is_cancelled)
547
+
548
+ def _scoped_progress(message: str, _idx: int = idx, _split: str = split) -> None:
549
+ _emit_progress(progress_cb, f"[{_idx}/{total}] {_split}: {message}")
550
+
551
+ result = download_dataset_split_from_hf(
552
+ repo_id,
553
+ revision,
554
+ split,
555
+ output_dir,
556
+ download_format=download_format,
557
+ dry_run=dry_run,
558
+ token=token,
559
+ progress_cb=_scoped_progress,
560
+ is_cancelled=is_cancelled,
561
+ )
562
+ results.append(result)
563
+
564
+ return results
565
+
566
+
438
567
  def download_dataset_split_from_hf(
439
568
  repo_id: str,
440
569
  revision: str,
@@ -512,28 +641,37 @@ def extract_local_input_upload_entries_from_json(dataset_json_path: str) -> list
512
641
  for inp in inputs:
513
642
  if not isinstance(inp, dict):
514
643
  continue
515
- raw_path = str(inp.get("path") or "").strip()
516
- if not raw_path:
517
- continue
518
644
 
519
- local_path = raw_path if os.path.isabs(raw_path) else os.path.join(base_dir, raw_path)
520
- local_path = os.path.abspath(local_path)
521
- if not os.path.isfile(local_path):
522
- raise FileNotFoundError(
523
- f"Input file from dataset JSON not found on disk: {raw_path} (resolved: {local_path})"
645
+ raw_paths = []
646
+ path = str(inp.get("path") or "").strip()
647
+ if path:
648
+ raw_paths.append(path)
649
+ # player_joints_h5 / player_centroids_h5 inputs may carry a sidecar
650
+ # ball_path pointing at a separate ball-tracking h5 file; include it
651
+ # alongside the primary input when present.
652
+ ball_path = str(inp.get("ball_path") or "").strip()
653
+ if ball_path:
654
+ raw_paths.append(ball_path)
655
+
656
+ for raw_path in raw_paths:
657
+ local_path = raw_path if os.path.isabs(raw_path) else os.path.join(base_dir, raw_path)
658
+ local_path = os.path.abspath(local_path)
659
+ if not os.path.isfile(local_path):
660
+ raise FileNotFoundError(
661
+ f"Input file from dataset JSON not found on disk: {raw_path} (resolved: {local_path})"
662
+ )
663
+
664
+ path_in_repo = _normalize_repo_path(raw_path)
665
+ if not path_in_repo:
666
+ raise ValueError(f"Invalid input path in dataset JSON: {raw_path}")
667
+
668
+ entries.append(
669
+ {
670
+ "local_path": local_path,
671
+ "path_in_repo": path_in_repo,
672
+ }
524
673
  )
525
674
 
526
- path_in_repo = _normalize_repo_path(raw_path)
527
- if not path_in_repo:
528
- raise ValueError(f"Invalid input path in dataset JSON: {raw_path}")
529
-
530
- entries.append(
531
- {
532
- "local_path": local_path,
533
- "path_in_repo": path_in_repo,
534
- }
535
- )
536
-
537
675
  if not entries:
538
676
  raise ValueError("No valid data[].inputs[].path entries found in the provided dataset JSON.")
539
677
  return entries
@@ -149,9 +149,14 @@ def _estimate_sample_tar_size(
149
149
  for input_item in _extract_inputs_with_path(sample):
150
150
  rel_path = str(input_item["path"])
151
151
  resolved = _resolve_media_path(media_root, rel_path, missing_policy=missing_policy)
152
- if resolved is None:
153
- continue
154
- total += _tar_member_size(resolved.stat().st_size)
152
+ if resolved is not None:
153
+ total += _tar_member_size(resolved.stat().st_size)
154
+
155
+ ball_rel_path = str(input_item.get("ball_path") or "").strip()
156
+ if ball_rel_path:
157
+ ball_resolved = _resolve_media_path(media_root, ball_rel_path, missing_policy=missing_policy)
158
+ if ball_resolved is not None:
159
+ total += _tar_member_size(ball_resolved.stat().st_size)
155
160
  return total
156
161
 
157
162
 
@@ -312,6 +317,7 @@ def convert_json_to_parquet(
312
317
  for input_idx, input_item in enumerate(_extract_inputs_with_path(sample)):
313
318
  rel_path = str(input_item["path"])
314
319
  resolved = _resolve_media_path(media_root, rel_path, missing_policy=missing_policy)
320
+ input_type = str(input_item.get("type", "")).strip()
315
321
 
316
322
  if resolved is None:
317
323
  total_missing_input_files += 1
@@ -319,25 +325,65 @@ def convert_json_to_parquet(
319
325
  "sample_id": sample_id,
320
326
  "shard_name": shard_name,
321
327
  "input_index": input_idx,
322
- "input_type": str(input_item.get("type", "")).strip(),
328
+ "file_role": "primary",
329
+ "input_type": input_type,
323
330
  "relative_path": rel_path,
324
331
  "resolved_path": None,
325
332
  "status": "missing",
326
333
  })
334
+ else:
335
+ ext = resolved.suffix.lstrip(".").lower() or "bin"
336
+ arcname = f"{key}.{input_idx}.{ext}"
337
+ _add_file_to_tar(tar, resolved, arcname)
338
+ shard_manifest.append({
339
+ "sample_id": sample_id,
340
+ "shard_name": shard_name,
341
+ "input_index": input_idx,
342
+ "file_role": "primary",
343
+ "input_type": input_type,
344
+ "relative_path": rel_path,
345
+ "resolved_path": str(resolved if not keep_relative_paths_in_parquet else rel_path),
346
+ "status": "ok",
347
+ "wds_member": arcname,
348
+ })
349
+ total_input_files_added += 1
350
+
351
+ # player_joints_h5 / player_centroids_h5 inputs may carry a
352
+ # sidecar ball_path pointing at a separate ball-tracking h5
353
+ # file; bundle it into the same shard, tagged with a distinct
354
+ # arcname (".ball.") so it doesn't collide with the primary
355
+ # input's tar member.
356
+ ball_rel_path = str(input_item.get("ball_path") or "").strip()
357
+ if not ball_rel_path:
358
+ continue
359
+ ball_resolved = _resolve_media_path(media_root, ball_rel_path, missing_policy=missing_policy)
360
+ if ball_resolved is None:
361
+ total_missing_input_files += 1
362
+ shard_manifest.append({
363
+ "sample_id": sample_id,
364
+ "shard_name": shard_name,
365
+ "input_index": input_idx,
366
+ "file_role": "ball",
367
+ "input_type": input_type,
368
+ "relative_path": ball_rel_path,
369
+ "resolved_path": None,
370
+ "status": "missing",
371
+ })
327
372
  continue
328
373
 
329
- ext = resolved.suffix.lstrip(".").lower() or "bin"
330
- arcname = f"{key}.{input_idx}.{ext}"
331
- _add_file_to_tar(tar, resolved, arcname)
374
+ ball_ext = ball_resolved.suffix.lstrip(".").lower() or "bin"
375
+ ball_arcname = f"{key}.{input_idx}.ball.{ball_ext}"
376
+ _add_file_to_tar(tar, ball_resolved, ball_arcname)
332
377
  shard_manifest.append({
333
378
  "sample_id": sample_id,
334
379
  "shard_name": shard_name,
335
380
  "input_index": input_idx,
336
- "input_type": str(input_item.get("type", "")).strip(),
337
- "relative_path": rel_path,
338
- "resolved_path": str(resolved if not keep_relative_paths_in_parquet else rel_path),
381
+ "file_role": "ball",
382
+ "input_type": input_type,
383
+ "relative_path": ball_rel_path,
384
+ "resolved_path": str(ball_resolved if not keep_relative_paths_in_parquet else ball_rel_path),
339
385
  "status": "ok",
340
- "wds_member": arcname,
386
+ "wds_member": ball_arcname,
341
387
  })
342
388
  total_input_files_added += 1
343
389
 
@@ -349,14 +395,25 @@ def convert_json_to_parquet(
349
395
  ok_manifest = manifest_df[manifest_df["status"] == "ok"].copy()
350
396
  if not ok_manifest.empty:
351
397
  ok_manifest["input_index"] = ok_manifest["input_index"].astype(int)
398
+ primary_manifest = ok_manifest[ok_manifest["file_role"] == "primary"]
399
+ ball_manifest = ok_manifest[ok_manifest["file_role"] == "ball"]
400
+
352
401
  by_sample_input_paths: Dict[str, Dict[int, str]] = {}
353
- for sample_id, sample_manifest in ok_manifest.groupby("sample_id", sort=False):
402
+ for sample_id, sample_manifest in primary_manifest.groupby("sample_id", sort=False):
354
403
  sample_manifest = sample_manifest.sort_values("input_index")
355
404
  by_sample_input_paths[str(sample_id)] = {
356
405
  int(rec["input_index"]): str(rec["resolved_path"])
357
406
  for _, rec in sample_manifest.iterrows()
358
407
  }
359
408
 
409
+ by_sample_ball_paths: Dict[str, Dict[int, str]] = {}
410
+ for sample_id, sample_manifest in ball_manifest.groupby("sample_id", sort=False):
411
+ sample_manifest = sample_manifest.sort_values("input_index")
412
+ by_sample_ball_paths[str(sample_id)] = {
413
+ int(rec["input_index"]): str(rec["resolved_path"])
414
+ for _, rec in sample_manifest.iterrows()
415
+ }
416
+
360
417
  def _resolved_payload_for_row(row: pd.Series) -> str:
361
418
  payload = maybe_json_loads(row.get("sample_payload"), {})
362
419
  if not isinstance(payload, dict):
@@ -368,6 +425,7 @@ def convert_json_to_parquet(
368
425
  return json_dumps_compact(payload_copy)
369
426
 
370
427
  resolved_by_index = by_sample_input_paths.get(str(row.get("sample_id")), {})
428
+ resolved_ball_by_index = by_sample_ball_paths.get(str(row.get("sample_id")), {})
371
429
  with_path_idx = 0
372
430
  rewritten: List[Any] = []
373
431
  for inp in inputs_value:
@@ -379,6 +437,10 @@ def convert_json_to_parquet(
379
437
  resolved = resolved_by_index.get(with_path_idx)
380
438
  if resolved is not None:
381
439
  inp_copy["path"] = resolved
440
+ if inp_copy.get("ball_path"):
441
+ resolved_ball = resolved_ball_by_index.get(with_path_idx)
442
+ if resolved_ball is not None:
443
+ inp_copy["ball_path"] = resolved_ball
382
444
  with_path_idx += 1
383
445
  rewritten.append(inp_copy)
384
446
  payload_copy["inputs"] = rewritten
@@ -52,18 +52,23 @@ def _extract_sample_media_from_tar(
52
52
  sample_index: int,
53
53
  output_media_root: Path,
54
54
  original_paths: List[str],
55
+ original_ball_paths: Optional[List[Optional[str]]] = None,
55
56
  overwrite: bool = False,
56
57
  ) -> int:
57
58
  """
58
- Extract all input files for *sample_index* from the shard.
59
+ Extract all input files for *sample_index* from the shard, including any
60
+ sidecar ball-tracking files (tar members named ``<key>.<idx>.ball.<ext>``).
59
61
 
60
- Files are written to ``output_media_root / original_path``, preserving the
61
- original relative path structure so that ``inputs[].path`` values stay valid.
62
+ Files are written to ``output_media_root / original_path`` (or
63
+ ``output_media_root / original_ball_path`` for ball members), preserving
64
+ the original relative path structure so that ``inputs[].path`` /
65
+ ``inputs[].ball_path`` values stay valid.
62
66
 
63
67
  Returns the number of files extracted.
64
68
  """
65
69
  key_prefix = f"{sample_index:09d}."
66
70
  extracted = 0
71
+ ball_paths = original_ball_paths or []
67
72
 
68
73
  with tarfile.open(tar_path, "r") as tar:
69
74
  members = [
@@ -72,20 +77,27 @@ def _extract_sample_media_from_tar(
72
77
  if m.isfile() and m.name.startswith(key_prefix) and not m.name.endswith(".json")
73
78
  ]
74
79
 
75
- def _input_idx(m: tarfile.TarInfo) -> int:
76
- part = m.name[len(key_prefix) :].split(".", 1)[0]
80
+ def _member_info(m: tarfile.TarInfo) -> tuple[int, bool]:
81
+ parts = m.name[len(key_prefix) :].split(".")
77
82
  try:
78
- return int(part)
83
+ idx = int(parts[0])
79
84
  except ValueError:
80
- return 0
85
+ idx = 0
86
+ is_ball = len(parts) >= 3 and parts[1] == "ball"
87
+ return idx, is_ball
81
88
 
82
- members.sort(key=_input_idx)
89
+ members.sort(key=_member_info)
83
90
 
84
91
  for member in members:
85
- input_idx = _input_idx(member)
86
- if input_idx >= len(original_paths):
87
- continue
88
- out_path = output_media_root / original_paths[input_idx]
92
+ input_idx, is_ball = _member_info(member)
93
+ if is_ball:
94
+ if input_idx >= len(ball_paths) or not ball_paths[input_idx]:
95
+ continue
96
+ out_path = output_media_root / ball_paths[input_idx]
97
+ else:
98
+ if input_idx >= len(original_paths):
99
+ continue
100
+ out_path = output_media_root / original_paths[input_idx]
89
101
  if out_path.exists() and not overwrite:
90
102
  extracted += 1
91
103
  continue
@@ -230,16 +242,20 @@ def convert_parquet_to_json(
230
242
 
231
243
  if extract_media:
232
244
  inputs = sample.get("inputs", []) if isinstance(sample, dict) else []
233
- original_input_paths = [
234
- str(inp["path"])
235
- for inp in inputs
236
- if isinstance(inp, dict) and inp.get("path")
237
- ]
245
+ original_input_paths: List[str] = []
246
+ original_ball_paths: List[Optional[str]] = []
247
+ for inp in inputs:
248
+ if not isinstance(inp, dict) or not inp.get("path"):
249
+ continue
250
+ original_input_paths.append(str(inp["path"]))
251
+ ball_path = inp.get("ball_path")
252
+ original_ball_paths.append(str(ball_path) if ball_path else None)
238
253
  extracted_media_count += _extract_sample_media_from_tar(
239
254
  tar_path=tar_path,
240
255
  sample_index=sample_index,
241
256
  output_media_root=output_media_root_path,
242
257
  original_paths=original_input_paths,
258
+ original_ball_paths=original_ball_paths,
243
259
  overwrite=overwrite_media,
244
260
  )
245
261
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.3.0.dev19
3
+ Version: 0.3.0.dev21
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opensportslib"
7
- version = "0.3.0.dev19"
7
+ version = "0.3.0.dev21"
8
8
  description = "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."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -12,11 +12,14 @@ from opensportslib.tools.hf_transfer import (
12
12
  create_dataset_repo_on_hf,
13
13
  dataset_repo_exists_on_hf,
14
14
  download_dataset_split_from_hf,
15
+ download_dataset_splits_from_hf,
15
16
  extract_local_input_upload_entries_from_json,
16
17
  extract_repo_paths_from_json,
17
18
  is_hf_download_url_not_found_error,
18
19
  is_hf_repo_not_found_error,
19
20
  is_hf_revision_not_found_error,
21
+ list_dataset_branches_on_hf,
22
+ list_dataset_splits_on_hf,
20
23
  read_hf_source_metadata_from_dataset,
21
24
  upload_dataset_as_parquet_to_hf,
22
25
  upload_dataset_inputs_from_json_to_hf,
@@ -226,6 +229,164 @@ def test_dataset_repo_exists_on_hf_returns_false_for_repo_not_found(monkeypatch)
226
229
  assert dataset_repo_exists_on_hf("OpenSportsLab/missing-repo", token="hf_token") is False
227
230
 
228
231
 
232
+ def test_list_dataset_branches_on_hf_puts_main_first_then_alphabetical(monkeypatch):
233
+ class _FakeRefs:
234
+ branches = [
235
+ type("_Ref", (), {"name": "zeta"})(),
236
+ type("_Ref", (), {"name": "main"})(),
237
+ type("_Ref", (), {"name": "alpha"})(),
238
+ ]
239
+
240
+ class _FakeApi:
241
+ def __init__(self, token=None):
242
+ pass
243
+
244
+ def list_repo_refs(self, repo_id, repo_type=None):
245
+ return _FakeRefs()
246
+
247
+ monkeypatch.setattr(
248
+ "opensportslib.tools.hf_transfer._import_hf_hub",
249
+ lambda: (_FakeApi, object(), object()),
250
+ )
251
+
252
+ assert list_dataset_branches_on_hf("OpenSportsLab/repo") == ["main", "alpha", "zeta"]
253
+
254
+
255
+ def test_list_dataset_splits_on_hf_detects_parquet_layout(monkeypatch):
256
+ class _FakeApi:
257
+ def __init__(self, token=None):
258
+ pass
259
+
260
+ def list_repo_files(self, repo_id, revision=None, repo_type=None):
261
+ return [
262
+ "README.md",
263
+ "train/metadata.parquet",
264
+ "train/shard_manifest.parquet",
265
+ "train/shards/shard-000000.tar",
266
+ "test/metadata.parquet",
267
+ "test/shards/shard-000000.tar",
268
+ ]
269
+
270
+ monkeypatch.setattr(
271
+ "opensportslib.tools.hf_transfer._import_hf_hub",
272
+ lambda: (_FakeApi, object(), object()),
273
+ )
274
+
275
+ result = list_dataset_splits_on_hf("OpenSportsLab/repo", "main")
276
+
277
+ assert result == {"format": "parquet", "splits": ["train", "test"]}
278
+
279
+
280
+ def test_list_dataset_splits_on_hf_treats_json_dataset_with_parquet_media_as_json(monkeypatch):
281
+ """
282
+ A JSON-format dataset can reference arbitrary media files (e.g. tensor-encoded
283
+ videos serialized as .parquet) inside a folder that happens to share a split's
284
+ name. That must not be misdetected as the canonical Parquet+WebDataset export
285
+ layout, which requires `{split}/metadata.parquet` + `{split}/shards/*.tar`.
286
+ Regression test for OpenSportsLab/SNGAR-Action-Spotting-Tracking.
287
+ """
288
+
289
+ class _FakeApi:
290
+ def __init__(self, token=None):
291
+ pass
292
+
293
+ def list_repo_files(self, repo_id, revision=None, repo_type=None):
294
+ return [
295
+ "README.md",
296
+ "train.json",
297
+ "valid.json",
298
+ "test.json",
299
+ "train/videos/10502.parquet",
300
+ "train/videos/10503.parquet",
301
+ "valid/videos/3841.parquet",
302
+ "test/videos/3850.parquet",
303
+ ]
304
+
305
+ monkeypatch.setattr(
306
+ "opensportslib.tools.hf_transfer._import_hf_hub",
307
+ lambda: (_FakeApi, object(), object()),
308
+ )
309
+
310
+ result = list_dataset_splits_on_hf("OpenSportsLab/repo", "main")
311
+
312
+ assert result == {"format": "json", "splits": ["train", "valid", "test"]}
313
+
314
+
315
+ def test_list_dataset_splits_on_hf_detects_json_layout(monkeypatch):
316
+ class _FakeApi:
317
+ def __init__(self, token=None):
318
+ pass
319
+
320
+ def list_repo_files(self, repo_id, revision=None, repo_type=None):
321
+ return [
322
+ "dataset_infos.json",
323
+ "train.json",
324
+ "challenge.json",
325
+ "valid.json",
326
+ ]
327
+
328
+ monkeypatch.setattr(
329
+ "opensportslib.tools.hf_transfer._import_hf_hub",
330
+ lambda: (_FakeApi, object(), object()),
331
+ )
332
+
333
+ result = list_dataset_splits_on_hf("OpenSportsLab/repo", "main")
334
+
335
+ assert result == {"format": "json", "splits": ["train", "valid", "challenge"]}
336
+
337
+
338
+ def test_list_dataset_splits_on_hf_returns_none_format_when_nothing_matches(monkeypatch):
339
+ class _FakeApi:
340
+ def __init__(self, token=None):
341
+ pass
342
+
343
+ def list_repo_files(self, repo_id, revision=None, repo_type=None):
344
+ return ["README.md", "dataset_infos.json"]
345
+
346
+ monkeypatch.setattr(
347
+ "opensportslib.tools.hf_transfer._import_hf_hub",
348
+ lambda: (_FakeApi, object(), object()),
349
+ )
350
+
351
+ assert list_dataset_splits_on_hf("OpenSportsLab/repo", "main") == {"format": None, "splits": []}
352
+
353
+
354
+ def test_download_dataset_splits_from_hf_downloads_each_split_with_prefixed_progress(monkeypatch, tmp_path):
355
+ progress_messages = []
356
+ calls = []
357
+
358
+ def _fake_download_dataset_split_from_hf(repo_id, revision, split, output_dir, **kwargs):
359
+ calls.append((repo_id, revision, split, output_dir))
360
+ kwargs["progress_cb"](f"working on {split}")
361
+ return {"split": split, "json_path": str(tmp_path / f"{split}.json")}
362
+
363
+ monkeypatch.setattr(
364
+ "opensportslib.tools.hf_transfer.download_dataset_split_from_hf",
365
+ _fake_download_dataset_split_from_hf,
366
+ )
367
+
368
+ results = download_dataset_splits_from_hf(
369
+ "OpenSportsLab/repo",
370
+ "main",
371
+ ["train", "valid"],
372
+ str(tmp_path),
373
+ download_format="json",
374
+ progress_cb=progress_messages.append,
375
+ )
376
+
377
+ assert [call[2] for call in calls] == ["train", "valid"]
378
+ assert [result["split"] for result in results] == ["train", "valid"]
379
+ assert progress_messages == [
380
+ "[1/2] train: working on train",
381
+ "[2/2] valid: working on valid",
382
+ ]
383
+
384
+
385
+ def test_download_dataset_splits_from_hf_requires_at_least_one_split():
386
+ with pytest.raises(ValueError):
387
+ download_dataset_splits_from_hf("OpenSportsLab/repo", "main", [], "/tmp/out")
388
+
389
+
229
390
  def test_upload_dataset_inputs_from_json_to_hf_uploads_inputs_and_json(monkeypatch, tmp_path):
230
391
  clip_path = tmp_path / "train" / "clip_0.mp4"
231
392
  clip_path.parent.mkdir(parents=True)