opensportslib 0.3.0.dev11__tar.gz → 0.3.0.dev13__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 (189) hide show
  1. {opensportslib-0.3.0.dev11/opensportslib.egg-info → opensportslib-0.3.0.dev13}/PKG-INFO +8 -2
  2. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/README.md +7 -1
  3. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/optimizer/builder.py +10 -0
  4. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/backbones/builder.py +1 -1
  5. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13/opensportslib.egg-info}/PKG-INFO +8 -2
  6. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib.egg-info/SOURCES.txt +5 -0
  7. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/pyproject.toml +1 -1
  8. opensportslib-0.3.0.dev13/tests/release/__init__.py +0 -0
  9. opensportslib-0.3.0.dev13/tests/release/_release_common.py +372 -0
  10. opensportslib-0.3.0.dev13/tests/release/test_classification_release.py +413 -0
  11. opensportslib-0.3.0.dev13/tests/release/test_localization_release.py +386 -0
  12. opensportslib-0.3.0.dev13/tests/release/test_vqa_release.py +188 -0
  13. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/LICENSE +0 -0
  14. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/LICENSE-COMMERCIAL +0 -0
  15. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/MANIFEST.in +0 -0
  16. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/examples/quickstart/basic_classification.py +0 -0
  17. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/examples/quickstart/basic_localization.py +0 -0
  18. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/examples/quickstart/basic_vqa.py +0 -0
  19. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/__init__.py +0 -0
  20. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/adaptation/__init__.py +0 -0
  21. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/adaptation/spotta.py +0 -0
  22. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/apis/__init__.py +0 -0
  23. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/apis/base_task_model.py +0 -0
  24. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/apis/classification.py +0 -0
  25. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/apis/localization.py +0 -0
  26. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/apis/vqa.py +0 -0
  27. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/cli.py +0 -0
  28. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/classification/default.yaml +0 -0
  29. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
  30. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
  31. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/classification/video.yaml +0 -0
  32. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/default.yaml +0 -0
  33. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
  34. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/localization/default.yaml +0 -0
  35. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/localization/e2e_spotta.yaml +0 -0
  36. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/localization/h5_header_distance.yaml +0 -0
  37. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/localization/h5_header_skeleton.yaml +0 -0
  38. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
  39. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/localization/tracking_action_spotting.yaml +0 -0
  40. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/localization/video_dali.yaml +0 -0
  41. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/localization/video_ocv.yaml +0 -0
  42. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/vqa/default.yaml +0 -0
  43. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/vqa/qwen.yaml +0 -0
  44. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/vqa/qwen3_vl_native.yaml +0 -0
  45. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/vqa/qwen_lora.yaml +0 -0
  46. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/vqa/qwen_sngar_frames.yaml +0 -0
  47. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/configs/vqa/xvars.yaml +0 -0
  48. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/__init__.py +0 -0
  49. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/__init__.py +0 -0
  50. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/accessors.py +0 -0
  51. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/conflicts.py +0 -0
  52. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/loader.py +0 -0
  53. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/migrate.py +0 -0
  54. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/migrations/__init__.py +0 -0
  55. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  56. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/runtime_adapter.py +0 -0
  57. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/schema.py +0 -0
  58. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/schemas/__init__.py +0 -0
  59. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  60. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  61. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/config/validate.py +0 -0
  62. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/loss/__init__.py +0 -0
  63. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/loss/builder.py +0 -0
  64. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/loss/calf.py +0 -0
  65. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/loss/ce.py +0 -0
  66. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/loss/combine.py +0 -0
  67. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/loss/nll.py +0 -0
  68. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/optimizer/__init__.py +0 -0
  69. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  70. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/scheduler/__init__.py +0 -0
  71. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/scheduler/builder.py +0 -0
  72. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/trainer/__init__.py +0 -0
  73. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/trainer/classification_trainer.py +0 -0
  74. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/trainer/localization_trainer.py +0 -0
  75. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/trainer/vqa_trainer.py +0 -0
  76. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/checkpoint.py +0 -0
  77. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/config.py +0 -0
  78. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/config_normalize.py +0 -0
  79. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/data.py +0 -0
  80. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/ddp.py +0 -0
  81. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/default_args.py +0 -0
  82. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/hf_runtime.py +0 -0
  83. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/lightning.py +0 -0
  84. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/load_annotations.py +0 -0
  85. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/seed.py +0 -0
  86. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/video_processing.py +0 -0
  87. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/core/utils/wandb.py +0 -0
  88. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/datasets/__init__.py +0 -0
  89. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/datasets/builder.py +0 -0
  90. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/datasets/classification_dataset.py +0 -0
  91. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/datasets/localization_dataset.py +0 -0
  92. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/datasets/utils/__init__.py +0 -0
  93. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/datasets/utils/h5_tracking.py +0 -0
  94. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/datasets/utils/tracking.py +0 -0
  95. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/datasets/vqa_dataset.py +0 -0
  96. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/legacy_config/classification.yaml +0 -0
  97. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  98. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  99. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  100. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/legacy_config/localization.yaml +0 -0
  101. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  102. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  103. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/metrics/classification_metric.py +0 -0
  104. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/metrics/localization_metric.py +0 -0
  105. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/metrics/vqa_metric.py +0 -0
  106. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/__init__.py +0 -0
  107. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/contextaware.py +0 -0
  108. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/e2e.py +0 -0
  109. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/learnablepooling.py +0 -0
  110. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/qwen_vl_native.py +0 -0
  111. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/qwen_xvars.py +0 -0
  112. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/rule_based.py +0 -0
  113. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/tracking.py +0 -0
  114. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/vars.py +0 -0
  115. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/video.py +0 -0
  116. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/video_chatgpt_compat.py +0 -0
  117. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/video_mae.py +0 -0
  118. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/base/xvars_videochatgpt.py +0 -0
  119. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/builder.py +0 -0
  120. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/heads/builder.py +0 -0
  121. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/neck/builder.py +0 -0
  122. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/common.py +0 -0
  123. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/impl/__init__.py +0 -0
  124. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/impl/asformer.py +0 -0
  125. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/impl/calf.py +0 -0
  126. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/impl/gsm.py +0 -0
  127. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/impl/gtad.py +0 -0
  128. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/impl/tsm.py +0 -0
  129. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/litebase.py +0 -0
  130. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/modules.py +0 -0
  131. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/shift.py +0 -0
  132. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/utils.py +0 -0
  133. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/vqa_prediction_priors.py +0 -0
  134. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/vqa_prompting.py +0 -0
  135. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/models/utils/xvars_clip_index.py +0 -0
  136. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/setup/setup.py +0 -0
  137. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/tools/__init__.py +0 -0
  138. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/tools/_common.py +0 -0
  139. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/tools/hf_transfer.py +0 -0
  140. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/tools/osl_json_to_parquet.py +0 -0
  141. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib/tools/parquet_to_osl_json.py +0 -0
  142. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib.egg-info/dependency_links.txt +0 -0
  143. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib.egg-info/entry_points.txt +0 -0
  144. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib.egg-info/requires.txt +0 -0
  145. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/opensportslib.egg-info/top_level.txt +0 -0
  146. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/scripts/run_h5_header_rule_inference.py +0 -0
  147. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/setup.cfg +0 -0
  148. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/conftest.py +0 -0
  149. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_classification_dataset_paths.py +0 -0
  150. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_classification_trainer_dataloader.py +0 -0
  151. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_config_architecture.py +0 -0
  152. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_config_split_override_sync.py +0 -0
  153. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_config_utils_smoke.py +0 -0
  154. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_conversion_tools.py +0 -0
  155. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_extract_xvars_features.py +0 -0
  156. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_h5_header_rule_spotter.py +0 -0
  157. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_h5_header_skeleton_spotter.py +0 -0
  158. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_h5_tracking_dataset.py +0 -0
  159. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_hf_transfer_tools.py +0 -0
  160. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_localization_dali_filenames.py +0 -0
  161. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_localization_hf_backend_override.py +0 -0
  162. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_localization_intervals.py +0 -0
  163. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_package_smoke.py +0 -0
  164. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_pretrained_config_merge_policy.py +0 -0
  165. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_public_apis_smoke.py +0 -0
  166. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_setup_cli.py +0 -0
  167. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_spotta_e2e.py +0 -0
  168. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_subset_train_infer_integration.py +0 -0
  169. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_task_model_api_contract.py +0 -0
  170. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_vqa_api.py +0 -0
  171. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_vqa_metrics_semantic.py +0 -0
  172. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_vqa_qwen_xvars.py +0 -0
  173. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_vqa_training_lora.py +0 -0
  174. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tests/test_vqa_xvars_videochatgpt.py +0 -0
  175. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/convert/build_sn_vqa_2026_vqa.py +0 -0
  176. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/convert/build_soccernet_gar.py +0 -0
  177. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  178. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/convert/build_soccernet_gar_vqa.py +0 -0
  179. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/convert/build_xvars_indexes.py +0 -0
  180. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/convert/extract_xvars_clip_features.py +0 -0
  181. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  182. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  183. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/download/download_hf_repo.py +0 -0
  184. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/download/download_osl_hf.py +0 -0
  185. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/download/upload_osl_hf.py +0 -0
  186. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/training/classification.py +0 -0
  187. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/training/localization.py +0 -0
  188. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/tools/training/vqa.py +0 -0
  189. {opensportslib-0.3.0.dev11 → opensportslib-0.3.0.dev13}/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.dev11
