opensportslib 0.3.1.dev2__tar.gz → 0.3.1.dev4__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 (202) hide show
  1. opensportslib-0.3.1.dev4/MANIFEST.in +4 -0
  2. {opensportslib-0.3.1.dev2/opensportslib.egg-info → opensportslib-0.3.1.dev4}/PKG-INFO +90 -1
  3. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/README.md +89 -0
  4. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/apis/__init__.py +2 -0
  5. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/apis/base_task_model.py +39 -8
  6. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/apis/classification.py +35 -3
  7. opensportslib-0.3.1.dev4/opensportslib/apis/config.py +5 -0
  8. opensportslib-0.3.1.dev4/opensportslib/apis/configuration.py +145 -0
  9. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/apis/localization.py +33 -3
  10. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/apis/vqa.py +7 -3
  11. opensportslib-0.3.1.dev4/opensportslib/core/config/editable.py +396 -0
  12. opensportslib-0.3.1.dev4/opensportslib/core/config/rule_variants.py +66 -0
  13. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/trainer/vqa_trainer.py +10 -0
  14. opensportslib-0.3.1.dev4/opensportslib/core/utils/direct_video.py +66 -0
  15. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/metrics/classification_metric.py +9 -11
  16. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/qwen_vl_native.py +4 -2
  17. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/qwen_xvars.py +4 -2
  18. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/rule_based.py +1 -62
  19. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/xvars_videochatgpt.py +6 -3
  20. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/setup/setup.py +1 -1
  21. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/tools/hf_transfer.py +135 -20
  22. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4/opensportslib.egg-info}/PKG-INFO +90 -1
  23. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib.egg-info/SOURCES.txt +7 -0
  24. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/pyproject.toml +9 -3
  25. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/conftest.py +2 -0
  26. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_config_architecture.py +4 -4
  27. opensportslib-0.3.1.dev4/tests/test_editable_config.py +190 -0
  28. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_hf_transfer_tools.py +147 -0
  29. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_localization_hf_backend_override.py +1 -0
  30. opensportslib-0.3.1.dev4/tests/test_optional_hf_config.py +84 -0
  31. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_setup_cli.py +4 -4
  32. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_vqa_xvars_videochatgpt.py +4 -8
  33. opensportslib-0.3.1.dev2/MANIFEST.in +0 -3
  34. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/LICENSE +0 -0
  35. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/LICENSE-COMMERCIAL +0 -0
  36. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/examples/quickstart/basic_classification.py +0 -0
  37. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/examples/quickstart/basic_localization.py +0 -0
  38. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/examples/quickstart/basic_vqa.py +0 -0
  39. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/__init__.py +0 -0
  40. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/adaptation/__init__.py +0 -0
  41. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/adaptation/spotta.py +0 -0
  42. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/cli.py +0 -0
  43. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/classification/default.yaml +0 -0
  44. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/classification/sngar_frames.yaml +0 -0
  45. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/classification/sngar_tracking.yaml +0 -0
  46. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/classification/video.yaml +0 -0
  47. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/default.yaml +0 -0
  48. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/localization/calf_resnetpca512.yaml +0 -0
  49. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/localization/default.yaml +0 -0
  50. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/localization/e2e_spotta.yaml +0 -0
  51. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/localization/h5_header_distance.yaml +0 -0
  52. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/localization/h5_header_skeleton.yaml +0 -0
  53. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/localization/netvladpp_resnetpca512.yaml +0 -0
  54. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/localization/tracking_action_spotting.yaml +0 -0
  55. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/localization/video_dali.yaml +0 -0
  56. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/localization/video_ocv.yaml +0 -0
  57. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/vqa/default.yaml +0 -0
  58. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/vqa/qwen.yaml +0 -0
  59. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/vqa/qwen3_vl_native.yaml +0 -0
  60. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/vqa/qwen_lora.yaml +0 -0
  61. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/vqa/qwen_sngar_frames.yaml +0 -0
  62. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/configs/vqa/xvars.yaml +0 -0
  63. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/__init__.py +0 -0
  64. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/__init__.py +0 -0
  65. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/accessors.py +0 -0
  66. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/conflicts.py +0 -0
  67. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/loader.py +0 -0
  68. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/migrate.py +0 -0
  69. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/migrations/__init__.py +0 -0
  70. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/migrations/legacy_to_canonical.py +0 -0
  71. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/runtime_adapter.py +0 -0
  72. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/schema.py +0 -0
  73. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/schemas/__init__.py +0 -0
  74. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/schemas/schema_canonical.py +0 -0
  75. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/schemas/schema_legacy.py +0 -0
  76. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/config/validate.py +0 -0
  77. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/loss/__init__.py +0 -0
  78. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/loss/builder.py +0 -0
  79. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/loss/calf.py +0 -0
  80. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/loss/ce.py +0 -0
  81. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/loss/combine.py +0 -0
  82. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/loss/nll.py +0 -0
  83. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/optimizer/__init__.py +0 -0
  84. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/optimizer/builder.py +0 -0
  85. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/sampler/weighted_sampler.py +0 -0
  86. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/scheduler/__init__.py +0 -0
  87. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/scheduler/builder.py +0 -0
  88. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/trainer/__init__.py +0 -0
  89. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/trainer/classification_trainer.py +0 -0
  90. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/trainer/localization_trainer.py +0 -0
  91. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/checkpoint.py +0 -0
  92. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/config.py +0 -0
  93. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/config_normalize.py +0 -0
  94. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/data.py +0 -0
  95. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/ddp.py +0 -0
  96. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/default_args.py +0 -0
  97. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/hf_runtime.py +0 -0
  98. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/lightning.py +0 -0
  99. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/load_annotations.py +0 -0
  100. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/seed.py +0 -0
  101. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/video_processing.py +0 -0
  102. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/core/utils/wandb.py +0 -0
  103. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/datasets/__init__.py +0 -0
  104. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/datasets/builder.py +0 -0
  105. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/datasets/classification_dataset.py +0 -0
  106. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/datasets/localization_dataset.py +0 -0
  107. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/datasets/utils/__init__.py +0 -0
  108. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/datasets/utils/h5_tracking.py +0 -0
  109. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/datasets/utils/tracking.py +0 -0
  110. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/datasets/vqa_dataset.py +0 -0
  111. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/legacy_config/classification.yaml +0 -0
  112. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/legacy_config/localization-e2e-ocv.yaml +0 -0
  113. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/legacy_config/localization-json_calf_resnetpca512.yaml +0 -0
  114. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/legacy_config/localization-json_netvlad++_resnetpca512.yaml +0 -0
  115. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/legacy_config/localization.yaml +0 -0
  116. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/legacy_config/sngar-frames.yaml +0 -0
  117. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/legacy_config/sngar-tracking.yaml +0 -0
  118. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/metrics/localization_metric.py +0 -0
  119. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/metrics/vqa_metric.py +0 -0
  120. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/__init__.py +0 -0
  121. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/backbones/builder.py +0 -0
  122. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/contextaware.py +0 -0
  123. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/e2e.py +0 -0
  124. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/learnablepooling.py +0 -0
  125. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/tracking.py +0 -0
  126. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/vars.py +0 -0
  127. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/video.py +0 -0
  128. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/video_chatgpt_compat.py +0 -0
  129. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/base/video_mae.py +0 -0
  130. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/builder.py +0 -0
  131. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/heads/builder.py +0 -0
  132. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/neck/builder.py +0 -0
  133. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/common.py +0 -0
  134. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/impl/__init__.py +0 -0
  135. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/impl/asformer.py +0 -0
  136. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/impl/calf.py +0 -0
  137. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/impl/gsm.py +0 -0
  138. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/impl/gtad.py +0 -0
  139. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/impl/tsm.py +0 -0
  140. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/litebase.py +0 -0
  141. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/modules.py +0 -0
  142. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/shift.py +0 -0
  143. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/utils.py +0 -0
  144. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/vqa_prediction_priors.py +0 -0
  145. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/vqa_prompting.py +0 -0
  146. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/models/utils/xvars_clip_index.py +0 -0
  147. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/tools/__init__.py +0 -0
  148. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/tools/_common.py +0 -0
  149. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/tools/osl_json_to_parquet.py +0 -0
  150. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib/tools/parquet_to_osl_json.py +0 -0
  151. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib.egg-info/dependency_links.txt +0 -0
  152. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib.egg-info/entry_points.txt +0 -0
  153. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib.egg-info/requires.txt +0 -0
  154. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/opensportslib.egg-info/top_level.txt +0 -0
  155. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/scripts/run_h5_header_rule_inference.py +0 -0
  156. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/setup.cfg +0 -0
  157. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/release/__init__.py +0 -0
  158. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/release/_release_common.py +0 -0
  159. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/release/test_classification_release.py +0 -0
  160. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/release/test_localization_release.py +0 -0
  161. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/release/test_vqa_release.py +0 -0
  162. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_classification_dataset_paths.py +0 -0
  163. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_classification_trainer_dataloader.py +0 -0
  164. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_config_split_override_sync.py +0 -0
  165. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_config_utils_smoke.py +0 -0
  166. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_conversion_tools.py +0 -0
  167. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_extract_xvars_features.py +0 -0
  168. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_h5_header_rule_spotter.py +0 -0
  169. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_h5_header_skeleton_spotter.py +0 -0
  170. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_h5_tracking_dataset.py +0 -0
  171. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_localization_dali_filenames.py +0 -0
  172. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_localization_intervals.py +0 -0
  173. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_package_smoke.py +0 -0
  174. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_pretrained_config_merge_policy.py +0 -0
  175. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_public_apis_smoke.py +0 -0
  176. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_spotta_e2e.py +0 -0
  177. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_subset_train_infer_integration.py +0 -0
  178. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_task_model_api_contract.py +0 -0
  179. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_vqa_api.py +0 -0
  180. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_vqa_metrics_semantic.py +0 -0
  181. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_vqa_qwen_xvars.py +0 -0
  182. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tests/test_vqa_training_lora.py +0 -0
  183. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/build_sn_vqa_2026_vqa.py +0 -0
  184. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/build_sngar_spotting.py +0 -0
  185. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/build_soccernet_gar.py +0 -0
  186. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/build_soccernet_gar_action_spotting.py +0 -0
  187. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/build_soccernet_gar_vqa.py +0 -0
  188. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/build_xvars_indexes.py +0 -0
  189. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/extract_xvars_clip_features.py +0 -0
  190. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/osl_json_to_parquet_webdataset.py +0 -0
  191. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/parquet_webdataset_to_osl_json.py +0 -0
  192. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/sngar_dataset_card.py +0 -0
  193. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/sngar_events.py +0 -0
  194. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/convert/verify_sngar_spotting.py +0 -0
  195. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/download/download_hf_repo.py +0 -0
  196. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/download/download_osl_hf.py +0 -0
  197. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/download/push_sngar_spotting.py +0 -0
  198. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/download/upload_osl_hf.py +0 -0
  199. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/training/classification.py +0 -0
  200. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/training/localization.py +0 -0
  201. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/training/vqa.py +0 -0
  202. {opensportslib-0.3.1.dev2 → opensportslib-0.3.1.dev4}/tools/upload/upload_model_hf.py +0 -0
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include opensportslib/config *.yaml
4
+ prune server
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensportslib
3
- Version: 0.3.1.dev2
3
+ Version: 0.3.1.dev4
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
@@ -35,6 +35,60 @@ Requires-Dist: pytest-cov; extra == "test"
35
35
  Dynamic: license-file
