opensportslib 0.3.0.dev18__tar.gz → 0.3.0.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 (194) hide show
  1. {opensportslib-0.3.0.dev18/opensportslib.egg-info → opensportslib-0.3.0.dev20}/PKG-INFO +3 -1
  2. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/README.md +2 -0
  3. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/setup/setup.py +0 -9
  4. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/tools/hf_transfer.py +153 -3
  5. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20/opensportslib.egg-info}/PKG-INFO +3 -1
  6. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/pyproject.toml +1 -1
  7. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/release/test_classification_release.py +36 -5
  8. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_hf_transfer_tools.py +196 -0
  9. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/LICENSE +0 -0
  10. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/LICENSE-COMMERCIAL +0 -0
  11. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/MANIFEST.in +0 -0
  12. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/examples/quickstart/basic_classification.py +0 -0
  13. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/examples/quickstart/basic_localization.py +0 -0
  14. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/examples/quickstart/basic_vqa.py +0 -0
  15. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/__init__.py +0 -0
  16. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/adaptation/__init__.py +0 -0
  17. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/adaptation/spotta.py +0 -0
  18. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/apis/__init__.py +0 -0
  19. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/apis/base_task_model.py +0 -0
  20. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/apis/classification.py +0 -0
  21. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/apis/localization.py +0 -0
  22. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/apis/vqa.py +0 -0
  23. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/cli.py +0 -0
  24. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/classification/default.yaml +0 -0
  25. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
  26. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
  27. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/classification/video.yaml +0 -0
  28. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/default.yaml +0 -0
  29. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
  30. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/localization/default.yaml +0 -0
  31. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/localization/e2e_spotta.yaml +0 -0
  32. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/localization/h5_header_distance.yaml +0 -0
  33. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/localization/h5_header_skeleton.yaml +0 -0
  34. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
  35. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/localization/tracking_action_spotting.yaml +0 -0
  36. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/localization/video_dali.yaml +0 -0
  37. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/localization/video_ocv.yaml +0 -0
  38. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/vqa/default.yaml +0 -0
  39. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/vqa/qwen.yaml +0 -0
  40. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/vqa/qwen3_vl_native.yaml +0 -0
  41. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/vqa/qwen_lora.yaml +0 -0
  42. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/vqa/qwen_sngar_frames.yaml +0 -0
  43. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/configs/vqa/xvars.yaml +0 -0
  44. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/__init__.py +0 -0
  45. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/__init__.py +0 -0
  46. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/accessors.py +0 -0
  47. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/conflicts.py +0 -0
  48. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/loader.py +0 -0
  49. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/migrate.py +0 -0
  50. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/migrations/__init__.py +0 -0
  51. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  52. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/runtime_adapter.py +0 -0
  53. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/schema.py +0 -0
  54. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/schemas/__init__.py +0 -0
  55. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  56. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  57. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/config/validate.py +0 -0
  58. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/loss/__init__.py +0 -0
  59. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/loss/builder.py +0 -0
  60. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/loss/calf.py +0 -0
  61. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/loss/ce.py +0 -0
  62. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/loss/combine.py +0 -0
  63. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/loss/nll.py +0 -0
  64. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/optimizer/__init__.py +0 -0
  65. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/optimizer/builder.py +0 -0
  66. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  67. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/scheduler/__init__.py +0 -0
  68. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/scheduler/builder.py +0 -0
  69. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/trainer/__init__.py +0 -0
  70. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/trainer/classification_trainer.py +0 -0
  71. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/trainer/localization_trainer.py +0 -0
  72. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/trainer/vqa_trainer.py +0 -0
  73. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/checkpoint.py +0 -0
  74. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/config.py +0 -0
  75. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/config_normalize.py +0 -0
  76. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/data.py +0 -0
  77. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/ddp.py +0 -0
  78. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/default_args.py +0 -0
  79. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/hf_runtime.py +0 -0
  80. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/lightning.py +0 -0
  81. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/load_annotations.py +0 -0
  82. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/seed.py +0 -0
  83. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/video_processing.py +0 -0
  84. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/core/utils/wandb.py +0 -0
  85. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/datasets/__init__.py +0 -0
  86. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/datasets/builder.py +0 -0
  87. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/datasets/classification_dataset.py +0 -0
  88. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/datasets/localization_dataset.py +0 -0
  89. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/datasets/utils/__init__.py +0 -0
  90. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/datasets/utils/h5_tracking.py +0 -0
  91. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/datasets/utils/tracking.py +0 -0
  92. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/datasets/vqa_dataset.py +0 -0
  93. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/legacy_config/classification.yaml +0 -0
  94. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  95. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  96. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  97. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/legacy_config/localization.yaml +0 -0
  98. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  99. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  100. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/metrics/classification_metric.py +0 -0
  101. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/metrics/localization_metric.py +0 -0
  102. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/metrics/vqa_metric.py +0 -0
  103. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/__init__.py +0 -0
  104. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/backbones/builder.py +0 -0
  105. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/contextaware.py +0 -0
  106. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/e2e.py +0 -0
  107. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/learnablepooling.py +0 -0
  108. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/qwen_vl_native.py +0 -0
  109. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/qwen_xvars.py +0 -0
  110. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/rule_based.py +0 -0
  111. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/tracking.py +0 -0
  112. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/vars.py +0 -0
  113. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/video.py +0 -0
  114. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/video_chatgpt_compat.py +0 -0
  115. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/video_mae.py +0 -0
  116. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/base/xvars_videochatgpt.py +0 -0
  117. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/builder.py +0 -0
  118. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/heads/builder.py +0 -0
  119. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/neck/builder.py +0 -0
  120. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/common.py +0 -0
  121. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/impl/__init__.py +0 -0
  122. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/impl/asformer.py +0 -0
  123. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/impl/calf.py +0 -0
  124. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/impl/gsm.py +0 -0
  125. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/impl/gtad.py +0 -0
  126. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/impl/tsm.py +0 -0
  127. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/litebase.py +0 -0
  128. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/modules.py +0 -0
  129. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/shift.py +0 -0
  130. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/utils.py +0 -0
  131. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/vqa_prediction_priors.py +0 -0
  132. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/vqa_prompting.py +0 -0
  133. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/models/utils/xvars_clip_index.py +0 -0
  134. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/tools/__init__.py +0 -0
  135. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/tools/_common.py +0 -0
  136. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/tools/osl_json_to_parquet.py +0 -0
  137. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib/tools/parquet_to_osl_json.py +0 -0
  138. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib.egg-info/SOURCES.txt +0 -0
  139. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib.egg-info/dependency_links.txt +0 -0
  140. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib.egg-info/entry_points.txt +0 -0
  141. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib.egg-info/requires.txt +0 -0
  142. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/opensportslib.egg-info/top_level.txt +0 -0
  143. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/scripts/run_h5_header_rule_inference.py +0 -0
  144. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/setup.cfg +0 -0
  145. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/conftest.py +0 -0
  146. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/release/__init__.py +0 -0
  147. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/release/_release_common.py +0 -0
  148. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/release/test_localization_release.py +0 -0
  149. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/release/test_vqa_release.py +0 -0
  150. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_classification_dataset_paths.py +0 -0
  151. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_classification_trainer_dataloader.py +0 -0
  152. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_config_architecture.py +0 -0
  153. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_config_split_override_sync.py +0 -0
  154. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_config_utils_smoke.py +0 -0
  155. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_conversion_tools.py +0 -0
  156. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_extract_xvars_features.py +0 -0
  157. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_h5_header_rule_spotter.py +0 -0
  158. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_h5_header_skeleton_spotter.py +0 -0
  159. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_h5_tracking_dataset.py +0 -0
  160. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_localization_dali_filenames.py +0 -0
  161. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_localization_hf_backend_override.py +0 -0
  162. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_localization_intervals.py +0 -0
  163. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_package_smoke.py +0 -0
  164. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_pretrained_config_merge_policy.py +0 -0
  165. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_public_apis_smoke.py +0 -0
  166. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_setup_cli.py +0 -0
  167. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_spotta_e2e.py +0 -0
  168. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_subset_train_infer_integration.py +0 -0
  169. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_task_model_api_contract.py +0 -0
  170. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_vqa_api.py +0 -0
  171. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_vqa_metrics_semantic.py +0 -0
  172. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_vqa_qwen_xvars.py +0 -0
  173. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_vqa_training_lora.py +0 -0
  174. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tests/test_vqa_xvars_videochatgpt.py +0 -0
  175. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/build_sn_vqa_2026_vqa.py +0 -0
  176. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/build_sngar_spotting.py +0 -0
  177. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/build_soccernet_gar.py +0 -0
  178. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  179. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/build_soccernet_gar_vqa.py +0 -0
  180. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/build_xvars_indexes.py +0 -0
  181. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/extract_xvars_clip_features.py +0 -0
  182. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  183. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  184. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/sngar_dataset_card.py +0 -0
  185. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/sngar_events.py +0 -0
  186. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/convert/verify_sngar_spotting.py +0 -0
  187. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/download/download_hf_repo.py +0 -0
  188. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/download/download_osl_hf.py +0 -0
  189. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/download/push_sngar_spotting.py +0 -0
  190. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/download/upload_osl_hf.py +0 -0
  191. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/training/classification.py +0 -0
  192. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/training/localization.py +0 -0
  193. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.dev20}/tools/training/vqa.py +0 -0
  194. {opensportslib-0.3.0.dev18 → opensportslib-0.3.0.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.0.dev18
3
+ Version: 0.3.0.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
@@ -385,6 +385,8 @@ python tools/download/upload_osl_hf.py --repo-id <org/repo> --json-path <local_d
385
385
  ```
386
386
 
387
387
  Downloads are placed under `<output-dir>/<revision>/<split>`.
388
+ For Parquet/WebDataset downloads, an existing `<split>.json` in that directory
389
+ is reused without downloading or converting the split again.
388
390
 
389
391
  ---
390
392
 
@@ -349,6 +349,8 @@ python tools/download/upload_osl_hf.py --repo-id <org/repo> --json-path <local_d
349
349
  ```
350
350
 
351
351
  Downloads are placed under `<output-dir>/<revision>/<split>`.
352
+ For Parquet/WebDataset downloads, an existing `<split>.json` in that directory
353
+ is reused without downloading or converting the split again.
352
354
 
353
355
  ---
354
356
 
@@ -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
 
@@ -211,7 +211,6 @@ def _download_parquet_split_and_convert(
211
211
  progress_cb: ProgressCallback | None = None,
212
212
  is_cancelled: CancelCheck | None = None,
213
213
  ) -> dict[str, Any]:
214
- _, _, snapshot_download = _import_hf_hub()
215
214
  cleaned_repo_id = str(repo_id or "").strip()
216
215
  cleaned_revision = str(revision or "").strip() or "main"
217
216
  cleaned_split = _clean_hf_split(split)
@@ -219,9 +218,36 @@ def _download_parquet_split_and_convert(
219
218
  raise ValueError("repo_id is required.")
220
219
 
221
220
  os.makedirs(output_dir, exist_ok=True)
221
+ output_json_path = Path(output_dir) / f"{cleaned_split}.json"
222
+ if output_json_path.is_file():
223
+ _emit_progress(
224
+ progress_cb,
225
+ f"JSON already exists at {output_json_path}; skipping Parquet/WebDataset download and conversion.",
226
+ )
227
+ return {
228
+ "repo_id": cleaned_repo_id,
229
+ "revision": cleaned_revision,
230
+ "split": cleaned_split,
231
+ "folder_path": cleaned_split,
232
+ "output_dir": output_dir,
233
+ "json_path": str(output_json_path),
234
+ "source": "parquet_split",
235
+ "download_kind": "parquet",
236
+ "downloaded_file_count": 0,
237
+ "download_skipped": True,
238
+ "extracted_media": True,
239
+ "extracted_media_count": 0,
240
+ "hf_source_metadata": {
241
+ "repo_id": cleaned_repo_id,
242
+ "branch": cleaned_revision,
243
+ "split": cleaned_split,
244
+ },
245
+ }
246
+
222
247
  _ensure_not_cancelled(is_cancelled)
223
248
  _emit_progress(progress_cb, f"Downloading Parquet split '{cleaned_split}' from {cleaned_repo_id}@{cleaned_revision}...")
224
249
 
250
+ _, _, snapshot_download = _import_hf_hub()
225
251
  tmp_dir = tempfile.mkdtemp(prefix="hf_parquet_dl_", dir=output_dir)
226
252
  try:
227
253
  snapshot_download(
@@ -235,8 +261,6 @@ def _download_parquet_split_and_convert(
235
261
  _ensure_not_cancelled(is_cancelled)
236
262
 
237
263
  parquet_dataset_dir = Path(tmp_dir) / cleaned_split
238
- output_json_path = Path(output_dir) / f"{cleaned_split}.json"
239
-
240
264
  _emit_progress(progress_cb, f"Converting Parquet split to JSON and extracting media into {output_dir}...")
241
265
  conversion_result = convert_parquet_to_json(
242
266
  dataset_dir=parquet_dataset_dir,
@@ -269,6 +293,7 @@ def _download_parquet_split_and_convert(
269
293
  "json_path": str(output_json_path),
270
294
  "source": "parquet_split",
271
295
  "download_kind": "parquet",
296
+ "download_skipped": False,
272
297
  "num_samples": int(conversion_result.get("num_samples") or 0),
273
298
  "extracted_media": True,
274
299
  "extracted_media_count": int(conversion_result.get("extracted_media_files") or 0),
@@ -410,6 +435,131 @@ def _download_json_path_from_hf(
410
435
  return result
411
436
 
412
437
 
438
+ _PREFERRED_SPLIT_ORDER = ["train", "valid", "val", "validation", "test", "challenge"]
439
+ _NON_SPLIT_JSON_FILES = {"dataset_infos.json", "dataset_dict.json"}
440
+
441
+
442
+ def _sort_splits(splits: set[str]) -> list[str]:
443
+ def _sort_key(name: str) -> tuple[int, str]:
444
+ try:
445
+ rank = _PREFERRED_SPLIT_ORDER.index(name.lower())
446
+ except ValueError:
447
+ rank = len(_PREFERRED_SPLIT_ORDER)
448
+ return (rank, name.lower())
449
+
450
+ return sorted(splits, key=_sort_key)
451
+
452
+
453
+ def list_dataset_branches_on_hf(
454
+ repo_id: str,
455
+ *,
456
+ token: str | None = None,
457
+ ) -> list[str]:
458
+ cleaned_repo_id = str(repo_id or "").strip()
459
+ if not cleaned_repo_id:
460
+ raise ValueError("repo_id is required.")
461
+
462
+ HfApi, _, _ = _import_hf_hub()
463
+ api = HfApi(token=token or None)
464
+ refs = api.list_repo_refs(cleaned_repo_id, repo_type="dataset")
465
+ branch_names = [str(branch.name) for branch in getattr(refs, "branches", [])]
466
+
467
+ unique_names = sorted(set(branch_names))
468
+ if "main" in unique_names:
469
+ unique_names.remove("main")
470
+ return ["main"] + unique_names
471
+ return unique_names
472
+
473
+
474
+ def list_dataset_splits_on_hf(
475
+ repo_id: str,
476
+ revision: str,
477
+ *,
478
+ token: str | None = None,
479
+ ) -> dict[str, Any]:
480
+ cleaned_repo_id = str(repo_id or "").strip()
481
+ cleaned_revision = str(revision or "").strip() or "main"
482
+ if not cleaned_repo_id:
483
+ raise ValueError("repo_id is required.")
484
+
485
+ HfApi, _, _ = _import_hf_hub()
486
+ api = HfApi(token=token or None)
487
+ repo_files = api.list_repo_files(
488
+ cleaned_repo_id,
489
+ revision=cleaned_revision,
490
+ repo_type="dataset",
491
+ )
492
+
493
+ parquet_splits: set[str] = set()
494
+ json_splits: set[str] = set()
495
+ for path in repo_files:
496
+ normalized = _normalize_repo_path(path)
497
+ if "/" in normalized:
498
+ folder, filename = normalized.split("/", 1)
499
+ # Only the canonical Parquet+WebDataset export layout counts as a
500
+ # parquet split (produced by convert_json_to_parquet / expected by
501
+ # convert_parquet_to_json): `{split}/metadata.parquet` plus TAR
502
+ # shards under `{split}/shards/`. A JSON-format dataset can also
503
+ # reference arbitrary `.parquet` media files (e.g. tensor-encoded
504
+ # videos) under a folder that happens to share the split's name,
505
+ # so a loose "any .parquet/.tar anywhere under this folder" check
506
+ # would misclassify those as Parquet+WebDataset splits.
507
+ if folder and (
508
+ filename == "metadata.parquet"
509
+ or (filename.startswith("shards/") and filename.lower().endswith(".tar"))
510
+ ):
511
+ parquet_splits.add(folder)
512
+ elif normalized.lower().endswith(".json") and normalized not in _NON_SPLIT_JSON_FILES:
513
+ json_splits.add(normalized[: -len(".json")])
514
+
515
+ if parquet_splits:
516
+ return {"format": "parquet", "splits": _sort_splits(parquet_splits)}
517
+ if json_splits:
518
+ return {"format": "json", "splits": _sort_splits(json_splits)}
519
+ return {"format": None, "splits": []}
520
+
521
+
522
+ def download_dataset_splits_from_hf(
523
+ repo_id: str,
524
+ revision: str,
525
+ splits: list[str],
526
+ output_dir: str,
527
+ *,
528
+ download_format: str = "parquet",
529
+ dry_run: bool = False,
530
+ token: str | None = None,
531
+ progress_cb: ProgressCallback | None = None,
532
+ is_cancelled: CancelCheck | None = None,
533
+ ) -> list[dict[str, Any]]:
534
+ cleaned_splits = [str(split or "").strip() for split in (splits or [])]
535
+ cleaned_splits = [split for split in cleaned_splits if split]
536
+ if not cleaned_splits:
537
+ raise ValueError("At least one split is required.")
538
+
539
+ total = len(cleaned_splits)
540
+ results: list[dict[str, Any]] = []
541
+ for idx, split in enumerate(cleaned_splits, start=1):
542
+ _ensure_not_cancelled(is_cancelled)
543
+
544
+ def _scoped_progress(message: str, _idx: int = idx, _split: str = split) -> None:
545
+ _emit_progress(progress_cb, f"[{_idx}/{total}] {_split}: {message}")
546
+
547
+ result = download_dataset_split_from_hf(
548
+ repo_id,
549
+ revision,
550
+ split,
551
+ output_dir,
552
+ download_format=download_format,
553
+ dry_run=dry_run,
554
+ token=token,
555
+ progress_cb=_scoped_progress,
556
+ is_cancelled=is_cancelled,
557
+ )
558
+ results.append(result)
559
+
560
+ return results
561
+
562
+
413
563
  def download_dataset_split_from_hf(
414
564
  repo_id: str,
415
565
  revision: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.3.0.dev18
3
+ Version: 0.3.0.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
@@ -385,6 +385,8 @@ python tools/download/upload_osl_hf.py --repo-id <org/repo> --json-path <local_d
385
385
  ```
386
386
 
387
387
  Downloads are placed under `<output-dir>/<revision>/<split>`.
388
+ For Parquet/WebDataset downloads, an existing `<split>.json` in that directory
389
+ is reused without downloading or converting the split again.
388
390
 
389
391
  ---
390
392
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opensportslib"
7
- version = "0.3.0.dev18"
7
+ version = "0.3.0.dev20"
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"
@@ -104,6 +104,19 @@ os.environ.setdefault("OSL_PRETRAINED_WEIGHTS", "0")
104
104
  # Dataset prep
105
105
  # --------------------------------------------------------------------------
106
106
 
107
+ # opensportslib.datasets.classification_dataset.ClassificationDataset
108
+ # hardcodes exclude_labels = ["Unknown", "Dont know"] and drops them from
109
+ # the label space it actually builds (label_map, class weights, etc). If
110
+ # DATA.common.classes / num_classes don't apply the same exclusion, the
111
+ # model is built with the wrong output size and weighted-loss class weights
112
+ # come back the wrong shape ("weight tensor should be defined for all N
113
+ # classes but got shape [N-k]"). Keep this in sync with that hardcoded list.
114
+ _DATASET_EXCLUDED_LABELS = {"Unknown", "Dont know"}
115
+
116
+
117
+ def _exclude_dataset_labels(classes: list[str]) -> list[str]:
118
+ return [c for c in classes if c not in _DATASET_EXCLUDED_LABELS]
119
+
107
120
 
108
121
  def _resolve_label(record: dict) -> str | None:
109
122
  for key in ("label", "action_class", "foul_type", "class", "action"):
@@ -225,8 +238,21 @@ def classification_dataset():
225
238
  split: download_shard_split(repo_id, split, root, revision=revision)
226
239
  for split in ("train", "valid", "test")
227
240
  }
228
- classes = classes_from_osl_json(json.loads(split_paths["test"].read_text(encoding="utf-8")), head="action")
229
- return {"data_root": root, "classes": classes, "split_paths": split_paths}
241
+ classes = _exclude_dataset_labels(
242
+ classes_from_osl_json(json.loads(split_paths["test"].read_text(encoding="utf-8")), head="action")
243
+ )
244
+ # Each split's media is extracted under its own split directory (see
245
+ # download_dataset_split_from_hf's split_output_dir), and the json's
246
+ # "inputs[].path" values (e.g. "train/action_0/clip_0.mp4") are
247
+ # relative to that directory -- NOT to a single shared data_root, so
248
+ # source_path must be resolved per split.
249
+ source_paths = {split: path.parent for split, path in split_paths.items()}
250
+ return {
251
+ "data_root": root,
252
+ "classes": classes,
253
+ "split_paths": split_paths,
254
+ "source_paths": source_paths,
255
+ }
230
256
 
231
257
  # Fallback: known-good, real (gated) dataset -- not sharded, but small
232
258
  # (13 clips), so downloaded and split locally rather than capped.
@@ -246,15 +272,20 @@ def classification_dataset():
246
272
  path.write_text(json.dumps(split_payload, indent=2), encoding="utf-8")
247
273
  split_paths[split] = path
248
274
 
249
- classes = payload["labels"]["action"]["labels"]
275
+ classes = _exclude_dataset_labels(payload["labels"]["action"]["labels"])
250
276
  counts = Counter(item["labels"]["action"]["label"] for item in payload["data"])
251
277
  print(f"Classes ({len(classes)}): {classes}")
252
278
  print(f"Label distribution: {dict(counts)}")
253
279
 
280
+ # All splits share one media root here (processed_videos/<id>.mp4
281
+ # relative to `root`), unlike the sharded branch above.
282
+ source_paths = {split: root for split in split_paths}
283
+
254
284
  return {
255
285
  "data_root": root,
256
286
  "classes": classes,
257
287
  "split_paths": split_paths,
288
+ "source_paths": source_paths,
258
289
  }
259
290
 
260
291
 
@@ -312,7 +343,7 @@ def test_classification_mvnetwork_backbone(classification_dataset, backbone):
312
343
  "splits": {
313
344
  split: {
314
345
  "annotation_path": str(path),
315
- "source_path": str(dataset["data_root"]),
346
+ "source_path": str(dataset["source_paths"][split]),
316
347
  }
317
348
  for split, path in dataset["split_paths"].items()
318
349
  },
@@ -356,7 +387,7 @@ def test_classification_video_mae_huggingface_backend(classification_dataset):
356
387
  "splits": {
357
388
  split: {
358
389
  "annotation_path": str(path),
359
- "source_path": str(dataset["data_root"]),
390
+ "source_path": str(dataset["source_paths"][split]),
360
391
  }
361
392
  for split, path in dataset["split_paths"].items()
362
393
  },
@@ -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)
@@ -607,6 +768,41 @@ def test_download_dataset_split_from_hf_parquet_downloads_split_folder(monkeypat
607
768
  assert result["output_dir"] == str(tmp_path / "dev" / "test")
608
769
  assert result["json_path"] == str(tmp_path / "dev" / "test" / "test.json")
609
770
  assert result["num_samples"] == 3
771
+ assert result["download_skipped"] is False
772
+
773
+
774
+ def test_download_dataset_split_from_hf_parquet_skips_download_when_json_exists(
775
+ monkeypatch, tmp_path
776
+ ):
777
+ output_json_path = tmp_path / "dev" / "test" / "test.json"
778
+ output_json_path.parent.mkdir(parents=True)
779
+ output_json_path.write_text(json.dumps({"data": []}), encoding="utf-8")
780
+ progress_messages = []
781
+
782
+ def _fail_hf_import():
783
+ raise AssertionError("Hugging Face must not be imported for an existing JSON")
784
+
785
+ def _fail_conversion(**kwargs):
786
+ raise AssertionError("Existing JSON must not be converted again")
787
+
788
+ monkeypatch.setattr("opensportslib.tools.hf_transfer._import_hf_hub", _fail_hf_import)
789
+ monkeypatch.setattr("opensportslib.tools.hf_transfer.convert_parquet_to_json", _fail_conversion)
790
+
791
+ result = download_dataset_split_from_hf(
792
+ "OpenSportsLab/repo",
793
+ "dev",
794
+ "test",
795
+ str(tmp_path),
796
+ download_format="parquet",
797
+ progress_cb=progress_messages.append,
798
+ )
799
+
800
+ assert result["json_path"] == str(output_json_path)
801
+ assert result["downloaded_file_count"] == 0
802
+ assert result["download_skipped"] is True
803
+ assert progress_messages == [
804
+ f"JSON already exists at {output_json_path}; skipping Parquet/WebDataset download and conversion."
805
+ ]
610
806
 
611
807
 
612
808
  def test_download_dataset_split_from_hf_json_writes_hf_metadata_on_non_dry_run(monkeypatch, tmp_path):