3
+ Version: 0.3.0.dev13
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
@@ -76,7 +76,7 @@ OpenSportsLib is designed for **researchers, ML engineers, and sports analytics
76
76
  ### Create conda env
77
77
 
78
78
  ```bash
79
- conda create -n osl python=3.12 pip
79
+ conda create -n osl python=3.12 pip -y
80
80
  conda activate osl
81
81
  ```
82
82
 
@@ -92,6 +92,12 @@ pip install opensportslib
92
92
  pip install --pre opensportslib
93
93
  ```
94
94
 
95
+ ### Source development version
96
+
97
+ ```bash
98
+ pip install -e .
99
+ ```
100
+
95
101
  ### Setup Environment (PyTorch, CUDA aware & Optional Dependencies)
96
102
  ```bash
97
103
  # Install PyTorch (CPU/GPU auto-detected)
@@ -40,7 +40,7 @@ OpenSportsLib is designed for **researchers, ML engineers, and sports analytics
40
40
  ### Create conda env
41
41
 
42
42
  ```bash
43
- conda create -n osl python=3.12 pip
43
+ conda create -n osl python=3.12 pip -y
44
44
  conda activate osl
45
45
  ```
46
46
 
@@ -56,6 +56,12 @@ pip install opensportslib
56
56
  pip install --pre opensportslib