36
36
 
37
37
  # OpenSportsLib
38
+
39
+ ## Configuration From Hugging Face
40
+
41
+ Prepare and inspect configuration before model weights are allocated:
42
+
43
+ ```python
44
+ from opensportslib.apis import Config, ClassificationModel
45
+
46
+ config = Config.from_pretrained("OpenSportsLab/OSL-cls-action-mvitv2")
47
+ # Or: Config.from_file("opensportslib/configs/classification/video.yaml")
48
+
49
+ config.update(
50
+ data={"data_root": "/datasets/fouls"},
51
+ training={"epochs": 30, "batch_size": 8},
52
+ inference={"batch_size": 4},
53
+ overrides={"TRAIN.scheduler.step_size": 5},
54
+ )
55
+ print(config.options())
56
+ model = ClassificationModel(config=config)
57
+ ```
58
+
59
+ `options()` reports editable parameters supported by the selected task and
60
+ backend. `get_config()` returns a detached canonical dictionary for discovering
61
+ advanced dotted paths. Dotted overrides must already exist, are validated as
62
+ one atomic update, and are intended for local execution. Initialization-sensitive
63
+ settings such as device and output directory must be changed before creating the
64
+ model. `model.update_config(...)` supports safe settings for the next operation.
65
+
66
+ When `weights` is a Hugging Face model ID, `config` may be omitted if the
67
+ repository contains a compatible OpenSportsLib `config.yaml`:
68
+
69
+ ```python
70
+ from opensportslib.apis import ClassificationModel
71
+
72
+ model = ClassificationModel(weights="OpenSportsLab/OSL-cls-action-mvitv2")
73
+ ```
74
+
75
+ This also applies to localization and VQA wrappers. Local checkpoints and
76
+ repositories containing only a Transformers `config.json` still require an
77
+ explicit OpenSportsLib config. Explicit configs retain existing merge behavior.
78
+ Provide your own input data when running inference; published dataset paths may
79
+ refer to the machine used for training.
80
+
81
+ Classification and localization accept a video directly, without a manifest:
82
+
83
+ ```python
84
+ classification_predictions = classification_model.infer(video_path="/path/to/clip.mp4")
85
+ localization_predictions = localization_model.infer(video_path="/path/to/full-match.mp4")
86
+ ```
87
+
88
+ Direct classification treats the file as one sample. Direct localization treats
89
+ it as one timeline and returns detected events. Both return the regular OSL JSON
90
+ prediction document; use `test_set=` instead when evaluating labeled data.
91
+
38
92
  <img src="docs/assets/osl.jpg" height="400">
