opensportslib 0.3.1.dev14__tar.gz → 0.3.1.dev15__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 (206) hide show
  1. {opensportslib-0.3.1.dev14/opensportslib.egg-info → opensportslib-0.3.1.dev15}/PKG-INFO +1 -1
  2. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/adaptation/spotta.py +4 -38
  3. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/localization/e2e_spotta.yaml +1 -10
  4. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/datasets/localization_dataset.py +42 -4
  5. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15/opensportslib.egg-info}/PKG-INFO +1 -1
  6. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib.egg-info/SOURCES.txt +0 -1
  7. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/pyproject.toml +1 -1
  8. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_localization_intervals.py +20 -0
  9. opensportslib-0.3.1.dev14/tests/test_spotta_e2e.py +0 -285
  10. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/LICENSE +0 -0
  11. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/LICENSE-COMMERCIAL +0 -0
  12. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/MANIFEST.in +0 -0
  13. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/README.md +0 -0
  14. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/examples/quickstart/basic_classification.py +0 -0
  15. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/examples/quickstart/basic_localization.py +0 -0
  16. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/examples/quickstart/basic_vqa.py +0 -0
  17. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/__init__.py +0 -0
  18. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/adaptation/__init__.py +0 -0
  19. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/apis/__init__.py +0 -0
  20. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/apis/base_task_model.py +0 -0
  21. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/apis/classification.py +0 -0
  22. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/apis/config.py +0 -0
  23. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/apis/configuration.py +0 -0
  24. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/apis/localization.py +0 -0
  25. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/apis/vqa.py +0 -0
  26. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/cli.py +0 -0
  27. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/classification/default.yaml +0 -0
  28. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
  29. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
  30. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/classification/video.yaml +0 -0
  31. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/default.yaml +0 -0
  32. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
  33. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/localization/default.yaml +0 -0
  34. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/localization/h5_header_distance.yaml +0 -0
  35. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/localization/h5_header_skeleton.yaml +0 -0
  36. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
  37. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/localization/tracking_action_spotting.yaml +0 -0
  38. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/localization/video_dali.yaml +0 -0
  39. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/localization/video_ocv.yaml +0 -0
  40. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/vqa/default.yaml +0 -0
  41. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/vqa/qwen.yaml +0 -0
  42. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/vqa/qwen3_vl_native.yaml +0 -0
  43. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/vqa/qwen_lora.yaml +0 -0
  44. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/vqa/qwen_sngar_frames.yaml +0 -0
  45. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/configs/vqa/xvars.yaml +0 -0
  46. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/__init__.py +0 -0
  47. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/__init__.py +0 -0
  48. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/accessors.py +0 -0
  49. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/conflicts.py +0 -0
  50. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/editable.py +0 -0
  51. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/loader.py +0 -0
  52. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/migrate.py +0 -0
  53. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/migrations/__init__.py +0 -0
  54. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  55. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/rule_variants.py +0 -0
  56. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/runtime_adapter.py +0 -0
  57. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/schema.py +0 -0
  58. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/schemas/__init__.py +0 -0
  59. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  60. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  61. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/config/validate.py +0 -0
  62. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/loss/__init__.py +0 -0
  63. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/loss/builder.py +0 -0
  64. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/loss/calf.py +0 -0
  65. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/loss/ce.py +0 -0
  66. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/loss/combine.py +0 -0
  67. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/loss/nll.py +0 -0
  68. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/optimizer/__init__.py +0 -0
  69. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/optimizer/builder.py +0 -0
  70. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  71. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/scheduler/__init__.py +0 -0
  72. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/scheduler/builder.py +0 -0
  73. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/trainer/__init__.py +0 -0
  74. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/trainer/classification_trainer.py +0 -0
  75. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/trainer/localization_trainer.py +0 -0
  76. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/trainer/vqa_trainer.py +0 -0
  77. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/checkpoint.py +0 -0
  78. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/config.py +0 -0
  79. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/config_normalize.py +0 -0
  80. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/data.py +0 -0
  81. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/ddp.py +0 -0
  82. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/default_args.py +0 -0
  83. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/direct_video.py +0 -0
  84. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/hf_runtime.py +0 -0
  85. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/lightning.py +0 -0
  86. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/load_annotations.py +0 -0
  87. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/seed.py +0 -0
  88. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/video_processing.py +0 -0
  89. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/core/utils/wandb.py +0 -0
  90. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/datasets/__init__.py +0 -0
  91. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/datasets/builder.py +0 -0
  92. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/datasets/classification_dataset.py +0 -0
  93. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/datasets/utils/__init__.py +0 -0
  94. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/datasets/utils/h5_tracking.py +0 -0
  95. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/datasets/utils/tracking.py +0 -0
  96. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/datasets/vqa_dataset.py +0 -0
  97. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/legacy_config/classification.yaml +0 -0
  98. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  99. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  100. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  101. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/legacy_config/localization.yaml +0 -0
  102. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  103. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  104. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/metrics/classification_metric.py +0 -0
  105. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/metrics/localization_metric.py +0 -0
  106. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/metrics/vqa_metric.py +0 -0
  107. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/__init__.py +0 -0
  108. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/backbones/builder.py +0 -0
  109. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/contextaware.py +0 -0
  110. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/e2e.py +0 -0
  111. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/learnablepooling.py +0 -0
  112. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/qwen_vl_native.py +0 -0
  113. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/qwen_xvars.py +0 -0
  114. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/rule_based.py +0 -0
  115. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/tracking.py +0 -0
  116. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/vars.py +0 -0
  117. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/video.py +0 -0
  118. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/video_chatgpt_compat.py +0 -0
  119. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/video_mae.py +0 -0
  120. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/base/xvars_videochatgpt.py +0 -0
  121. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/builder.py +0 -0
  122. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/heads/builder.py +0 -0
  123. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/neck/builder.py +0 -0
  124. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/common.py +0 -0
  125. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/impl/__init__.py +0 -0
  126. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/impl/asformer.py +0 -0
  127. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/impl/calf.py +0 -0
  128. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/impl/gsm.py +0 -0
  129. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/impl/gtad.py +0 -0
  130. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/impl/tsm.py +0 -0
  131. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/litebase.py +0 -0
  132. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/modules.py +0 -0
  133. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/shift.py +0 -0
  134. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/utils.py +0 -0
  135. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/vqa_prediction_priors.py +0 -0
  136. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/vqa_prompting.py +0 -0
  137. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/models/utils/xvars_clip_index.py +0 -0
  138. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/remote_registry.py +0 -0
  139. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/setup/setup.py +0 -0
  140. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/tools/__init__.py +0 -0
  141. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/tools/_common.py +0 -0
  142. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/tools/hf_transfer.py +0 -0
  143. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/tools/osl_json_to_parquet.py +0 -0
  144. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib/tools/parquet_to_osl_json.py +0 -0
  145. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib.egg-info/dependency_links.txt +0 -0
  146. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib.egg-info/entry_points.txt +0 -0
  147. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib.egg-info/requires.txt +0 -0
  148. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/opensportslib.egg-info/top_level.txt +0 -0
  149. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/scripts/release_versions.py +0 -0
  150. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/scripts/run_h5_header_rule_inference.py +0 -0
  151. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/setup.cfg +0 -0
  152. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/conftest.py +0 -0
  153. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/release/__init__.py +0 -0
  154. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/release/_release_common.py +0 -0
  155. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/release/test_classification_release.py +0 -0
  156. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/release/test_localization_release.py +0 -0
  157. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/release/test_vqa_release.py +0 -0
  158. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_classification_dataset_paths.py +0 -0
  159. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_classification_trainer_dataloader.py +0 -0
  160. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_config_architecture.py +0 -0
  161. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_config_split_override_sync.py +0 -0
  162. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_config_utils_smoke.py +0 -0
  163. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_conversion_tools.py +0 -0
  164. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_editable_config.py +0 -0
  165. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_extract_xvars_features.py +0 -0
  166. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_h5_header_rule_spotter.py +0 -0
  167. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_h5_header_skeleton_spotter.py +0 -0
  168. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_h5_tracking_dataset.py +0 -0
  169. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_hf_transfer_tools.py +0 -0
  170. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_localization_dali_filenames.py +0 -0
  171. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_localization_hf_backend_override.py +0 -0
  172. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_optional_hf_config.py +0 -0
  173. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_package_smoke.py +0 -0
  174. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_pretrained_config_merge_policy.py +0 -0
  175. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_public_apis_smoke.py +0 -0
  176. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_release_versions.py +0 -0
  177. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_remote_model_registry.py +0 -0
  178. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_server_model_registry.py +0 -0
  179. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_setup_cli.py +0 -0
  180. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_subset_train_infer_integration.py +0 -0
  181. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_task_model_api_contract.py +0 -0
  182. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_vqa_api.py +0 -0
  183. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_vqa_metrics_semantic.py +0 -0
  184. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_vqa_qwen_xvars.py +0 -0
  185. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_vqa_training_lora.py +0 -0
  186. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tests/test_vqa_xvars_videochatgpt.py +0 -0
  187. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/build_sn_vqa_2026_vqa.py +0 -0
  188. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/build_sngar_spotting.py +0 -0
  189. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/build_soccernet_gar.py +0 -0
  190. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  191. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/build_soccernet_gar_vqa.py +0 -0
  192. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/build_xvars_indexes.py +0 -0
  193. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/extract_xvars_clip_features.py +0 -0
  194. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  195. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  196. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/sngar_dataset_card.py +0 -0
  197. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/sngar_events.py +0 -0
  198. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/convert/verify_sngar_spotting.py +0 -0
  199. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/download/download_hf_repo.py +0 -0
  200. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/download/download_osl_hf.py +0 -0
  201. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/download/push_sngar_spotting.py +0 -0
  202. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/download/upload_osl_hf.py +0 -0
  203. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/training/classification.py +0 -0
  204. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/training/localization.py +0 -0
  205. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/training/vqa.py +0 -0
  206. {opensportslib-0.3.1.dev14 → opensportslib-0.3.1.dev15}/tools/upload/upload_model_hf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.3.1.dev14