57
57
  ```
58
58
 
59
+ ### Source development version
60
+
61
+ ```bash
62
+ pip install -e .
63
+ ```
64
+
59
65
  ### Setup Environment (PyTorch, CUDA aware & Optional Dependencies)
60
66
  ```bash
61
67
  # Install PyTorch (CPU/GPU auto-detected)
@@ -39,4 +39,14 @@ def build_optimizer(parameters, cfg, default_args=None):
39
39
  weight_decay=cfg.weight_decay,
40
40
  amsgrad=cfg.amsgrad,
41
41
  )
42
+ elif cfg.type == "SGD":
43
+ optimizer = torch.optim.SGD(
44
+ parameters,
45
+ lr=cfg.lr,
46
+ momentum=getattr(cfg, "momentum", 0.0),
47
+ weight_decay=getattr(cfg, "weight_decay", 0.0),
48
+ nesterov=getattr(cfg, "nesterov", False),
49
+ )
50
+ else:
51
+ raise ValueError(f"Unsupported optimizer type: {cfg.type}")
42
52
  return optimizer
@@ -253,7 +253,7 @@ class RegnetyExtractFeatures(BaseExtractFeatures):
253
253
  self._feat_dim = feat_dim
254
254
 
255
255
 
256
- class ResnetExtractFeatures(nn.Module):
256
+ class ResnetExtractFeatures(BaseExtractFeatures):
257
257
  """Feature extractor which is based on the "resnet" models of the torchvision models.
258
258
  The model is adapted for this task by adding temporal shift modules.
259
259
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.3.0.dev11
3
+ Version: 0.3.0.dev13
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
@@ -76,7 +76,7 @@ OpenSportsLib is designed for **researchers, ML engineers, and sports analytics
76
76
  ### Create conda env
77
77
 
78
78
  ```bash
79
- conda create -n osl python=3.12 pip
79
+ conda create -n osl python=3.12 pip -y
80
80
  conda activate osl
81
81
  ```
82
82
 
@@ -92,6 +92,12 @@ pip install opensportslib
92
92
  pip install --pre opensportslib
93
93
  ```
94
94
 
95
+ ### Source development version
96
+
97
+ ```bash
98
+ pip install -e .
99
+ ```
100
+
95
101
  ### Setup Environment (PyTorch, CUDA aware & Optional Dependencies)
96
102
  ```bash
97
103
  # Install PyTorch (CPU/GPU auto-detected)
@@ -165,6 +165,11 @@ tests/test_vqa_metrics_semantic.py
165
165
  tests/test_vqa_qwen_xvars.py
166
166
  tests/test_vqa_training_lora.py
167
167
  tests/test_vqa_xvars_videochatgpt.py
168
+ tests/release/__init__.py
169
+ tests/release/_release_common.py
170
+ tests/release/test_classification_release.py
171
+ tests/release/test_localization_release.py
172
+ tests/release/test_vqa_release.py
168
173
  tools/convert/build_sn_vqa_2026_vqa.py
169
174
  tools/convert/build_soccernet_gar.py
170
175
  tools/convert/build_soccernet_gar_action_spotting.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opensportslib"
7
- version = "0.3.0.dev11"
7
+ version = "0.3.0.dev13"
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"
File without changes
@@ -0,0 +1,372 @@
1
+ """Shared helpers for the release-verification test suite (tests/release/).
2
+
3
+ These tests are NOT part of the regular `pytest tests/test_*.py` contract.
4
+ They download real datasets from the OpenSportsLab Hugging Face org
5
+ (some of them large) and run real training/inference/evaluation on GPU.
6
+ They exist to be run manually after a big release to confirm that training
7
+ still works end-to-end for every model family the library ships.
8
+
9
+ See tests/release/README.md for the full contract, prerequisites, and
10
+ invocation examples.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import importlib.util
16
+ import os
17
+ from copy import deepcopy
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ import pytest
22
+
23
+ REPO_ROOT = Path(__file__).resolve().parents[2]
24
+
25
+ RELEASE_ENV_FLAG = "RUN_OSL_RELEASE_TESTS"
26
+
27
+ # Where materialized configs / run outputs (checkpoints, logs, predictions)
28
+ # are cached. Override with OSL_RELEASE_CACHE_DIR to point at a disk with
29
+ # more room.
30
+ CACHE_ROOT = Path(
31
+ os.environ.get("OSL_RELEASE_CACHE_DIR", str(REPO_ROOT / ".release_test_cache"))
32
+ ).expanduser()
33
+ CONFIG_DIR = CACHE_ROOT / "configs"
34
+ OUTPUT_DIR = CACHE_ROOT / "outputs"
35
+
36
+ # Where datasets are downloaded to / read from. Deliberately independent of
37
+ # CACHE_ROOT: point OSL_RELEASE_DATA_DIR at a folder that already hosts these
38
+ # datasets (a shared drive, a previous run's download, ...) and nothing gets
39
+ # re-downloaded or overwritten -- snapshot_dataset()/download_files() below
40
+ # pass this straight through as `local_dir` to huggingface_hub, which only
41
+ # fetches files that are missing or whose content has changed (standard HF
42
+ # Hub behavior; see https://huggingface.co/docs/huggingface_hub/guides/download).
43
+ # Defaults to a subdirectory of CACHE_ROOT when unset.
44
+ DATA_DIR = Path(
45
+ os.environ.get("OSL_RELEASE_DATA_DIR", str(CACHE_ROOT / "data"))
46
+ ).expanduser()
47
+
48
+ for _d in (DATA_DIR, CONFIG_DIR, OUTPUT_DIR):
49
+ _d.mkdir(parents=True, exist_ok=True)
50
+
51
+
52
+ # --------------------------------------------------------------------------
53
+ # Opt-in gate
54
+ # --------------------------------------------------------------------------
55
+
56
+
57
+ def release_tests_enabled() -> bool:
58
+ return os.environ.get(RELEASE_ENV_FLAG, "") == "1"
59
+
60
+
61
+ def require_release_enabled() -> None:
62
+ """Call at the top of every release test / fixture.
63
+
64
+ Keeps these tests from ever running by accident (plain `pytest tests/`,
65
+ an IDE "run all tests" button, a CI job someone forgot to scope) even
66
+ though pytest can discover them. The flat `pytest tests/test_*.py`
67
+ command from AGENTS.md never reaches this directory in the first place
68
+ since it's a shell glob, not a recursive pattern — this is the second,
69
+ explicit line of defense for anyone running `pytest tests/` directly.
70
+ """
71
+ if not release_tests_enabled():
72
+ pytest.skip(
73
+ f"Release verification tests are opt-in. "
74
+ f"Set {RELEASE_ENV_FLAG}=1 to run them (see tests/release/README.md). "
75
+ f"They download real datasets and run real training; do not enable "
76
+ f"them in routine CI."
77
+ )
78
+
79
+
80
+ # --------------------------------------------------------------------------
81
+ # Tunables (env-overridable so a maintainer can scale a run up or down)
82
+ # --------------------------------------------------------------------------
83
+
84
+
85
+ def _env_int(name: str, default: int) -> int:
86
+ raw = os.environ.get(name)
87
+ if raw is None or raw == "":
88
+ return default
89
+ return int(raw)
90
+
91
+
92
+ def epochs_for(default: int) -> int:
93
+ """TRAIN.epochs override. OSL_RELEASE_EPOCHS=0 means 'keep config default'."""
94
+ value = _env_int("OSL_RELEASE_EPOCHS", default)
95
+ return default if value == 0 else value
96
+
97
+
98
+ def max_items_for(env_name: str, default: int | None) -> int | None:
99
+ """Generic dataset-subset-size override. 0 (or 'all') means 'download everything'."""
100
+ raw = os.environ.get(env_name)
101
+ if raw is None or raw == "":
102
+ return default
103
+ if raw.strip().lower() == "all":
104
+ return None
105
+ value = int(raw)
106
+ return None if value == 0 else value
107
+
108
+
109
+ def hf_token() -> str | None:
110
+ return os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN")
111
+
112
+
113
+ def optional_module_available(module_name: str) -> bool:
114
+ try:
115
+ return importlib.util.find_spec(module_name) is not None
116
+ except (ImportError, ValueError):
117
+ return False
118
+
119
+
120
+ # --------------------------------------------------------------------------
121
+ # Hugging Face dataset access helpers
122
+ # --------------------------------------------------------------------------
123
+
124
+
125
+ def _hf_api():
126
+ from huggingface_hub import HfApi
127
+
128
+ return HfApi(token=hf_token())
129
+
130
+
131
+ def repo_accessible(repo_id: str, repo_type: str = "dataset") -> bool:
132
+ """True if we can read repo metadata (i.e. it's public or we're authorized).
133
+
134
+ Distinguishes "gated/private, no access" (returns False -> caller should
135
+ skip) from genuine infrastructure problems (network down, HF outage),
136
+ which are re-raised so the test fails loudly instead of silently skipping.
137
+ """
138
+ from huggingface_hub.utils import GatedRepoError, RepositoryNotFoundError
139
+ from requests.exceptions import ConnectionError as RequestsConnectionError
140
+
141
+ try:
142
+ _hf_api().repo_info(repo_id, repo_type=repo_type)
143
+ return True
144
+ except (GatedRepoError, RepositoryNotFoundError):
145
+ return False
146
+ except RequestsConnectionError:
147
+ raise
148
+
149
+
150
+ def require_repo_access(repo_id: str, repo_type: str = "dataset") -> None:
151
+ if not repo_accessible(repo_id, repo_type=repo_type):
152
+ pytest.skip(
153
+ f"No access to {repo_id!r} on Hugging Face. It may be gated — "
154
+ f"request access at https://huggingface.co/datasets/{repo_id} and "
155
+ f"export HF_TOKEN (or HUGGINGFACE_TOKEN) for an account that has "
156
+ f"been granted access, then re-run."
157
+ )
158
+
159
+
160
+ def list_repo_files(repo_id: str, repo_type: str = "dataset") -> list[str]:
161
+ """List a repo's files with a single cheap API call -- use this to find
162
+ what you actually need (e.g. small annotation JSONs) before downloading
163
+ anything, rather than snapshot-downloading an entire many-file repo.
164
+ """
165
+ return list(_hf_api().list_repo_files(repo_id, repo_type=repo_type))
166
+
167
+
168
+ def repo_is_populated(repo_id: str, repo_type: str = "dataset", min_files: int = 2, revision: str = "main") -> bool:
169
+ """Some OpenSportsLab dataset repos have nothing but a placeholder (just
170
+ a .gitattributes / README) on their default branch while the real data
171
+ lives on a named branch (see prefer_osl_ready_dataset() below) -- always
172
+ pass the branch you actually intend to read from. Treat a repo/revision
173
+ with no real files as "not yet available" rather than crashing on an
174
+ empty split.
175
+ """
176
+ files = _hf_api().list_repo_files(repo_id, repo_type=repo_type, revision=revision)
177
+ real_files = [f for f in files if f not in (".gitattributes", "README.md")]
178
+ return len(real_files) >= min_files
179
+
180
+
181
+ def require_repo_populated(repo_id: str, repo_type: str = "dataset", min_files: int = 2, revision: str = "main") -> None:
182
+ if not repo_is_populated(repo_id, repo_type=repo_type, min_files=min_files, revision=revision):
183
+ pytest.skip(
184
+ f"{repo_id!r}@{revision} does not have data uploaded yet. This "
185
+ f"test is ready to run as soon as the dataset/branch is "
186
+ f"published — re-run once it is."
187
+ )
188
+
189
+
190
+ # --------------------------------------------------------------------------
191
+ # OSL-ready (sharded) datasets
192
+ #
193
+ # https://huggingface.co/collections/OpenSportsLab/osl-ready-datasets pins
194
+ # the datasets the org publishes as parquet + webdataset shards (a handful
195
+ # of large files per split) rather than one file per clip/game -- always
196
+ # prefer these when they're populated. Use prefer_osl_ready_dataset() to try
197
+ # a collection dataset first and fall back to a known-good loose-file
198
+ # dataset only while the sharded one isn't published yet.
199
+ #
200
+ # Important: for every OSL-ready repo observed so far, the *default* ("main")
201
+ # branch is an empty placeholder -- the actual shards live on named branches
202
+ # (e.g. "224p", "720p", "ResNET_PCA512", "224p-2024"). Always pass the
203
+ # specific revision you want; there is no sensible repo-wide default.
204
+ # --------------------------------------------------------------------------
205
+
206
+
207
+ def download_shard_split(repo_id: str, split: str, output_dir: Path, *, revision: str) -> Path:
208
+ """Download one split of a parquet/webdataset-shard OSL dataset and
209
+ convert it to a local OSL v2 JSON with media extracted alongside it.
210
+
211
+ Thin wrapper around opensportslib.tools.hf_transfer.
212
+ download_dataset_split_from_hf(..., download_format="parquet"), which
213
+ itself does snapshot_download(allow_patterns=[f"{split}/*"]) -- for a
214
+ sharded dataset that's a handful of `metadata.parquet` /
215
+ `shard_manifest.parquet` / `shards/shard-*.tar` files (see
216
+ opensportslib/tools/parquet_to_osl_json.py for the exact expected
217
+ layout), not one request per sample, regardless of how many samples the
218
+ split has.
219
+ """
220
+ from opensportslib.tools.hf_transfer import download_dataset_split_from_hf
221
+
222
+ output_dir.mkdir(parents=True, exist_ok=True)
223
+ result = download_dataset_split_from_hf(
224
+ repo_id,
225
+ revision,
226
+ split,
227
+ str(output_dir),
228
+ download_format="parquet",
229
+ token=hf_token(),
230
+ progress_cb=lambda msg: report_step(f"[{repo_id}@{revision}:{split}] {msg}"),
231
+ )
232
+ return Path(result["json_path"])
233
+
234
+
235
+ def prefer_osl_ready_dataset(
236
+ primary: str, fallback: str | None, *, primary_revision: str
237
+ ) -> tuple[str, str, bool]:
238
+ """Prefer `primary`@`primary_revision` (an OSL-ready/sharded dataset
239
+ branch) if it's populated; otherwise fall back to `fallback` (a
240
+ known-good, currently-populated but non-sharded dataset on its default
241
+ branch) and say why. Returns (repo_id, revision, is_sharded).
242
+
243
+ If `fallback` is None and `primary` isn't populated, skips the test --
244
+ use this when there's no non-sharded alternative worth falling back to.
245
+ """
246
+ if repo_is_populated(primary, revision=primary_revision):
247
+ return primary, primary_revision, True
248
+ if fallback is None:
249
+ require_repo_populated(primary, revision=primary_revision) # raises pytest.skip
250
+ report_step(
251
+ f"{primary!r}@{primary_revision} (OSL-ready/sharded) is not populated "
252
+ f"yet -- falling back to {fallback!r}. Re-run once {primary!r}@"
253
+ f"{primary_revision} is published to automatically switch to the "
254
+ f"sharded version."
255
+ )
256
+ return fallback, "main", False
257
+
258
+
259
+ def snapshot_dataset(
260
+ repo_id: str,
261
+ local_dir: Path,
262
+ *,
263
+ allow_patterns: list[str] | None = None,
264
+ ) -> Path:
265
+ """Download (or update) a full dataset repo, or a pattern-restricted
266
+ subset of it, into local_dir. Safe to call repeatedly (resumable)."""
267
+ from huggingface_hub import snapshot_download
268
+
269
+ local_dir.mkdir(parents=True, exist_ok=True)
270
+ snapshot_download(
271
+ repo_id=repo_id,
272
+ repo_type="dataset",
273
+ local_dir=str(local_dir),
274
+ token=hf_token(),
275
+ allow_patterns=allow_patterns,
276
+ )
277
+ return local_dir
278
+
279
+
280
+ def download_files(repo_id: str, filenames: list[str], local_dir: Path) -> list[Path]:
281
+ from huggingface_hub import hf_hub_download
282
+
283
+ local_dir.mkdir(parents=True, exist_ok=True)
284
+ out = []
285
+ for filename in filenames:
286
+ path = hf_hub_download(
287
+ repo_id=repo_id,
288
+ repo_type="dataset",
289
+ filename=filename,
290
+ local_dir=str(local_dir),
291
+ token=hf_token(),
292
+ )
293
+ out.append(Path(path))
294
+ return out
295
+
296
+
297
+ # --------------------------------------------------------------------------
298
+ # Canonical config materialization
299
+ #
300
+ # opensportslib/configs/<task>/<name>.yaml files are NOT self-contained: the
301
+ # library layers them on top of opensportslib/configs/default.yaml and
302
+ # opensportslib/configs/<task>/default.yaml at load time (see
303
+ # opensportslib/core/config/loader.py::_compose_yaml_layers), but only when
304
+ # the path is physically inside opensportslib/configs/<task>/. We reuse the
305
+ # library's own composer to get the real, maintained defaults, apply our
306
+ # dataset/run overrides on top in Python, then write the fully-resolved
307
+ # result out to CONFIG_DIR (outside opensportslib/configs/) so it loads as a
308
+ # plain standalone config.
309
+ # --------------------------------------------------------------------------
310
+
311
+
312
+ def _deep_merge(base: Any, override: Any) -> Any:
313
+ if not isinstance(base, dict) or not isinstance(override, dict):
314
+ return deepcopy(override)
315
+ merged = deepcopy(base)
316
+ for key, value in override.items():
317
+ merged[key] = _deep_merge(merged.get(key), value) if key in merged else deepcopy(value)
318
+ return merged
319
+
320
+
321
+ def materialize_config(task: str, name: str, overrides: dict, *, out_name: str | None = None) -> str:
322
+ """Load opensportslib/configs/<task>/<name>.yaml with its real defaults
323
+ applied, deep-merge `overrides` on top, write the result under
324
+ CONFIG_DIR, and return the path to the materialized file.
325
+ """
326
+ from opensportslib.core.config import load_config, save_config
327
+
328
+ canonical_path = REPO_ROOT / "opensportslib" / "configs" / task / f"{name}.yaml"
329
+ if not canonical_path.is_file():
330
+ raise FileNotFoundError(f"No such canonical config: {canonical_path}")
331
+
332
+ base = load_config(str(canonical_path), as_namespace=False, validate=False)
333
+ merged = _deep_merge(base, overrides)
334
+
335
+ out_path = CONFIG_DIR / (out_name or f"{task}_{name}.yaml")
336
+ save_config(merged, out_path)
337
+ return str(out_path)
338
+
339
+
340
+ def system_block(run_name: str, *, gpu_count: int = 1) -> dict:
341
+ """Standard SYSTEM override pointing checkpoints/logs at OUTPUT_DIR."""
342
+ run_dir = OUTPUT_DIR / run_name
343
+ return {
344
+ "SYSTEM": {
345
+ "paths": {
346
+ "save_dir": str(run_dir / "checkpoints"),
347
+ "work_dir": str(run_dir),
348
+ "log_dir": str(run_dir / "logs"),
349
+ },
350
+ "device": "auto",
351
+ "gpu": {"count": gpu_count, "id": 0},
352
+ "reproducibility": {"use_seed": True, "seed": 0},
353
+ }
354
+ }
355
+
356
+
357
+ def report_step(label: str) -> None:
358
+ print(f"\n=== {label} ===", flush=True)
359
+
360
+
361
+ def classes_from_osl_json(payload: dict, *, head: str | None = None) -> list[str]:
362
+ """Extract a single_label head's class list from an OSL v2 JSON payload.
363
+ Defaults to the first label head found (fine for datasets with exactly
364
+ one classification/event head, which is the common case here); pass
365
+ `head` explicitly for multi-head datasets (e.g. OSL-XFoul's `action` +
366
+ `offence`).
367
+ """
368
+ labels = payload.get("labels") or {}
369
+ if not labels:
370
+ raise ValueError("No 'labels' block in this OSL v2 payload.")
371
+ key = head or next(iter(labels))
372
+ return list(labels[key]["labels"])