39
93
 
40
94
  OpenSportsLib is a modular Python library for sports video understanding.
@@ -392,6 +446,19 @@ The JSON records the resolved Hugging Face commit and can later be passed to
392
446
  Parquet/WebDataset download always completes the local split even when a
393
447
  metadata-only `<split>.json` already exists.
394
448
 
449
+ Download APIs accept `byte_progress_cb(filename, downloaded_bytes,
450
+ total_bytes)`. When the repository file is Xet-backed, OpenSportsLib keeps the
451
+ accelerated Xet transfer and adapts Xet's byte updates to this callback. It
452
+ falls back to classic HTTP progress when Xet is unavailable, disabled, or not
453
+ used by the file.
454
+ When byte progress is enabled, Parquet downloads also emit `[current/total]`
455
+ file messages through `progress_cb` so clients can present file-count progress.
456
+ High-level split downloads also accept `file_plan_cb(filenames)`,
457
+ `file_completed_cb(filename, local_path)`, and
458
+ `json_ready_cb(split, json_path)`. These are transfer lifecycle notifications;
459
+ callers remain responsible for queue policy and presentation. For non-dry-run
460
+ JSON datasets, pinned source metadata is persisted before `json_ready_cb` runs.
461
+
395
462
  JSON uploads support partially downloaded datasets: the JSON and all