3
+ Version: 0.3.1.dev15
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
@@ -59,7 +59,6 @@ class SpoTTAConfig:
59
59
  drift_scale: float = 10.0
60
60
  drift_threshold: float = 1.0
61
61
  drift_gamma: float = 0.2
62
- augmentation: bool = True
63
62
 
64
63
  @classmethod
65
64
  def from_mapping(cls, value: Any) -> "SpoTTAConfig":
@@ -70,7 +69,6 @@ class SpoTTAConfig:
70
69
  memory = _mapping(root.get("memory"))
71
70
  optimizer = _mapping(root.get("optimizer"))
72
71
  teacher = _mapping(root.get("teacher"))
73
- augmentation = _mapping(root.get("augmentation"))
74
72
 
75
73
  _require_supported_value(
76
74
  root,
@@ -78,34 +76,6 @@ class SpoTTAConfig:
78
76
  "adapt_then_predict",
79
77
  "adaptation",
80
78
  )
81
- _require_supported_value(tether, "mode", "bayesian", "robust_bn.tether")
82
- _require_supported_value(
83
- gate,
84
- "uncertainty",
85
- "one_minus_max_probability",
86
- "confidence_gate",
87
- )
88
- _require_supported_value(
89
- gate,
90
- "aggregation",
91
- "min_over_predicted_action_frames",
92
- "confidence_gate",
93
- )
94
- _require_supported_value(optimizer, "type", "Adam", "optimizer")
95
- _require_supported_value(
96
- optimizer,
97
- "trainable_parameters",
98
- "batch_norm_affine_only",
99
- "optimizer",
100
- )
101
- _require_supported_value(teacher, "type", "ema", "teacher")
102
- _require_supported_value(
103
- teacher, "adaptive_from_bn_drift", True, "teacher"
104
- )
105
- _require_supported_value(
106
- augmentation, "mode", "framewise_rotta_strong", "augmentation"
107
- )
108
-
109
79
  config = cls(
110
80
  alpha=float(robust_bn.get("alpha", cls.alpha)),
111
81
  tether_cap=float(tether.get("cap", cls.tether_cap)),
@@ -133,7 +103,6 @@ class SpoTTAConfig:
133
103
  teacher.get("drift_threshold", cls.drift_threshold)
134
104
  ),
135
105
  drift_gamma=float(teacher.get("drift_gamma", cls.drift_gamma)),
136
- augmentation=bool(augmentation.get("enabled", cls.augmentation)),
137
106
  )
138
107
  config.validate()
139
108
  return config
@@ -577,13 +546,10 @@ class SpoTTA:
577
546
  with torch.no_grad():
578
547
  teacher_logits = _logits(self.teacher(clips))
579
548
 
580
- if self.config.augmentation:
581
- batch, time, channels, height, width = clips.shape
582
- augmented = self.augmentation(
583
- clips.reshape(batch * time, channels, height, width)
584
- ).reshape(batch, time, channels, height, width)
585
- else:
586
- augmented = clips
549
+ batch, time, channels, height, width = clips.shape
550
+ augmented = self.augmentation(
551
+ clips.reshape(batch * time, channels, height, width)
552
+ ).reshape(batch, time, channels, height, width)
587
553
 
588
554
  self.student.train()
589
555
  student_logits = _logits(self.student(augmented))
@@ -39,6 +39,7 @@ DATA:
39
39
  resize:
40
40
  height: 224
41
41
  width: 398
42
+ preserve_aspect_ratio: true
42
43
  params:
43
44
  crop_dim: 224
44
45
  dilate_len: 0
@@ -69,13 +70,10 @@ MODEL:
69
70
  robust_bn:
70
71
  alpha: 0.05
71
72
  tether:
72
- mode: bayesian
73
73
  cap: 0.5
74
74
  confidence_gate:
75
75
  action_class_index: 1
76
76
  min_action_frames: 1
77
- uncertainty: one_minus_max_probability
78
- aggregation: min_over_predicted_action_frames
79
77
  threshold: 0.3
80
78
  memory:
81
79
  capacity: 8
@@ -83,21 +81,14 @@ MODEL:
83
81
  lambda_t: 1.0
84
82
  lambda_u: 1.0
85
83
  optimizer:
86
- type: Adam
87
84
  learning_rate: 0.001
88
85
  beta: 0.9
89
- trainable_parameters: batch_norm_affine_only
90
86
  teacher:
91
- type: ema
92
87
  base_nu: 0.001
93
- adaptive_from_bn_drift: true
94
88
  max_nu: 0.02
95
89
  drift_scale: 10.0
96
90
  drift_threshold: 1.0
97
91
  drift_gamma: 0.2
98
- augmentation:
99
- enabled: true
100
- mode: framewise_rotta_strong
101
92
 
102
93
  TRAIN:
103
94
  execution:
@@ -161,6 +161,7 @@ class LocalizationDataset(Dataset):
161
161
  imagenet_std=normalization.get("std"),
162
162
  target_height=resize.get("height"),
163
163
  target_width=resize.get("width"),
164
+ preserve_aspect_ratio=resize.get("preserve_aspect_ratio", False),
164
165
  )
165
166
  annotation_path = annotations_path or getattr(
166
167
  split_cfg, "annotation_path", getattr(split_cfg, "path", None)
@@ -346,6 +347,7 @@ class LocalizationDataset(Dataset):
346
347
  IMAGENET_STD=self.data_cfg.imagenet_std,
347
348
  TARGET_HEIGHT=self.data_cfg.target_height,
348
349
  TARGET_WIDTH=self.data_cfg.target_width,
350
+ preserve_aspect_ratio=self.data_cfg.preserve_aspect_ratio,
349
351
  )
350
352
  elif dataset_type == "VideoGameWithOpencvVideo":
351
353
  dataset = ActionSpotVideoDataset(
@@ -362,6 +364,7 @@ class LocalizationDataset(Dataset):
362
364
  IMAGENET_STD=self.data_cfg.imagenet_std,
363
365
  TARGET_HEIGHT=self.data_cfg.target_height,
364
366
  TARGET_WIDTH=self.data_cfg.target_width,
367
+ preserve_aspect_ratio=self.data_cfg.preserve_aspect_ratio,
365
368
  )
366
369
  elif dataset_type == "VideoGameWithDali":
367
370
  if not DALI_AVAILABLE:
@@ -543,6 +546,7 @@ class FrameReader:
543
546
  sample_fps=2,
544
547
  TARGET_HEIGHT=224,
545
548
  TARGET_WIDTH=398,
549
+ preserve_aspect_ratio=False,
546
550
  ):
547
551
  self._is_flow = modality == "flow"
548
552
  self._crop_transform = crop_transform
@@ -551,6 +555,37 @@ class FrameReader:
551
555
  self._sample_fps = sample_fps
552
556
  self.TARGET_HEIGHT = TARGET_HEIGHT
553
557
  self.TARGET_WIDTH = TARGET_WIDTH