396
463
  referenced files available locally are committed, while missing referenced
397
464
  files are skipped and reported. Remote files not included in that commit are
@@ -543,3 +610,25 @@ If you use OpenSportsLib in your research, please cite the project.
543
610
  ## Acknowledgments
544
611
 
545
612
  OpenSportsLib is developed within the broader OpenSportsLab effort for sports video understanding.
613
+
614
+ ## Inference server
615
+
616
+ The optional FastAPI + Redis/RQ inference server lives in [`server/`](server/README.md),
617
+ beside the main library package. It supports classification, localization, VQA,
618
+ video/manifest uploads, and the library's remote inference client.
619
+
620
+ `pip install opensportslib` installs the library only. To run the server from
621
+ this repository, activate a fresh Python 3.12 or newer environment and install the server:
622
+
623
+ ```bash
624
+ pip install -e ./server
625
+ bash server/scripts/setup_env.sh
626
+ bash server/scripts/start_all.sh
627
+ ```
628
+
629
+ The server installs the OpenSportsLib release from PyPI pinned to the root project
630
+ version. That release must be published before installing or building the server.
631
+
632
+ See the [server guide](server/README.md) for uv setup, model configuration,
633
+ Redis and GPU deployment with Docker Compose. Server dependencies
634
+ and runtime data are managed separately from the library.
@@ -1,4 +1,58 @@
1
1
  # OpenSportsLib
2
+
3
+ ## Configuration From Hugging Face
4
+
5
+ Prepare and inspect configuration before model weights are allocated:
6
+
7
+ ```python
8
+ from opensportslib.apis import Config, ClassificationModel
9
+
10
+ config = Config.from_pretrained("OpenSportsLab/OSL-cls-action-mvitv2")
11
+ # Or: Config.from_file("opensportslib/configs/classification/video.yaml")
12
+
13
+ config.update(
14
+ data={"data_root": "/datasets/fouls"},
15
+ training={"epochs": 30, "batch_size": 8},
16
+ inference={"batch_size": 4},
17
+ overrides={"TRAIN.scheduler.step_size": 5},
18
+ )
19
+ print(config.options())
20
+ model = ClassificationModel(config=config)
21
+ ```
22
+
23
+ `options()` reports editable parameters supported by the selected task and
24
+ backend. `get_config()` returns a detached canonical dictionary for discovering
25
+ advanced dotted paths. Dotted overrides must already exist, are validated as
26
+ one atomic update, and are intended for local execution. Initialization-sensitive
27
+ settings such as device and output directory must be changed before creating the
28
+ model. `model.update_config(...)` supports safe settings for the next operation.
29
+
30
+ When `weights` is a Hugging Face model ID, `config` may be omitted if the
31
+ repository contains a compatible OpenSportsLib `config.yaml`:
32
+
33
+ ```python
34
+ from opensportslib.apis import ClassificationModel
35
+
36
+ model = ClassificationModel(weights="OpenSportsLab/OSL-cls-action-mvitv2")
37
+ ```
38
+
39
+ This also applies to localization and VQA wrappers. Local checkpoints and
40
+ repositories containing only a Transformers `config.json` still require an
41
+ explicit OpenSportsLib config. Explicit configs retain existing merge behavior.
42
+ Provide your own input data when running inference; published dataset paths may
43
+ refer to the machine used for training.
44
+
45
+ Classification and localization accept a video directly, without a manifest:
46
+
47
+ ```python
48
+ classification_predictions = classification_model.infer(video_path="/path/to/clip.mp4")
49
+ localization_predictions = localization_model.infer(video_path="/path/to/full-match.mp4")
50
+ ```
51
+
52
+ Direct classification treats the file as one sample. Direct localization treats
53
+ it as one timeline and returns detected events. Both return the regular OSL JSON
54
+ prediction document; use `test_set=` instead when evaluating labeled data.
55
+
2
56
  <img src="docs/assets/osl.jpg" height="400">
3
57
 
4
58
  OpenSportsLib is a modular Python library for sports video understanding.
@@ -356,6 +410,19 @@ The JSON records the resolved Hugging Face commit and can later be passed to
356
410
  Parquet/WebDataset download always completes the local split even when a
357
411
  metadata-only `<split>.json` already exists.
358
412
 