558
+ self.preserve_aspect_ratio = preserve_aspect_ratio
559
+
560
+ def _resize_frame_ocv(self, frame):
561
+ """Resize an OpenCV frame using the configured spatial policy.
562
+
563
+ With aspect preservation enabled, ``TARGET_HEIGHT`` fixes the output
564
+ height and the width is derived from the source aspect ratio. If no
565
+ target height is configured, ``TARGET_WIDTH`` fixes the output width.
566
+ """
567
+ import cv2
568
+
569
+ height, width = frame.shape[:2]
570
+ if self.preserve_aspect_ratio:
571
+ if self.TARGET_HEIGHT is not None and self.TARGET_HEIGHT > 0:
572
+ scale = self.TARGET_HEIGHT / height
573
+ elif self.TARGET_WIDTH is not None and self.TARGET_WIDTH > 0:
574
+ scale = self.TARGET_WIDTH / width
575
+ else:
576
+ return frame
577
+
578
+ output_height = max(1, round(height * scale))
579
+ output_width = max(1, round(width * scale))
580
+ else:
581
+ output_height = self.TARGET_HEIGHT
582
+ output_width = self.TARGET_WIDTH
583
+
584
+ if output_height is None or output_width is None:
585
+ return frame
586
+ if (height, width) == (output_height, output_width):
587
+ return frame
588
+ return cv2.resize(frame, (output_width, output_height))
554
589
 
555
590
  def adapt_frame_ocv(self, frame):