413
+ Download APIs accept `byte_progress_cb(filename, downloaded_bytes,
414
+ total_bytes)`. When the repository file is Xet-backed, OpenSportsLib keeps the
415
+ accelerated Xet transfer and adapts Xet's byte updates to this callback. It
416
+ falls back to classic HTTP progress when Xet is unavailable, disabled, or not
417
+ used by the file.
418
+ When byte progress is enabled, Parquet downloads also emit `[current/total]`
419
+ file messages through `progress_cb` so clients can present file-count progress.
420
+ High-level split downloads also accept `file_plan_cb(filenames)`,
421
+ `file_completed_cb(filename, local_path)`, and
422
+ `json_ready_cb(split, json_path)`. These are transfer lifecycle notifications;
423
+ callers remain responsible for queue policy and presentation. For non-dry-run
424
+ JSON datasets, pinned source metadata is persisted before `json_ready_cb` runs.
425
+
359
426
  JSON uploads support partially downloaded datasets: the JSON and all
360
427
  referenced files available locally are committed, while missing referenced
361
428
  files are skipped and reported. Remote files not included in that commit are
@@ -507,3 +574,25 @@ If you use OpenSportsLib in your research, please cite the project.
507
574
  ## Acknowledgments
508
575
 
509
576
  OpenSportsLib is developed within the broader OpenSportsLab effort for sports video understanding.
577
+
578
+ ## Inference server
579
+
580
+ The optional FastAPI + Redis/RQ inference server lives in [`server/`](server/README.md),
581
+ beside the main library package. It supports classification, localization, VQA,
582
+ video/manifest uploads, and the library's remote inference client.
583
+
584
+ `pip install opensportslib` installs the library only. To run the server from
585
+ this repository, activate a fresh Python 3.12 or newer environment and install the server:
586
+
587
+ ```bash
588
+ pip install -e ./server
589
+ bash server/scripts/setup_env.sh
590
+ bash server/scripts/start_all.sh
591
+ ```
592
+
593
+ The server installs the OpenSportsLib release from PyPI pinned to the root project
594
+ version. That release must be published before installing or building the server.
595
+
596
+ See the [server guide](server/README.md) for uv setup, model configuration,
597
+ Redis and GPU deployment with Docker Compose. Server dependencies
598
+ and runtime data are managed separately from the library.
@@ -1,6 +1,7 @@
1
1
  # opensportslib/apis/__init__.py
2
2
 
3
3
  # Import task APIs
4
+ from opensportslib.apis.config import Config
4
5
  from opensportslib.apis.base_task_model import BaseTaskModel
5
6
  from opensportslib.apis.classification import ClassificationModel
6
7
  from opensportslib.apis.localization import LocalizationModel
@@ -10,6 +11,7 @@ warnings.filterwarnings("ignore")
10
11
 
11
12
  # Expose only these