556
591
  """Apply some modifications to the frame to have the expected shape and format.
@@ -675,8 +710,7 @@ class FrameReader:
675
710
  ret, frame = vc.read()
676
711
  if ret:
677
712
  if i % stride_extract == 0:
678
- if frame.shape[0] != oh or frame.shape[1] != ow:
679
- frame = cv2.resize(frame, (ow, oh))
713
+ frame = self._resize_frame_ocv(frame)
680
714
  img = self.adapt_frame_ocv(frame)
681
715
  if self._crop_transform:
682
716
  if self._same_transform:
@@ -781,6 +815,7 @@ class ActionSpotDataset(Dataset):
781
815
  IMAGENET_STD=[0.229, 0.224, 0.225],
782
816
  TARGET_HEIGHT=224,
783
817
  TARGET_WIDTH=398,
818
+ preserve_aspect_ratio=False,
784
819
  ):
785
820
  import random
786
821
  from opensportslib.core.utils.load_annotations import annotationstoe2eformat
@@ -850,7 +885,8 @@ class ActionSpotDataset(Dataset):
850
885
  same_transform,
851
886
  extract_fps,
852
887
  self.TARGET_HEIGHT,
853
- self.TARGET_WIDTH
888
+ self.TARGET_WIDTH,
889
+ preserve_aspect_ratio,
854
890
  )
855
891
 
856
892
  def load_frame_gpu(self, batch, device):
@@ -1097,6 +1133,7 @@ class ActionSpotVideoDataset(Dataset, DatasetVideoSharedMethods):
1097
1133
  IMAGENET_STD=[0.229, 0.224, 0.225],
1098
1134
  TARGET_HEIGHT=224,
1099
1135
  TARGET_WIDTH=398,
1136
+ preserve_aspect_ratio=False,
1100
1137
  ):
1101
1138
  from opensportslib.core.utils.load_annotations import annotationstoe2eformat, construct_labels
1102
1139
  from opensportslib.core.utils.video_processing import _get_img_transforms
@@ -1138,7 +1175,8 @@ class ActionSpotVideoDataset(Dataset, DatasetVideoSharedMethods):
1138
1175
  False,
1139
1176
  extract_fps,
1140
1177
  self.TARGET_HEIGHT,
1141
- self.TARGET_WIDTH
1178
+ self.TARGET_WIDTH,
1179
+ preserve_aspect_ratio,
1142
1180
  )
1143
1181
 
1144
1182
  self._flip = flip
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.3.1.dev14
3
+ Version: 0.3.1.dev15
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
@@ -169,7 +169,6 @@ tests/test_release_versions.py
169
169
  tests/test_remote_model_registry.py
170
170
  tests/test_server_model_registry.py
171
171
  tests/test_setup_cli.py
172
- tests/test_spotta_e2e.py
173
172
  tests/test_subset_train_infer_integration.py
174
173
  tests/test_task_model_api_contract.py
175
174
  tests/test_vqa_api.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opensportslib"
7
- version = "0.3.1.dev14"
7
+ version = "0.3.1.dev15"
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"
@@ -209,6 +209,26 @@ def test_frame_reader_seeks_and_stops_inside_interval(monkeypatch):
209
209
  assert max(capture.read_positions) < 50
210
210
 
211
211
 
212
+ def test_frame_reader_can_preserve_aspect_ratio_before_crop():
213
+ reader = FrameReader(
214
+ "rgb",
215
+ crop_transform=None,
216
+ img_transform=lambda image: image,
217
+ same_transform=False,
218
+ TARGET_HEIGHT=224,
219
+ TARGET_WIDTH=398,
220
+ preserve_aspect_ratio=True,
221
+ )
222
+
223
+ four_by_three = np.zeros((224, 298, 3), dtype=np.uint8)
224
+ sixteen_by_nine = np.zeros((224, 398, 3), dtype=np.uint8)
225
+ scaled_four_by_three = np.zeros((448, 596, 3), dtype=np.uint8)
226
+
227
+ assert reader._resize_frame_ocv(four_by_three).shape == (224, 298, 3)
228
+ assert reader._resize_frame_ocv(sixteen_by_nine).shape == (224, 398, 3)
229
+ assert reader._resize_frame_ocv(scaled_four_by_three).shape == (224, 298, 3)
230
+
231
+
212
232
  def test_v2_evaluator_scores_only_verified_logical_intervals(tmp_path):
213
233
  verified = _record()
214
234
  unlabeled = _record(status="unlabeled")
@@ -1,285 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from types import SimpleNamespace
4
- from unittest.mock import patch
5
-
6
- import torch
7
- import pytest
8
- from torch import nn
9
-
10
- from opensportslib.core.config.accessors import get_loader_backend
11
- from opensportslib.models.base.e2e import E2EModel
12
- from opensportslib.adaptation.spotta import (
13
- FramewiseStrongAugmentation,
14
- RobustBatchNorm,
15
- SpoTTA,
16
- SpoTTAConfig,
17
- replace_batch_norm,
18
- )
19
- from opensportslib.core.config import load_config
20
-
21
-
22
- class _TinyE2ESpot(nn.Module):
23
- def __init__(self, num_classes=2, predicted_class=1):
24
- super().__init__()
25
- self.bn = nn.BatchNorm2d(3)
26
- self.classifier = nn.Linear(3, num_classes)
27
- with torch.no_grad():
28
- self.classifier.weight.zero_()
29
- self.classifier.bias.fill_(-2.0)
30
- self.classifier.bias[predicted_class] = 2.0
31
-
32
- def forward(self, clips):
33
- batch, time, channels, height, width = clips.shape
34
- frames = self.bn(clips.reshape(batch * time, channels, height, width))
35
- features = frames.mean(dim=(-1, -2))
36
- return self.classifier(features).reshape(batch, time, -1)
37
-
38
-
39
- def _spotta_config(**overrides):
40
- config = {
41
- "enabled": True,
42
- "name": "spotta",
43
- "prediction_timing": "adapt_then_predict",
44
- "robust_bn": {"alpha": 0.05, "tether": {"cap": 0.5}},
45
- "confidence_gate": {
46
- "action_class_index": 1,
47
- "min_action_frames": 1,
48
- "threshold": 0.3,
49
- },
50
- "memory": {
51
- "capacity": 8,
52
- "update_frequency": 2,
53
- "lambda_t": 1.0,
54
- "lambda_u": 1.0,
55
- },
56
- "optimizer": {"learning_rate": 0.001, "beta": 0.9},
57
- "teacher": {
58
- "base_nu": 0.001,
59
- "max_nu": 0.02,
60
- "drift_scale": 10.0,
61
- "drift_threshold": 1.0,
62
- "drift_gamma": 0.2,
63
- },
64
- "augmentation": {"enabled": False},
65
- }
66
- config.update(overrides)
67
- return config
68
-
69
-
70
- def test_robust_batch_norm_preserves_eval_output_and_frozen_anchor():
71
- torch.manual_seed(7)
72
- source = nn.Sequential(nn.BatchNorm2d(3)).eval()
73
- with torch.no_grad():
74
- source[0].running_mean.copy_(torch.tensor([0.2, -0.1, 0.4]))
75
- source[0].running_var.copy_(torch.tensor([0.7, 1.3, 2.0]))
76
- source[0].weight.copy_(torch.tensor([0.8, 1.1, 0.9]))
77
- source[0].bias.copy_(torch.tensor([-0.2, 0.3, 0.1]))
78
- inputs = torch.randn(4, 3, 5, 5)
79
- expected = source(inputs)
80
-
81
- layers = replace_batch_norm(source, alpha=0.05)
82
- source.eval()
83
- actual = source(inputs)
84
-
85
- assert len(layers) == 1
86
- assert isinstance(layers[0], RobustBatchNorm)
87
- assert torch.allclose(actual, expected, atol=1e-6, rtol=1e-5)
88
-
89
- anchor_mean = layers[0].anchor_mean.clone()
90
- source.train()
91
- source(inputs + 2)
92
- assert torch.equal(layers[0].anchor_mean, anchor_mean)
93
- assert not torch.equal(layers[0].source_mean, anchor_mean)
94
-
95
-
96
- def test_spotta_gates_actions_and_updates_every_second_gated_clip():
97
- spotta = SpoTTA(_TinyE2ESpot(), _spotta_config())
98
- clips = torch.randn(2, 4, 3, 5, 5)
99
-
100
- predicted, probabilities = spotta.predict(clips, use_amp=False)
101
-
102
- assert predicted.shape == (2, 4)
103
- assert probabilities.shape == (2, 4, 2)
104
- assert spotta.stats["clips_seen"] == 2
105
- assert spotta.stats["clips_gated"] == 2
106
- assert spotta.stats["memory_insertions"] == 2
107
- assert spotta.stats["memory_occupancy"] == 2
108
- assert spotta.stats["update_attempts"] == 1
109
- assert spotta.stats["updates_completed"] == 1
110
- assert {layer.tether_rho for layer in spotta.tether_layers} == {0.25}
111
-
112
-
113
- def test_framewise_strong_augmentation_preserves_clip_frame_shape_and_range():
114
- torch.manual_seed(11)
115
- frames = torch.randn(3, 3, 16, 20)
116
-
117
- augmented = FramewiseStrongAugmentation(image_size=16)(frames)
118
-
119
- assert augmented.shape == frames.shape
120
- assert float(augmented.min()) >= 0.0
121
- assert float(augmented.max()) <= 1.0
122
-
123
-
124
- def test_e2e_wrapper_starts_fresh_spotta_without_mutating_source_model():
125
- wrapper = E2EModel.__new__(E2EModel)
126
- wrapper._model = _TinyE2ESpot()
127
- wrapper._num_classes = 2
128
- wrapper._multi_gpu = False
129
- wrapper._test_time_adapter = None
130
- source_state = {
131
- name: tensor.clone() for name, tensor in wrapper._model.state_dict().items()
132
- }
133
-
134
- wrapper.configure_test_time_adaptation(_spotta_config())
135
- wrapper.predict(torch.randn(2, 4, 3, 5, 5), use_amp=False)
136
- first_adapter = wrapper._test_time_adapter
137
- wrapper.configure_test_time_adaptation(_spotta_config())
138
-
139
- assert wrapper._test_time_adapter is not first_adapter
140
- for name, tensor in wrapper._model.state_dict().items():
141
- assert torch.equal(tensor, source_state[name])
142
-
143
-
144
- def test_e2e_wrapper_keeps_one_spotta_instance_across_session_batches():
145
- wrapper = E2EModel.__new__(E2EModel)
146
- wrapper._model = _TinyE2ESpot()
147
- wrapper._num_classes = 2
148
- wrapper._multi_gpu = False
149
- wrapper._test_time_adapter = None
150
- wrapper.configure_test_time_adaptation(_spotta_config())
151
- session_adapter = wrapper._test_time_adapter
152
-
153
- wrapper.predict(torch.randn(1, 4, 3, 5, 5), use_amp=False)
154
- wrapper.predict(torch.randn(1, 4, 3, 5, 5), use_amp=False)
155
-
156
- assert wrapper._test_time_adapter is session_adapter
157
- assert wrapper.test_time_adaptation_stats["clips_seen"] == 2
158
- assert wrapper.test_time_adaptation_stats["memory_occupancy"] == 2
159
- assert wrapper.test_time_adaptation_stats["updates_completed"] == 1
160
-
161
-
162
- def test_disabled_spotta_uses_ordinary_e2e_prediction_path():
163
- wrapper = E2EModel.__new__(E2EModel)
164
- wrapper._model = _TinyE2ESpot()
165
- wrapper._num_classes = 2
166
- wrapper._multi_gpu = False
167
- wrapper._test_time_adapter = None
168
- wrapper.device = torch.device("cpu")
169
- clips = torch.randn(2, 4, 3, 5, 5)
170
- wrapper._model.eval()
171
- with torch.no_grad():
172
- expected_probabilities = wrapper._model(clips).softmax(dim=2)
173
- expected_classes = expected_probabilities.argmax(dim=2)
174
-
175
- wrapper.configure_test_time_adaptation({"enabled": False, "name": "spotta"})
176
- predicted_classes, probabilities = wrapper.predict(clips, use_amp=False)
177
-
178
- assert wrapper._test_time_adapter is None
179
- assert torch.equal(torch.from_numpy(predicted_classes), expected_classes)
180
- assert torch.allclose(
181
- torch.from_numpy(probabilities), expected_probabilities, atol=1e-7
182
- )
183
-
184
-
185
- def test_spotta_supports_configured_action_class_in_multiclass_output():
186
- config = _spotta_config()
187
- config["confidence_gate"]["action_class_index"] = 2
188
- spotta = SpoTTA(
189
- _TinyE2ESpot(num_classes=4, predicted_class=2), config
190
- )
191
-
192
- predicted, probabilities = spotta.predict(
193
- torch.randn(2, 4, 3, 5, 5), use_amp=False
194
- )
195
-
196
- assert predicted.shape == (2, 4)
197
- assert probabilities.shape == (2, 4, 4)
198
- assert torch.from_numpy(predicted).eq(2).all()
199
- assert spotta.stats["clips_gated"] == 2
200
- assert spotta.stats["updates_completed"] == 1
201
-
202
-
203
- def test_spotta_config_rejects_negative_action_class_index():
204
- config = _spotta_config()
205
- config["confidence_gate"]["action_class_index"] = -1
206
-
207
- with pytest.raises(ValueError, match="must be non-negative"):
208
- SpoTTAConfig.from_mapping(config)
209
-
210
-
211
- def test_spotta_rejects_action_class_outside_model_outputs():
212
- config = _spotta_config()
213
- config["confidence_gate"]["action_class_index"] = 4
214
- spotta = SpoTTA(_TinyE2ESpot(num_classes=4), config)
215
-
216
- with pytest.raises(ValueError, match="outside the model's 4 output classes"):
217
- spotta.predict(torch.randn(1, 4, 3, 5, 5), use_amp=False)
218
-
219
-
220
- def test_spotta_config_rejects_unsupported_semantic_changes():
221
- config = _spotta_config()
222
- config["confidence_gate"]["aggregation"] = "mean"
223
-
224
- try:
225
- SpoTTAConfig.from_mapping(config)
226
- except ValueError as exc:
227
- assert "min_over_predicted_action_frames" in str(exc)
228
- else:
229
- raise AssertionError("Expected a changed confidence aggregation to fail.")
230
-
231
-
232
- def test_spotta_config_contains_only_supported_options():
233
- with patch(
234
- "opensportslib.core.config.loader._dali_available", return_value=False
235
- ):
236
- config = load_config(
237
- "opensportslib/configs/localization/e2e_spotta.yaml",
238
- as_namespace=False,
239
- )
240
- spotta_config = config["MODEL"]["policies"]["test_time_adaptation"]
241
-
242
- assert spotta_config["enabled"] is True
243
- assert spotta_config["prediction_timing"] == "adapt_then_predict"
244
- assert spotta_config["confidence_gate"]["threshold"] == 0.3
245
- assert spotta_config["memory"]["capacity"] == 8
246
- assert spotta_config["memory"]["update_frequency"] == 2
247
- assert spotta_config["robust_bn"]["tether"]["mode"] == "bayesian"
248
- assert "class_policy" not in spotta_config["memory"]
249
- assert "frame_filter" not in spotta_config
250
- assert "steps" not in spotta_config
251
- assert "reset_frequency" not in spotta_config
252
- assert "action_frame_weight" not in spotta_config
253
-
254
-
255
- def test_localization_starts_fresh_spotta_session_and_forces_opencv_runtime():
256
- from opensportslib.apis.localization import LocalizationModel
257
-
258
- configured = []
259
-
260
- class _Model:
261
- def configure_test_time_adaptation(self, adaptation):
262
- configured.append(adaptation)
263
-
264
- adaptation = SimpleNamespace(enabled=True, name="spotta")
265
- api = LocalizationModel.__new__(LocalizationModel)
266
- api.model = _Model()
267
- api.config = SimpleNamespace(
268
- MODEL=SimpleNamespace(
269
- metadata=SimpleNamespace(family="E2E"),
270
- policies=SimpleNamespace(test_time_adaptation=adaptation),
271
- ),
272
- DATA=SimpleNamespace(
273
- common=SimpleNamespace(
274
- runtime=SimpleNamespace(loader_backend="dali"),
275
- splits=SimpleNamespace(
276
- test=SimpleNamespace(type="VideoGameWithOpencvVideo")
277
- ),
278
- )
279
- ),
280
- )
281
-
282
- api._configure_test_time_adaptation()
283
-
284
- assert configured == [adaptation]
285
- assert get_loader_backend(api.config) == "opencv"