12
13
  __all__ = [
14
+ "Config",
13
15
  "BaseTaskModel",
14
16
  "ClassificationModel",
15
17
  "LocalizationModel",
@@ -16,10 +16,12 @@ from typing import Any
16
16
  from urllib import error as urlerror
17
17
  from urllib import request as urlrequest
18
18
 
19
+ from opensportslib.apis.configuration import ConfigurationMixin
19
20
  from opensportslib.core.config.accessors import get_component_name_by_kind
21
+ from opensportslib.core.config.editable import Config
22
+ from opensportslib.core.config.runtime_adapter import dict_to_namespace
20
23
  from opensportslib.core.utils.config import (
21
24
  expand,
22
- load_config_omega,
23
25
  fetch_and_merge_config_from_HF,
24
26
  resolve_config_path,
25
27
  resolve_inference_class_metadata,
@@ -55,8 +57,7 @@ def _manifest_media_references(payload: dict[str, Any]):
55
57
  if isinstance(value, str):
56
58
  yield value, lambda replacement, values=values, index=index: values.__setitem__(index, replacement)
57
59
 
58
-
59
- class BaseTaskModel(ABC):
60
+ class BaseTaskModel(ConfigurationMixin, ABC):
60
61
  """Thin shared contract for task-level OpenSportsLib wrappers."""
61
62
 
62
63
  def __init__(
@@ -78,11 +79,38 @@ class BaseTaskModel(ABC):
78
79
  if self.remote_timeout <= 0 or self.remote_poll_interval <= 0 or self.remote_result_timeout <= 0:
79
80
  raise ValueError("Remote timeout values must be positive.")
80
81
 
81
- if config is None:
82
- raise ValueError("config path is required")
82
+ self._config_editor = copy.deepcopy(config) if isinstance(config, Config) else None
83
+ if self._config_editor is not None:
84
+ weights = weights if weights is not None else self._config_editor.weights
85
+ config = self._config_editor.source
86
+ if self.is_remote:
87
+ self._config_editor.remote_overrides()
88
+
89
+ if config is None and self._config_editor is None:
90
+ from huggingface_hub.utils import HFValidationError, validate_repo_id
83
91
 
84
- self.config_path = resolve_config_path(config)
85
- self.config = load_config_omega(self.config_path)
92
+ if (not isinstance(weights, str) or os.path.exists(expand(weights))
93
+ or weights.endswith((".pt", ".pth", ".tar"))):
94
+ raise ValueError("config path is required unless weights is a Hugging Face model ID")
95
+ try:
96
+ validate_repo_id(weights)
97
+ except HFValidationError as exc:
98
+ raise ValueError("config path is required unless weights is a Hugging Face model ID") from exc
99
+ try:
100
+ config = resolve_config_path(weights)
101
+ except Exception as exc:
102
+ raise ValueError(
103
+ f"Could not load OpenSportsLib config.yaml from {weights!r}; "
104
+ "provide config explicitly or publish a compatible config.yaml."
105
+ ) from exc
106
+
107
+ if self._config_editor is not None:
108
+ self.config_path = self._config_editor.source
109
+ self.config = dict_to_namespace(self._config_editor.get_config())
110
+ else:
111
+ self.config_path = resolve_config_path(config)
112
+ self._config_editor = Config.from_file(self.config_path)
113
+ self.config = dict_to_namespace(self._config_editor.get_config())
86
114
  self.last_loaded_weights = None
87
115
  self.best_checkpoint = None
88
116
 
@@ -96,6 +124,7 @@ class BaseTaskModel(ABC):
96
124
  self.last_loaded_weights = weights
97
125
  self.best_checkpoint = weights
98
126
 
127
+ self.config = self._effective_config(self.config)
99
128
  self.train_flag = False # Flag to indicate whether we're in training mode (affects checkpoint loading behavior)
100
129
 
101
130
  data_cfg = getattr(self.config, "DATA", None)
@@ -253,7 +282,7 @@ class BaseTaskModel(ABC):
253
282
  model_id: str | None = None,
254
283
  task_options: dict[str, Any] | None = None,
255
284
  ) -> dict[str, Any]:
256
- """Submit a single uploaded video, primarily for direct VQA inference."""
285
+ """Submit one video for direct task inference."""
257
286
 
258
287
  if not self.remote:
259
288
  raise RuntimeError("Remote inference is not configured. Pass `remote=` to the model constructor.")
@@ -405,6 +434,8 @@ class BaseTaskModel(ABC):
405
434
  return self._open_request(request)
406
435
 
407
436
  def _post_multipart(self, endpoint: str, *, fields: dict[str, str], files: dict[str, Path]) -> dict[str, Any]:
437
+ if endpoint == "/predict":
438
+ fields = self._config_request_fields(fields)
408
439
  boundary = f"----OpenSportsLib{uuid.uuid4().hex}"
409
440
  chunks: list[bytes] = []
410
441
  for name, value in fields.items():
@@ -7,6 +7,7 @@ import os
7
7
  import json
8
8
 
9
9
  from opensportslib.apis.base_task_model import BaseTaskModel
10
+ from opensportslib.apis.configuration import config_operation
10
11
  from opensportslib.core.config.accessors import (
11
12
  get_component_provider_by_kind,
12
13
  get_data_modality,
@@ -180,6 +181,7 @@ class ClassificationModel(BaseTaskModel):
180
181
  # public training interface
181
182
  # -----------------------------------------------------------------
182
183
 
184
+ @config_operation
183
185
  def train(
184
186
  self,
185
187
  train_set=None,
@@ -200,7 +202,7 @@ class ClassificationModel(BaseTaskModel):
200
202
  train_set = self._resolve_split_path("train", train_set)
201
203
  valid_set = self._resolve_split_path("valid", valid_set)
202
204
 
203
- self.config = resolve_config_omega(self.config, weights=weights)
205
+ self.config = self._effective_config(resolve_config_omega(self.config, weights=weights))
204
206
  logging.info("Configuration:")
205
207
  logging.info(self.config)
206
208
 
@@ -251,15 +253,19 @@ class ClassificationModel(BaseTaskModel):
251
253
  self.last_loaded_weights = self.best_checkpoint
252
254
  return self.best_checkpoint
253
255
 
256
+ @config_operation
254
257
  def infer(
255
258
  self,
256
259
  test_set=None,
257
260
  weights=None,
258
261
  use_ddp=False,
259
262
  use_wandb=True,
263
+ video_path: str | None = None,
260
264
  **kwargs,
261
265
  ):
262
266
  """Run model inference and return predictions in OSL JSON format."""
267
+ if test_set is not None and video_path is not None:
268
+ raise ValueError("Provide either `test_set` or `video_path`, not both.")
263
269
  remote_mode_provided = "remote_mode" in kwargs
264
270
  remote_mode = kwargs.pop("remote_mode", "full_test_set")
265
271
  if self.is_remote:
@@ -267,6 +273,17 @@ class ClassificationModel(BaseTaskModel):
267
273
  remote_task_options = kwargs.pop("remote_task_options", None)
268
274
  if kwargs:
269
275
  raise TypeError(f"Unsupported remote inference options: {', '.join(kwargs)}")
276
+ if video_path is not None:
277
+ if remote_mode != "full_test_set":
278
+ raise ValueError("`remote_mode=per_sample` requires `test_set`, not direct video input.")
279
+ job = self.submit_video_inference(
280
+ task_type="classification",
281
+ video_path=video_path,
282
+ model_id=remote_model_id,
283
+ task_options=remote_task_options,
284
+ )
285
+ self.last_remote_failures = []
286
+ return self.wait_for_remote_result(job["job_id"])["result"]["predictions"]
270
287
  test_set = self._resolve_split_path("test", test_set)
271
288
  if remote_mode == "per_sample":
272
289
  batch = self.submit_per_sample_inference(
@@ -294,13 +311,27 @@ class ClassificationModel(BaseTaskModel):
294
311
  raise ValueError("`remote_mode` is available only when `remote` is configured.")
295
312
  del kwargs
296
313
 
314
+ if video_path is not None:
315
+ from opensportslib.core.utils.direct_video import direct_video_manifest
316
+ from opensportslib.core.utils.config import resolve_config_omega
317
+
318
+ manifest_config = self._effective_config(resolve_config_omega(self.config, weights=weights))
319
+ manifest_config = resolve_inference_class_metadata(manifest_config)
320
+ with direct_video_manifest(manifest_config, video_path, "classification") as manifest:
321
+ return self.infer(
322
+ test_set=manifest,
323
+ weights=weights,
324
+ use_ddp=use_ddp,
325
+ use_wandb=use_wandb,
326
+ )
327
+
297
328
  import torch
298
329
  import torch.multiprocessing as mp
299
330
  from opensportslib.core.utils.config import resolve_config_omega
300
331
 
301
332
  test_set = self._resolve_split_path("test", test_set)
302
333
 
303
- self.config = resolve_config_omega(self.config, weights=weights)
334
+ self.config = self._effective_config(resolve_config_omega(self.config, weights=weights))
304
335
  self.config = resolve_inference_class_metadata(self.config)
305
336
  logging.info("Configuration:")
306
337
  logging.info(self.config)
@@ -349,6 +380,7 @@ class ClassificationModel(BaseTaskModel):
349
380
  predictions = json.load(f)
350
381
  return predictions
351
382
 
383
+ @config_operation
352
384
  def evaluate(
353
385
  self,
354
386
  test_set=None,
@@ -367,7 +399,7 @@ class ClassificationModel(BaseTaskModel):
367
399
 
368
400
  test_set = self._resolve_split_path("test", test_set)
369
401
 
370
- self.config = resolve_config_omega(self.config, weights=weights)
402
+ self.config = self._effective_config(resolve_config_omega(self.config, weights=weights))
371
403
  self.config = resolve_inference_class_metadata(self.config)
372
404
  logging.info("Configuration:")
373
405
  logging.info(self.config)
@@ -0,0 +1,5 @@
1
+ """Public configuration API."""
2
+
3
+ from opensportslib.core.config.editable import Config
4
+
5
+ __all__ = ["Config"]
@@ -0,0 +1,145 @@
1
+ """Configuration lifecycle shared by task wrappers."""
2
+
3
+ from copy import deepcopy
4
+ from functools import wraps
5
+ import inspect
6
+ import os
7
+ import json
8
+ from threading import RLock
9
+ from urllib.parse import urlencode
10
+ from urllib.request import Request
11
+
12
+ from opensportslib.core.config.editable import Config, _get, _set
13
+ from opensportslib.core.config.runtime_adapter import dict_to_namespace, namespace_to_plain_dict
14
+
15
+
16
+ def config_operation(method):
17
+ """Serialize operations and prevent edits while an operation is active."""
18
+ @wraps(method)
19
+ def wrapped(self, *args, **kwargs):
20
+ lock = self.__dict__.setdefault("_config_lock", RLock())
21
+ with lock:
22
+ depth = getattr(self, "_operation_depth", 0)
23
+ self._operation_depth = depth + 1
24
+ before = namespace_to_plain_dict(self.config)
25
+ previous_inputs = getattr(self, "_call_config_inputs", {})
26
+ bound = inspect.signature(method).bind(self, *args, **kwargs)
27
+ self._call_config_inputs = {**previous_inputs, **{
28
+ name.removesuffix("_set"): os.path.abspath(os.path.expanduser(str(value)))
29
+ for name, value in bound.arguments.items()
30
+ if name in {"train_set", "valid_set", "test_set"} and value is not None
31
+ }}
32
+ try:
33
+ if method.__name__ == "train" and self.is_remote:
34
+ raise ValueError("Remote training is not supported")
35
+ return method(self, *args, **kwargs)
36
+ finally:
37
+ self._operation_depth = depth
38
+ self._call_config_inputs = previous_inputs
39
+ if depth == 0:
40
+ # Local localization helpers write temporary input paths into
41
+ # config. Restore only those defaults, preserving model state.
42
+ current = namespace_to_plain_dict(self.config)
43
+ for split, cfg in _get(before, "DATA.common.splits", {}).items():
44
+ for key in ("annotation_path", "source_path"):
45
+ if key in cfg:
46
+ _set(current, f"DATA.common.splits.{split}.{key}", cfg[key])
47
+ self.config = dict_to_namespace(current)
48
+ self._refresh_config_references()
49
+ return wrapped
50
+
51
+
52
+ class ConfigurationMixin:
53
+ def _effective_config(self, config):
54
+ editor = getattr(self, "_config_editor", None)
55
+ result = dict_to_namespace(editor.apply_to(config)) if editor is not None else config
56
+ if getattr(self, "_call_config_inputs", None):
57
+ doc = namespace_to_plain_dict(result)
58
+ for split, path in self._call_config_inputs.items():
59
+ _set(doc, f"DATA.common.splits.{split}.annotation_path", path)
60
+ if split == "valid" and _get(doc, "DATA.common.splits.valid_data_frames"):
61
+ _set(doc, "DATA.common.splits.valid_data_frames.annotation_path", path)
62
+ result = dict_to_namespace(doc)
63
+ return result
64
+
65
+ def _refresh_config_references(self):
66
+ for owner in (getattr(self, "model", None), getattr(self, "trainer", None)):
67
+ if owner is None:
68
+ continue
69
+ for key in ("config", "cfg", "cfg_model"):
70
+ if hasattr(owner, key):
71
+ setattr(owner, key, self.config)
72
+
73
+ def get_config(self):
74
+ return deepcopy(namespace_to_plain_dict(self.config))
75
+
76
+ def config_options(self):
77
+ editor = deepcopy(getattr(self, "_config_editor", None)) or Config(self.get_config(), source=self.config_path)
78
+ # Inspection reflects normalization and runtime-selected checkpoint data.
79
+ editor._document = self.get_config()
80
+ return editor.options()
81
+
82
+ def update_config(self, **options):
83
+ lock = self.__dict__.setdefault("_config_lock", RLock())
84
+ if not lock.acquire(blocking=False):
85
+ raise RuntimeError("Cannot update configuration during an active operation")
86
+ try:
87
+ if getattr(self, "_operation_depth", 0):
88
+ raise RuntimeError("Cannot update configuration during an active operation")
89
+ original = getattr(self, "_config_editor", None)
90
+ editor = deepcopy(original) if original is not None else Config(self.get_config(), source=self.config_path)
91
+ # Carry current runtime values while retaining expressions and prior
92
+ # explicit updates from the editable source.
93
+ current = self.get_config()
94
+ editor._document = editor.apply_to(current)
95
+ if original is not None:
96
+ from opensportslib.core.config.editable import _leaves
97
+ for path, value in _leaves(original._document):
98
+ if isinstance(value, str) and "${" in value:
99
+ _set(editor._document, path, value)
100
+ previous = deepcopy(editor._updates)
101
+ editor.update(**options)
102
+ safe = []
103
+ for name, spec in editor._registry().items():
104
+ if not spec.requires_initialization and name.split(".")[0] in {"data", "training", "inference", "runtime", "scheduler", "training_sampling", "sft", "prompt"}:
105
+ safe.extend(spec.paths)
106
+ changed = [p for p, value in editor._updates.items() if p not in previous or previous[p] != value]
107
+ for path in changed:
108
+ if not any(path == p for p in safe):
109
+ raise ValueError(f"{path} requires a new model; update Config before initialization")
110
+ if self.is_remote:
111
+ editor.remote_overrides()
112
+ candidate = dict_to_namespace(editor.apply_to(current))
113
+ self.config = candidate
114
+ self._config_editor = editor
115
+ self._refresh_config_references()
116
+ return self
117
+ finally:
118
+ lock.release()
119
+
120
+ def _config_request_fields(self, fields):
121
+ fields = dict(fields)
122
+ editor = getattr(self, "_config_editor", None)
123
+ overrides = editor.remote_overrides() if editor is not None else {}
124
+ task_options = json.loads(fields.get("task_options") or "{}")
125
+ if "config_overrides" in task_options:
126
+ raise ValueError("Use Config.update(inference=...) for remote configuration overrides")
127
+ if not overrides:
128
+ return fields
129
+ query = urlencode({"model_id": fields.get("model_id", ""), "task_type": fields.get("task_type", "")})
130
+ try:
131
+ capabilities = self._open_request(Request(f"{self.remote}/config-capabilities?{query}"))
132
+ except Exception as exc:
133
+ raise ValueError("Server does not expose configuration capabilities; upgrade the server or omit overrides") from exc
134
+ if capabilities.get("version") != 1:
135
+ raise ValueError("Unsupported server configuration protocol version")
136
+ allowed = capabilities.get("options", {})
137
+ for name, value in overrides.items():
138
+ if name not in allowed:
139
+ raise ValueError(f"Server does not support inference.{name}")
140
+ spec = allowed[name]
141
+ if spec.get("minimum") is not None and value < spec["minimum"] or spec.get("maximum") is not None and value > spec["maximum"]:
142
+ raise ValueError(f"inference.{name} exceeds server limits")
143
+ task_options["config_overrides"] = {"version": 1, "inference": overrides}
144
+ fields["task_options"] = json.dumps(task_options)
145
+ return fields