hyperstudy-perception 0.1.2__tar.gz → 0.1.4__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 (284) hide show
  1. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/PKG-INFO +1 -1
  2. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/pyproject.toml +1 -1
  3. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/RELEASE.json +2 -1
  4. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/runpod/image/Dockerfile +4 -3
  5. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody-captions/node.py +44 -24
  6. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody-captions/style_captions.py +57 -3
  7. hyperstudy_perception-0.1.4/src/perception/_pipeline/nodes/audio/prosody-captions/test_prosody_captions_node.py +107 -0
  8. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody-captions/test_style_captions.py +55 -2
  9. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/manifest.yaml +5 -3
  10. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/node.py +7 -1
  11. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/backends/base.py +9 -3
  12. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/backends/cloud.py +22 -2
  13. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/backends/native.py +6 -1
  14. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/cli.py +304 -19
  15. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/hosts.py +11 -0
  16. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/plan.py +3 -1
  17. hyperstudy_perception-0.1.4/src/perception/pod_run.py +238 -0
  18. hyperstudy_perception-0.1.4/src/perception/pod_session.py +272 -0
  19. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/prepare_first.py +9 -3
  20. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/remote_coordinator.py +26 -0
  21. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/remote_driver.py +35 -200
  22. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/remote_run.py +50 -30
  23. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/LICENSE +0 -0
  24. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/README.md +0 -0
  25. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/__init__.py +0 -0
  26. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/runpod/guard.sh +0 -0
  27. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/runpod/image/start.sh +0 -0
  28. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/runpod/image/tag.py +0 -0
  29. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/skypilot/gpu-faces.yaml +0 -0
  30. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/skypilot/gpu-tune.yaml +0 -0
  31. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/skypilot/guard.sh +0 -0
  32. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/skypilot/iam-perception-node.json +0 -0
  33. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/skypilot/launch.sh +0 -0
  34. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/skypilot/node-gpu.yaml +0 -0
  35. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/skypilot/node-setup.sh +0 -0
  36. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/skypilot/sky.yaml +0 -0
  37. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/cloud/skypilot/smoke.yaml +0 -0
  38. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/api/pyproject.toml +0 -0
  39. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/api/uv.lock +0 -0
  40. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/asr-cuda/pyproject.toml +0 -0
  41. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/asr-cuda/uv.lock +0 -0
  42. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/audio/pyproject.toml +0 -0
  43. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/audio/uv.lock +0 -0
  44. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/audio-events/pyproject.toml +0 -0
  45. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/audio-events/uv.lock +0 -0
  46. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/core/pyproject.toml +0 -0
  47. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/core/uv.lock +0 -0
  48. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/faces/pyproject.toml +0 -0
  49. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/faces/uv.lock +0 -0
  50. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/gaze/pyproject.toml +0 -0
  51. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/gaze/uv.lock +0 -0
  52. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/prosody/pyproject.toml +0 -0
  53. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/prosody/uv.lock +0 -0
  54. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/vision/pyproject.toml +0 -0
  55. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/envs/vision/uv.lock +0 -0
  56. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/graphs/decode.yaml +0 -0
  57. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/graphs/faces.yaml +0 -0
  58. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/graphs/full.yaml +0 -0
  59. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/graphs/mvp-captions.yaml +0 -0
  60. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/graphs/mvp.yaml +0 -0
  61. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/manifest.yaml +0 -0
  62. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/nemo_model.py +0 -0
  63. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/nemo_tokens.py +0 -0
  64. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/node.py +0 -0
  65. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/node_nemo.py +0 -0
  66. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/prepare.py +0 -0
  67. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/raw_tokens.py +0 -0
  68. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/test_nemo_model.py +0 -0
  69. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/test_nemo_tokens.py +0 -0
  70. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/test_raw_tokens.py +0 -0
  71. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/test_wav.py +0 -0
  72. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/utterances.py +0 -0
  73. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/asr/wav.py +0 -0
  74. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/diarization_raw.py +0 -0
  75. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/diarizer.py +0 -0
  76. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/manifest.yaml +0 -0
  77. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/node.py +0 -0
  78. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/pcm16.py +0 -0
  79. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/prepare.py +0 -0
  80. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/test_diarization_raw.py +0 -0
  81. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/test_diarizer.py +0 -0
  82. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/test_pcm16.py +0 -0
  83. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/test_voices.py +0 -0
  84. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/diarize/voices.py +0 -0
  85. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/manifest.yaml +0 -0
  86. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/node.py +0 -0
  87. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/odyssey.py +0 -0
  88. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/praat.py +0 -0
  89. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/prepare.py +0 -0
  90. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/prosody_layer.py +0 -0
  91. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/prosody_measure.py +0 -0
  92. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/prosody_tracks.py +0 -0
  93. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/prosody_units.py +0 -0
  94. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/test_prosody_layer.py +0 -0
  95. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/test_prosody_measure.py +0 -0
  96. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/test_prosody_tracks.py +0 -0
  97. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/test_prosody_units.py +0 -0
  98. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/third_party/README.md +0 -0
  99. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/third_party/odyssey/LICENSE +0 -0
  100. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody/third_party/odyssey/ser_model.py +0 -0
  101. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody-captions/captions_raw.py +0 -0
  102. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/prosody-captions/manifest.yaml +0 -0
  103. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/audioset_labels.csv +0 -0
  104. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/efficientat/LICENSE +0 -0
  105. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/efficientat/UPSTREAM.md +0 -0
  106. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/efficientat/__init__.py +0 -0
  107. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/__init__.py +0 -0
  108. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/attention_pooling.py +0 -0
  109. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/block_types.py +0 -0
  110. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/model.py +0 -0
  111. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/utils.py +0 -0
  112. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/efficientat/preprocess.py +0 -0
  113. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/models.py +0 -0
  114. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/panns/LICENSE.MIT +0 -0
  115. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/panns/UPSTREAM.md +0 -0
  116. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/panns/__init__.py +0 -0
  117. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/panns/cnn14.py +0 -0
  118. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/panns/pytorch_utils.py +0 -0
  119. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/prepare.py +0 -0
  120. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/sound_events.py +0 -0
  121. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/sound_scores.py +0 -0
  122. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/soundtrack.py +0 -0
  123. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/test_sound_events.py +0 -0
  124. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/test_sound_scores.py +0 -0
  125. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/sounds/test_soundtrack.py +0 -0
  126. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/waveform/manifest.yaml +0 -0
  127. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/waveform/node.py +0 -0
  128. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/waveform/test_waveform_peaks.py +0 -0
  129. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/audio/waveform/waveform_peaks.py +0 -0
  130. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/conftest.py +0 -0
  131. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/core/decode/manifest.yaml +0 -0
  132. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/core/decode/media.py +0 -0
  133. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/core/decode/node.py +0 -0
  134. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/core/decode/shot_scores.py +0 -0
  135. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/core/decode/test_media.py +0 -0
  136. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/core/decode/test_shot_scores.py +0 -0
  137. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/testdata/README.md +0 -0
  138. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/testdata/rotated-90.mp4 +0 -0
  139. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/asd_crops.py +0 -0
  140. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/asd_raw.py +0 -0
  141. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/manifest.yaml +0 -0
  142. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/models.py +0 -0
  143. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/node.py +0 -0
  144. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/prepare.py +0 -0
  145. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/speaker_links.py +0 -0
  146. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/test_asd_crops.py +0 -0
  147. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/test_asd_raw.py +0 -0
  148. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/test_speaker_links.py +0 -0
  149. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/README.md +0 -0
  150. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/LICENSE +0 -0
  151. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/loss.py +0 -0
  152. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/model/Classifier.py +0 -0
  153. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/model/Encoder.py +0 -0
  154. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/model/Model.py +0 -0
  155. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/active-speaker/video25.py +0 -0
  156. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/body-pose/bodies.py +0 -0
  157. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/body-pose/detections.py +0 -0
  158. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/body-pose/keypoints.json +0 -0
  159. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/body-pose/manifest.yaml +0 -0
  160. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/body-pose/models.py +0 -0
  161. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/body-pose/node.py +0 -0
  162. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/body-pose/prepare.py +0 -0
  163. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/body-pose/test_bodies.py +0 -0
  164. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/body-pose/test_detections.py +0 -0
  165. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/face_detail.py +0 -0
  166. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/frames.py +0 -0
  167. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/loader.py +0 -0
  168. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/manifest.yaml +0 -0
  169. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/node.py +0 -0
  170. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/prepare.py +0 -0
  171. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/pyfeat_raw.py +0 -0
  172. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/test_face_detail.py +0 -0
  173. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/test_frames.py +0 -0
  174. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/test_loader.py +0 -0
  175. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/test_pyfeat_raw.py +0 -0
  176. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/test_thumbnails.py +0 -0
  177. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/thumbnails.py +0 -0
  178. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/face-track/tracking.py +0 -0
  179. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/facing/facing.py +0 -0
  180. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/facing/facing_raw.py +0 -0
  181. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/facing/manifest.yaml +0 -0
  182. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/facing/models.py +0 -0
  183. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/facing/node.py +0 -0
  184. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/facing/prepare.py +0 -0
  185. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/facing/test_facing.py +0 -0
  186. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/facing/test_facing_raw.py +0 -0
  187. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/gaze_polygons.py +0 -0
  188. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/gaze_targets.py +0 -0
  189. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/heatmaps.py +0 -0
  190. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/manifest.yaml +0 -0
  191. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/models.py +0 -0
  192. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/node.py +0 -0
  193. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/prepare.py +0 -0
  194. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/test_gaze_polygons.py +0 -0
  195. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/test_gaze_targets.py +0 -0
  196. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/test_heatmaps.py +0 -0
  197. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/README.md +0 -0
  198. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/LICENSE +0 -0
  199. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/__init__.py +0 -0
  200. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/__init__.py +0 -0
  201. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/attention.py +0 -0
  202. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/block.py +0 -0
  203. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/dino_head.py +0 -0
  204. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/drop_path.py +0 -0
  205. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/layer_scale.py +0 -0
  206. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/mlp.py +0 -0
  207. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/patch_embed.py +0 -0
  208. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/swiglu_ffn.py +0 -0
  209. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/models/__init__.py +0 -0
  210. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/models/vision_transformer.py +0 -0
  211. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/gazelle/LICENSE +0 -0
  212. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/gazelle/gazelle/backbone.py +0 -0
  213. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/gazelle/gazelle/model.py +0 -0
  214. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gaze/third_party/gazelle/gazelle/utils.py +0 -0
  215. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gestures/gesture_evidence.py +0 -0
  216. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gestures/gesture_inputs.py +0 -0
  217. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gestures/gesture_rules.py +0 -0
  218. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gestures/manifest.yaml +0 -0
  219. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gestures/node.py +0 -0
  220. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gestures/test_gesture_evidence.py +0 -0
  221. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/gestures/test_gesture_rules.py +0 -0
  222. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/hand_detections.py +0 -0
  223. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/hand_tracks.py +0 -0
  224. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/manifest.yaml +0 -0
  225. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/models.py +0 -0
  226. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/node.py +0 -0
  227. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/prepare.py +0 -0
  228. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/scipy_lsap/LICENSE +0 -0
  229. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/scipy_lsap/UPSTREAM.md +0 -0
  230. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/scipy_lsap/__init__.py +0 -0
  231. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/scipy_lsap/rectangular_lsap.py +0 -0
  232. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/test_hand_detections.py +0 -0
  233. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/test_hand_tracks.py +0 -0
  234. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/test_node.py +0 -0
  235. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/hands/test_rectangular_lsap.py +0 -0
  236. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/budget.py +0 -0
  237. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/garments.py +0 -0
  238. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/geometry.py +0 -0
  239. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/haiku.py +0 -0
  240. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/link.py +0 -0
  241. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/linking.py +0 -0
  242. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/models.py +0 -0
  243. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/names.py +0 -0
  244. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/object_raw.py +0 -0
  245. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/object_tracks.py +0 -0
  246. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/person_masks.py +0 -0
  247. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/prepare.py +0 -0
  248. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/shot_frames.py +0 -0
  249. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/test_garments.py +0 -0
  250. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/test_models_hub.py +0 -0
  251. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/test_object_budget.py +0 -0
  252. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/test_object_links.py +0 -0
  253. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/test_object_raw.py +0 -0
  254. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/test_person_masks.py +0 -0
  255. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/test_shot_frames.py +0 -0
  256. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/test_split.py +0 -0
  257. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/testdata/node_1_1_0.py +0 -0
  258. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/track.py +0 -0
  259. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects/vocabulary.py +0 -0
  260. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects-link/manifest.yaml +0 -0
  261. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects-names/manifest.yaml +0 -0
  262. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vision/objects-track/manifest.yaml +0 -0
  263. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vlm/shot-captions/manifest.yaml +0 -0
  264. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/nodes/vlm/shot-captions/node.py +0 -0
  265. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/schema/README.md +0 -0
  266. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/schema/bundle-0.1.schema.json +0 -0
  267. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/sdk/pyproject.toml +0 -0
  268. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/sdk/src/perception_node/__init__.py +0 -0
  269. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/_pipeline/sdk/src/perception_node/weights.py +0 -0
  270. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/artifacts.py +0 -0
  271. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/backends/__init__.py +0 -0
  272. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/backends/probe.py +0 -0
  273. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/backends/remote.py +0 -0
  274. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/estimates.py +0 -0
  275. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/export.py +0 -0
  276. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/ir.py +0 -0
  277. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/keycheck.py +0 -0
  278. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/keys.py +0 -0
  279. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/manifests.py +0 -0
  280. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/release.py +0 -0
  281. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/runpod.py +0 -0
  282. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/scheduler.py +0 -0
  283. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/setup.py +0 -0
  284. {hyperstudy_perception-0.1.2 → hyperstudy_perception-0.1.4}/src/perception/store.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hyperstudy-perception
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Video perception pipeline: shots, speech, faces and captions from a video, as a versioned track bundle, run locally, on cloud GPUs or lab servers
5
5
  Project-URL: Homepage, https://github.com/hyperstudyio/hyperstudy-perception
6
6
  Project-URL: Changelog, https://github.com/hyperstudyio/hyperstudy-perception/releases
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hyperstudy-perception"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "Video perception pipeline: shots, speech, faces and captions from a video, as a versioned track bundle, run locally, on cloud GPUs or lab servers"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,5 +1,5 @@
1
1
  {
2
- "commit": "6301eca468f046c39fbf4a84f05e106ce6b12688",
2
+ "commit": "7a657d9319167d9fb6eb8374260e9d74d72365b2",
3
3
  "files": [
4
4
  "cloud/runpod/guard.sh",
5
5
  "cloud/runpod/image/Dockerfile",
@@ -65,6 +65,7 @@
65
65
  "nodes/audio/prosody-captions/manifest.yaml",
66
66
  "nodes/audio/prosody-captions/node.py",
67
67
  "nodes/audio/prosody-captions/style_captions.py",
68
+ "nodes/audio/prosody-captions/test_prosody_captions_node.py",
68
69
  "nodes/audio/prosody-captions/test_style_captions.py",
69
70
  "nodes/audio/prosody/manifest.yaml",
70
71
  "nodes/audio/prosody/node.py",
@@ -8,10 +8,11 @@
8
8
  FROM runpod/pytorch:2.1.0-py3.10-cuda11.8.0-devel-ubuntu22.04@sha256:2594f25beeef751aeebcc207ba4c45a382ba174655f01e15cbbfad0e2bdb5ef8
9
9
  ENV DEBIAN_FRONTEND=noninteractive
10
10
 
11
- # what nodes and the driver use (setup then installs nothing); sshd takes keys only. Each pod makes its own host keys
12
- # at start (start.sh): the ones in the base image are public, and are never used
11
+ # what nodes and the driver use (setup then installs nothing): ffmpeg, rsync, and the EGL and GLES libraries MediaPipe
12
+ # loads even on the CPU (vision.facing failed for want of libEGL.so.1 on a pod, 2026-10-31). sshd takes keys only.
13
+ # Each pod makes its own host keys at start (start.sh): the ones in the base image are public, and are never used
13
14
  RUN apt-get update \
14
- && apt-get install -y --no-install-recommends ffmpeg rsync \
15
+ && apt-get install -y --no-install-recommends ffmpeg rsync libegl1 libgles2 \
15
16
  && rm -rf /var/lib/apt/lists/* /etc/ssh/ssh_host_* \
16
17
  && mkdir -p /run/sshd /etc/ssh/sshd_config.d \
17
18
  && printf 'PermitRootLogin prohibit-password\nPasswordAuthentication no\nKbdInteractiveAuthentication no\n' \
@@ -80,28 +80,25 @@ with Node() as node:
80
80
  u = units[k]
81
81
  i, j = u["samples"]
82
82
  text = sc.prompt(units, k)
83
- r = call(lambda: client.chat.completions.create(
84
- model=sc.GPT_MODEL, modalities=["text"],
85
- tools=[{"type": "function", "function": {"name": "record_style", "description": "Record how the target is said",
86
- "parameters": sc.CAPTION_SCHEMA, "strict": True}}],
87
- tool_choice={"type": "function", "function": {"name": "record_style"}},
88
- messages=[{"role": "system", "content": sc.GPT_INSTRUCTIONS},
89
- {"role": "user", "content": [{"type": "text", "text": text},
90
- {"type": "input_audio", "input_audio": {"data": wav_b64(pcm[2 * i:2 * j]), "format": "wav"}}]}]))
91
- d = r.usage.prompt_tokens_details
92
- usage = sc.cost(sc.GPT_MODEL, r.usage.prompt_tokens, d.audio_tokens if d else 0, r.usage.completion_tokens)
93
- spend(usage)
94
- choice = r.choices[0]
95
- if not choice.message.tool_calls:
96
- raise NodeError("internal", f"{u['id']}: no function call in the reply (finish_reason {choice.finish_reason})")
97
- arguments = choice.message.tool_calls[0].function.arguments
98
- try:
99
- parsed = sc.parse_caption(arguments)
100
- except ValueError as e:
101
- raise NodeError("internal", f"{u['id']}: {e}") from None
102
- return {"parsed": parsed, "usage": usage, "prompt": text,
103
- "raw": {"model": r.model, "finish_reason": choice.finish_reason, "arguments": arguments,
104
- "usage": r.usage.model_dump(mode="json")}}
83
+
84
+ def ask():
85
+ r = call(lambda: client.chat.completions.create(
86
+ model=sc.GPT_MODEL, modalities=["text"],
87
+ tools=[{"type": "function", "function": {"name": "record_style", "description": "Record how the target is said",
88
+ "parameters": sc.CAPTION_SCHEMA, "strict": True}}],
89
+ tool_choice={"type": "function", "function": {"name": "record_style"}},
90
+ messages=[{"role": "system", "content": sc.GPT_INSTRUCTIONS},
91
+ {"role": "user", "content": [{"type": "text", "text": text},
92
+ {"type": "input_audio", "input_audio": {"data": wav_b64(pcm[2 * i:2 * j]), "format": "wav"}}]}]))
93
+ d = r.usage.prompt_tokens_details
94
+ usage = sc.cost(sc.GPT_MODEL, r.usage.prompt_tokens, d.audio_tokens if d else 0, r.usage.completion_tokens)
95
+ spend(usage)
96
+ choice = r.choices[0]
97
+ arguments = choice.message.tool_calls[0].function.arguments if choice.message.tool_calls else None
98
+ return arguments, usage, {"model": r.model, "finish_reason": choice.finish_reason, "arguments": arguments,
99
+ "usage": r.usage.model_dump(mode="json")}
100
+ got = sc.caption_attempt(ask)
101
+ return {**got, "prompt": text, "ask": ask, "asked_again": []}
105
102
 
106
103
  def tag(description: str) -> dict:
107
104
  r = call(lambda: client.chat.completions.create(
@@ -125,8 +122,26 @@ with Node() as node:
125
122
  captions = sc.fan_out(caption, range(len(units))) # on a failure, no further call starts
126
123
  caption_s = round(time.time() - t0, 1)
127
124
  t0 = time.time()
128
- tagged = sc.fan_out(lambda c: tag(c["parsed"]["description"]), captions)
125
+ good = [k for k, c in enumerate(captions) if "rejected" not in c]
126
+ tagged = dict(zip(good, sc.fan_out(lambda k: tag(captions[k]["parsed"]["description"]), good)))
129
127
  tagger_s = round(time.time() - t0, 1)
128
+ # a reply that was not the schema's, asked again once every other call is made: then what the step spent is all but
129
+ # this second call and its tag, which must stay within its reservation (style_captions.may_ask_again)
130
+ for k, c in enumerate(captions):
131
+ if "rejected" not in c:
132
+ continue
133
+ tag_usd = max((t["usage"]["usd"] for t in tagged.values()), default=c["usage"]["usd"])
134
+ if not sc.may_ask_again(totals["usd"], c["usage"]["usd"], tag_usd, len(units)):
135
+ raise NodeError("internal", f"{units[k]['id']}: not asked again (the step's reservation would not cover it): "
136
+ f"{sc.rejected_text(c['rejected'])}")
137
+ try:
138
+ captions[k] = {**c, **sc.asked_again(c, sc.caption_attempt(c["ask"]))}
139
+ except ValueError as e:
140
+ raise NodeError("internal", f"{units[k]['id']}: {e}") from None
141
+ tagged[k] = tag(captions[k]["parsed"]["description"])
142
+ tagged = [tagged[k] for k in range(len(units))]
143
+ for c in captions:
144
+ c["outside"] = sc.outside_tags(c["arguments"])
130
145
 
131
146
  for m in prosody["producer"]["models"]: # the measures' and emotion's models, as the prosody node recorded them
132
147
  node.model(m["id"], licence_mode=m["licence_mode"], source=m.get("source"), revision=m.get("revision"), licence=m.get("licence"))
@@ -141,6 +156,11 @@ with Node() as node:
141
156
  node.write_json("prosody", "prosody.json", sc.refine(
142
157
  prosody, entries, node.producer(timings={"caption_s": caption_s, "tagger_s": tagger_s}, device="OpenAI API"),
143
158
  {"file": captions_raw.FILE, "format": "parquet", "description": captions_raw.DESCRIPTION}))
144
- node.usage(utterances=len(units), workers=sc.WORKERS,
159
+ # tags the audio model gave outside the list, dropped from their captions: utterance -> tags
160
+ outside = {u["id"]: c["outside"] for u, c in zip(units, captions) if c["outside"]}
161
+ # replies that were not the schema's JSON, asked again once (style_captions.caption_attempt, asked_again): utterance -> the first
162
+ # reply as it came (its problem, finish_reason and arguments), to find out why
163
+ asked_again = {u["id"]: c["asked_again"][0] for u, c in zip(units, captions) if c["asked_again"]}
164
+ node.usage(utterances=len(units), workers=sc.WORKERS, tags_outside_list=outside, asked_again=asked_again,
145
165
  usd_by_model={sc.GPT_MODEL: round(sum(c["usage"]["usd"] for c in captions), 4),
146
166
  sc.TAGGER: round(sum(t["usage"]["usd"] for t in tagged), 4)})
@@ -136,14 +136,68 @@ def cost(model: str, prompt_tokens: int, audio_tokens: int | None, completion_to
136
136
  return {"text_in": text_in, "audio_in": audio_in, "out": completion_tokens, "usd": usd}
137
137
 
138
138
 
139
+ def caption_attempt(ask) -> dict:
140
+ """One call for one target's caption: `ask()` makes it and gives (its function call's arguments, or None when it
141
+ made none; its usage, as cost() gives it; its raw reply). {parsed, arguments, raw, usage}, or {rejected: {problem,
142
+ finish_reason, arguments}, usage} when the reply is not the schema's. Why one can fail the schema is known: the
143
+ audio models do not constrain their function call to it (strict or not; the same reason off-list tags are dropped,
144
+ parse_caption), so a sample can be malformed (one was cut short of a comma on a pod's run, 2026-10-31)."""
145
+ arguments, usage, raw = ask()
146
+ try:
147
+ if arguments is None:
148
+ raise ValueError(f"no function call in the reply (finish_reason {raw.get('finish_reason')})")
149
+ return {"parsed": parse_caption(arguments), "arguments": arguments, "raw": raw, "usage": usage}
150
+ except ValueError as e:
151
+ return {"rejected": {"problem": str(e), "finish_reason": raw.get("finish_reason"), "arguments": arguments},
152
+ "usage": usage}
153
+
154
+
155
+ def rejected_text(rejected: dict) -> str:
156
+ return (f"{rejected['problem']} (finish_reason {rejected['finish_reason']}, "
157
+ f"arguments {str(rejected['arguments'])[:300]!r})")
158
+
159
+
160
+ def may_ask_again(spent_usd: float, caption_usd: float, tag_usd: float, utterances: int) -> bool:
161
+ """Whether a rejected caption may be asked again: once every other call of the step has been made (spent_usd is
162
+ then all but this), only if this caption's second call and its tag stay within what the step reserved (the
163
+ manifest's cost, an utterance: review of #53)."""
164
+ return spent_usd + caption_usd + tag_usd <= utterances * COST_PER_UTTERANCE
165
+
166
+
167
+ def asked_again(first: dict, second: dict) -> dict:
168
+ """A caption asked a second time: the second's, with both calls' usage and the first's rejected reply
169
+ (asked_again). ValueError when the second is not the schema's either."""
170
+ if "rejected" in second:
171
+ raise ValueError(f"asked twice, and neither reply was the schema's: {rejected_text(first['rejected'])}; "
172
+ f"{rejected_text(second['rejected'])}")
173
+ usage = {k: first["usage"][k] + second["usage"][k] for k in first["usage"]}
174
+ return {**second, "usage": usage, "asked_again": [first["rejected"]]}
175
+
176
+
177
+ # what the step reserves an utterance (manifest.yaml's cost; a test checks they agree): one caption call and its tags
178
+ COST_PER_UTTERANCE = 0.008
179
+
180
+
139
181
  def parse_caption(arguments: str) -> dict:
140
- """The function call's arguments -> {description, tags, emphasized_words}; ValueError when they are not that."""
182
+ """The function call's arguments -> {description, tags, emphasized_words}; ValueError when they are not that. Its tags
183
+ are those of the list only: gpt-audio-1.5 does not hold a function call to the schema's enum (strict or not), so a tag
184
+ outside it is dropped here (outside_tags says which; the side file keeps the reply as it came)."""
141
185
  import json
142
186
  out = json.loads(arguments)
143
187
  if not (isinstance(out, dict) and set(out) == {"description", "tags", "emphasized_words"}):
144
188
  raise ValueError(f"the caption is not {{description, tags, emphasized_words}}: {sorted(out) if isinstance(out, dict) else type(out).__name__}")
145
- check_tags(out["tags"])
146
- return out
189
+ if not isinstance(out["tags"], list):
190
+ check_tags(out["tags"]) # not a list: said as such
191
+ if not all(isinstance(t, str) for t in out["tags"]):
192
+ raise ValueError(f"the caption's tags are not strings: {[t for t in out['tags'] if not isinstance(t, str)][:5]}")
193
+ return {**out, "tags": [t for t in out["tags"] if t in ALL_TAGS]}
194
+
195
+
196
+ def outside_tags(arguments: str) -> list[str]:
197
+ """The tags a caption's reply gave that are not in the list (dropped by parse_caption)."""
198
+ import json
199
+ tags = json.loads(arguments).get("tags")
200
+ return [t for t in tags if t not in ALL_TAGS] if isinstance(tags, list) else []
147
201
 
148
202
 
149
203
  def parse_tags(content: str) -> list[str]:
@@ -0,0 +1,107 @@
1
+ """audio.prosody-captions run as the coordinator runs it, with OpenAI stood in for: every caption made once, then the
2
+ tags, then a reply that was not the schema's asked again once, only within what the step reserved (review of #53).
3
+ The stand-in answers by what it is asked (the target's line, from the prompt), never by when.
4
+ Run: uv run --no-project --with pytest --with numpy --with-editable ./sdk pytest -q nodes/audio/prosody-captions"""
5
+
6
+ import json
7
+ import runpy
8
+ import sys
9
+ import threading
10
+ import types
11
+ import wave
12
+ from pathlib import Path
13
+ from types import SimpleNamespace
14
+
15
+ import pytest
16
+
17
+ import style_captions as sc
18
+ from test_style_captions import N, PROSODY, TRANSCRIPT
19
+
20
+ HERE = Path(__file__).resolve().parent
21
+ GOOD = {"description": "Calm and even.", "tags": ["calm"], "emphasized_words": []}
22
+
23
+
24
+ class OpenAI:
25
+ """The client: a caption (the first one for `bad`'s line cut short, as gpt-audio's was on a pod), or tags. calls:
26
+ every request, in order; usd: what each call is billed (prompt tokens, at the models' prices)."""
27
+ calls: list = []
28
+ bad: set = set()
29
+ prompt_tokens = 100
30
+ lock = threading.Lock()
31
+
32
+ def __init__(self, api_key):
33
+ assert api_key == "key"
34
+ self.chat = SimpleNamespace(completions=self)
35
+
36
+ def create(self, **request):
37
+ usage = SimpleNamespace(prompt_tokens=self.prompt_tokens, completion_tokens=20,
38
+ prompt_tokens_details=SimpleNamespace(audio_tokens=0), model_dump=lambda mode: {})
39
+ if "tools" in request:
40
+ text = request["messages"][1]["content"][0]["text"]
41
+ target = text.split('Target transcript (automatic, may contain errors): "')[1].split('"')[0]
42
+ with self.lock:
43
+ self.calls.append(request)
44
+ asked = sum(1 for c in self.calls if "tools" in c and c["messages"][1]["content"][0]["text"] == text)
45
+ arguments = '{"description": "Calm" "tags": []}' if target in self.bad and asked == 1 else json.dumps(GOOD)
46
+ message = SimpleNamespace(tool_calls=[SimpleNamespace(function=SimpleNamespace(arguments=arguments))])
47
+ return SimpleNamespace(model=request["model"], usage=usage,
48
+ choices=[SimpleNamespace(message=message, finish_reason="tool_calls")])
49
+ with self.lock:
50
+ self.calls.append(request)
51
+ return SimpleNamespace(model=request["model"], usage=usage,
52
+ choices=[SimpleNamespace(message=SimpleNamespace(content=json.dumps({"tags": ["calm"]})),
53
+ finish_reason="stop")])
54
+
55
+
56
+ @pytest.fixture
57
+ def openai(monkeypatch):
58
+ module = types.ModuleType("openai")
59
+ module.OpenAI = OpenAI
60
+ for name in ("RateLimitError", "InternalServerError", "APIConnectionError", "AuthenticationError"):
61
+ setattr(module, name, type(name, (Exception,), {}))
62
+ monkeypatch.setitem(sys.modules, "openai", module)
63
+ monkeypatch.setenv("OPENAI_API_KEY", "key")
64
+ OpenAI.calls, OpenAI.bad, OpenAI.prompt_tokens = [], set(), 100
65
+ return OpenAI
66
+
67
+
68
+ def run(tmp_path, monkeypatch) -> dict:
69
+ audio = tmp_path / "audio.wav"
70
+ with wave.open(str(audio), "wb") as w:
71
+ w.setnchannels(1)
72
+ w.setsampwidth(2)
73
+ w.setframerate(sc.RATE)
74
+ w.writeframes(b"\0\0" * N)
75
+ (tmp_path / "transcript.json").write_text(json.dumps(TRANSCRIPT))
76
+ (tmp_path / "prosody.json").write_text(json.dumps(PROSODY))
77
+ request = tmp_path / "request.json"
78
+ request.write_text(json.dumps({"protocol": 1, "out_dir": str(tmp_path / "out"), "accelerator": "cpu", "config": {},
79
+ "secrets": ["OPENAI_API_KEY"], "producer": {"stage": "prosody", "stage_version": "1.0.0"},
80
+ "inputs": {"audio": {"path": str(audio)}, "transcript": {"path": str(tmp_path / "transcript.json")},
81
+ "prosody": {"path": str(tmp_path / "prosody.json")}}}))
82
+ monkeypatch.setattr(sys, "argv", ["node.py", "--request", str(request)])
83
+ try:
84
+ runpy.run_path(str(HERE / "node.py"), run_name="__main__")
85
+ except SystemExit:
86
+ pass
87
+ return json.loads((tmp_path / "response.json").read_text())
88
+
89
+
90
+ def test_a_reply_that_is_not_the_schemas_is_asked_again_after_every_other_call(tmp_path, monkeypatch, openai):
91
+ openai.bad = {"orange lamp."} # utt:3's line
92
+ got = run(tmp_path, monkeypatch)
93
+ assert got["ok"], got.get("error")
94
+ kinds = ["caption" if "tools" in c else "tag" for c in openai.calls]
95
+ # three captions, two tags (the good ones), then utt:3 asked again and tagged
96
+ assert kinds == ["caption"] * 3 + ["tag"] * 2 + ["caption", "tag"]
97
+ first = got["usage"]["asked_again"]["utt:3"]
98
+ assert "delimiter" in first["problem"] and first["arguments"] == '{"description": "Calm" "tags": []}'
99
+ assert got["usage"]["calls"] == 7
100
+
101
+
102
+ def test_a_reply_is_not_asked_again_past_the_steps_reservation(tmp_path, monkeypatch, openai):
103
+ openai.bad = {"orange lamp."}
104
+ openai.prompt_tokens = 4000 # each call near the utterance's reservation: a second caption would pass it
105
+ got = run(tmp_path, monkeypatch)
106
+ assert not got["ok"] and "not asked again" in got["error"]["message"] and "utt:3" in got["error"]["message"]
107
+ assert ["caption" if "tools" in c else "tag" for c in openai.calls] == ["caption"] * 3 + ["tag"] * 2
@@ -68,8 +68,15 @@ def test_cost_is_the_bakeoffs_at_its_prices():
68
68
  def test_replies_must_be_the_schemas_with_tags_from_the_list():
69
69
  ok = {"description": "Calm.", "tags": ["calm", "falling-end"], "emphasized_words": []}
70
70
  assert sc.parse_caption(json.dumps(ok)) == ok
71
- with pytest.raises(ValueError, match="outside the vocabulary"):
72
- sc.parse_caption(json.dumps({**ok, "tags": ["calm", "grumpy"]}))
71
+ # gpt-audio-1.5 does not hold its function call to the schema's tag list (strict or not: "friendly" on an OOO
72
+ # held-out clip, 2026-10-30): a tag outside it is dropped from that caption and said, not the step's failure
73
+ assert sc.parse_caption(json.dumps({**ok, "tags": ["calm", "grumpy", "falling-end"]})) == ok
74
+ assert sc.outside_tags(json.dumps({**ok, "tags": ["calm", "grumpy", "falling-end"]})) == ["grumpy"]
75
+ assert sc.outside_tags(json.dumps(ok)) == []
76
+ with pytest.raises(ValueError, match="not strings"): # not a tag at all: the reply is not the schema's
77
+ sc.parse_caption(json.dumps({**ok, "tags": ["calm", 3, None]}))
78
+ with pytest.raises(ValueError, match="outside the vocabulary"): # the tagger's json_schema holds it to the list
79
+ sc.parse_tags('{"tags": ["neutral", "grumpy"]}')
73
80
  with pytest.raises(ValueError, match="not {description, tags, emphasized_words}"):
74
81
  sc.parse_caption(json.dumps({"description": "x"}))
75
82
  assert sc.parse_tags('{"tags": ["neutral"]}') == ["neutral"]
@@ -155,3 +162,49 @@ def test_the_fan_out_keeps_the_items_order():
155
162
  client = FailingClient(-1)
156
163
  got = sc.fan_out(lambda i: client.chat.completions.create(utterance=i)["utterance"], range(50), workers=4)
157
164
  assert got == list(range(50)) and client.calls == 50
165
+
166
+
167
+ GOOD = json.dumps({"description": "Calm.", "tags": ["calm"], "emphasized_words": []})
168
+
169
+
170
+ def asker(*replies):
171
+ """One call per reply given, in order: (arguments or None, usage, raw), as node.py's ask makes them."""
172
+ calls = iter(replies)
173
+
174
+ def ask():
175
+ arguments = next(calls)
176
+ return arguments, {"text_in": 10, "audio_in": 5, "out": 3, "usd": 0.01}, {"finish_reason": "tool_calls", "arguments": arguments}
177
+ return ask
178
+
179
+
180
+ def test_a_caption_reply_that_is_not_the_schemas_json_is_rejected_whole_and_a_second_one_taken():
181
+ """gpt-audio does not constrain its function call to the schema: a reply that is not its JSON (a pod's run,
182
+ 2026-10-31: "Expecting ',' delimiter") is kept as it came, and a second reply, if asked, is the caption, both calls
183
+ billed."""
184
+ calls = asker('{"description": "Calm" "tags": []}', GOOD)
185
+ first = sc.caption_attempt(calls)
186
+ assert "delimiter" in first["rejected"]["problem"] and first["rejected"]["arguments"] == '{"description": "Calm" "tags": []}'
187
+ assert first["rejected"]["finish_reason"] == "tool_calls"
188
+ got = sc.asked_again(first, sc.caption_attempt(calls))
189
+ assert got["parsed"]["description"] == "Calm." and got["arguments"] == GOOD and got["asked_again"] == [first["rejected"]]
190
+ assert got["usage"] == {"text_in": 20, "audio_in": 10, "out": 6, "usd": 0.02}
191
+ assert "rejected" not in sc.caption_attempt(asker(GOOD))
192
+
193
+
194
+ def test_a_second_reply_that_is_not_the_schemas_fails_the_caption():
195
+ calls = asker("{", None)
196
+ first = sc.caption_attempt(calls)
197
+ with pytest.raises(ValueError, match="asked twice"):
198
+ sc.asked_again(first, sc.caption_attempt(calls))
199
+
200
+
201
+ def test_a_reply_is_asked_again_only_within_what_the_step_reserved():
202
+ """Review of #53: asked once every other call is made, so what the step has spent is all but this caption's second
203
+ call and its tag, which must stay within the step's reservation (COST_PER_UTTERANCE an utterance)."""
204
+ assert sc.may_ask_again(0.07, 0.005, 0.001, 10) # 0.076 <= 0.08
205
+ assert not sc.may_ask_again(0.076, 0.005, 0.001, 10) # 0.082 > 0.08
206
+
207
+
208
+ def test_what_an_utterance_is_reserved_is_the_manifests():
209
+ lines = (Path(sc.__file__).parent / "manifest.yaml").read_text().splitlines()
210
+ assert f"cost: {{usd: {sc.COST_PER_UTTERANCE}, per: utterance, input: transcript}}" in lines
@@ -16,9 +16,11 @@ config:
16
16
  runners:
17
17
  - {kind: native, env: envs/audio-events, entry: node.py, accelerator: mps, platforms: [darwin/arm64]}
18
18
  # PyPI's torch 2.8 wheels for Linux carry CUDA 12.8 (envs/audio-events); the machine needs an NVIDIA driver >= 570.
19
- # PE-A-Frame large is 5.8 GB of float32 weights, run on up to 140 s of audio at once: 17 GB allocated on the Apple GPU
20
- # (not yet measured on CUDA). 20, not 24: a "24 GB" card reports 23.99, an L4 22.5, and the cloud places 22 and 20.
21
- - {kind: native, env: envs/audio-events, entry: node.py, accelerator: cuda, platforms: [linux/amd64], minimum_vram_gb: 20}
19
+ # PE-A-Frame large is 5.8 GB of float32 weights, run on up to 140 s of audio at once: 17 GB allocated on the Apple GPU.
20
+ # On CUDA (an RTX 3090, 2026-10-31), with expandable segments (node.py), the whole step peaks at 17.1 GB for a 92 s
21
+ # soundtrack and 22.1 GB for 131.8 s, about 0.13 GB a second more: 23.1 GB for a 140 s piece. 23, not 24: a "24 GB"
22
+ # card reports 24 or 23.99; an L4 (22.5) and a 20 GB card are too small
23
+ - {kind: native, env: envs/audio-events, entry: node.py, accelerator: cuda, platforms: [linux/amd64], minimum_vram_gb: 23}
22
24
  - {kind: native, env: envs/audio-events, entry: node.py, accelerator: cpu}
23
25
  # on the CPU, 28 GB resident at most (PE-A-Frame on nes-biking's 137.6 s soundtrack, one piece)
24
26
  resources: {cpu_cores: 4, memory_gb: 28, gpu: exclusive}
@@ -18,9 +18,15 @@ Licences: PANNs code MIT and weights CC-BY-4.0, EfficientAT code and weights MIT
18
18
  are CC-BY-4.0), PE-A-Frame Apache-2.0: commercial-ok, as PLAN.md §4 lists them.
19
19
  """
20
20
 
21
+ import os
21
22
  import time
22
23
 
23
- import numpy as np
24
+ # PE-A-Frame's largest allocation (7.38 GiB on a 92 s soundtrack) failed on a 24 GB card with 6.7 GiB free and 6.7 GiB
25
+ # more reserved by PyTorch but unused: the caching allocator's blocks, split by the pass's own earlier tensors, could not
26
+ # hold it (a RunPod pod, 2026-10-31). Expandable segments map that memory as one; set before torch is first imported
27
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
28
+
29
+ import numpy as np # noqa: E402
24
30
 
25
31
  from perception_node import Node, NodeError
26
32
  from models import PEAFRAME, PEAFRAME_SR, TAGGER_SR, WEIGHTS, device, load_mn40, load_panns, load_peaframe, run_mn40, run_panns, run_peaframe, sync
@@ -330,9 +330,15 @@ def child_env(requested: list[str], secrets: dict[str, str]) -> dict[str, str]:
330
330
  # once, not a download it has no token for. Nor does it load a token stored on the machine (`hf auth login`'s, or
331
331
  # HF_TOKEN_PATH's), which huggingface_hub otherwise does even offline
332
332
  OFFLINE = {"HF_HUB_OFFLINE": "1", "HF_HUB_DISABLE_IMPLICIT_TOKEN": "1"}
333
- # a prepare on another machine: the token it was sent, or none, never the machine's own login (its HF_TOKEN in a
334
- # profile, its stored token): access to a gated model is a person's. A prepare that prepare_first runs has it from OFFLINE
335
- SENT_TOKEN_ONLY = {"HF_HUB_DISABLE_IMPLICIT_TOKEN": "1"}
333
+ # A prepare is online, with the token it was sent as HF_TOKEN: access to a gated model is a person's. It runs without
334
+ # HF_HUB_DISABLE_IMPLICIT_TOKEN (PREPARE_UNSET), and only when it was sent one: transformers 5.20 fetches a model's
335
+ # config without the token it is given when implicit tokens are off (SAM 3's prepare got a 401 on liquidswords2,
336
+ # 2026-10-30). The implicit token must then be the one it was sent: huggingface_hub (2.3) takes an OIDC identity first
337
+ # (OIDC_LOGINS, unset), then HF_TOKEN (which replaces any other), then a stored token (its path NO_STORED_TOKEN)
338
+ PREPARE_UNSET = ("HF_HUB_OFFLINE", "HF_HUB_DISABLE_IMPLICIT_TOKEN")
339
+ OIDC_LOGINS = ("HF_OIDC_RESOURCE", "HF_OIDC_ID_TOKEN")
340
+ # where a prepare looks for a stored token: a path that cannot exist (below /dev/null)
341
+ NO_STORED_TOKEN = "/dev/null/no-stored-token"
336
342
 
337
343
 
338
344
  def hf_cache_env(home: str) -> dict[str, str]:
@@ -143,9 +143,9 @@ class RunPodBackend(CloudBackend):
143
143
  guard = REPO / "cloud" / "runpod" / "guard.sh"
144
144
  # perception's own image (cloud/runpod/image): RunPod's cached PyTorch image with ffmpeg, rsync, uv and runpodctl 3
145
145
  # added; software only. Its tag is tag.py's, from what goes into it (a test checks they agree)
146
- image_tag = "e92ef7a71b30"
146
+ image_tag = "efba77cf5632"
147
147
  # and the digest of the build that was measured: a rebuild under the same tag (apt's versions move) is never pulled
148
- image_digest = "sha256:09e6eb03f0ab44353e13b198e9f2b114b07c29af5613b33da42e2e122b7a74c4"
148
+ image_digest = "sha256:472c7316c3bb200d88948b43acbb4afd054f9d56621f869d700a6680884e234a"
149
149
  # under the repository's owner, where .github/workflows/runpod-image.yml pushes it (a test checks)
150
150
  image = f"ghcr.io/hyperstudyio/perception-runpod:{image_tag}@{image_digest}"
151
151
  start = "/perception-start.sh" # its own start (host keys, PUBLIC_KEY, sshd), run after the pod's removal is set
@@ -158,6 +158,14 @@ class RunPodBackend(CloudBackend):
158
158
  "NVIDIA RTX PRO 4500 Blackwell": 0.72, "NVIDIA L40": 0.82,
159
159
  "NVIDIA GeForce RTX 4090": 0.89, "NVIDIA RTX PRO 5000 Blackwell": 0.96, "NVIDIA RTX 6000 Ada Generation": 0.99,
160
160
  "NVIDIA L40S": 1.09, "NVIDIA GeForce RTX 5090": 1.19}
161
+ # each one's memory as nvidia-smi reports it (GB; the probe's memory_gb), against the steps' minimum_vram_gb: a "24 GB"
162
+ # card reports 24 or 23.99, an L4 22.5, a "48 GB" A40, L40 or L40S 45
163
+ gpu_vram_gb = {"NVIDIA RTX A4500": 20, "NVIDIA RTX A5000": 24, "NVIDIA RTX 4000 Ada Generation": 20,
164
+ "NVIDIA GeForce RTX 3090": 24, "NVIDIA RTX PRO 6000 Blackwell Max-Q Workstation Edition": 95.6,
165
+ "NVIDIA RTX PRO 4000 Blackwell": 24, "NVIDIA A40": 45, "NVIDIA L4": 22.5, "NVIDIA RTX A6000": 48,
166
+ "NVIDIA RTX PRO 4500 Blackwell": 32, "NVIDIA L40": 45, "NVIDIA GeForce RTX 4090": 24,
167
+ "NVIDIA RTX PRO 5000 Blackwell": 48, "NVIDIA RTX 6000 Ada Generation": 48, "NVIDIA L40S": 45,
168
+ "NVIDIA GeForce RTX 5090": 31.8}
161
169
  # datacenters in the US, Canada and Europe, whichever has a GPU free: those RunPod's REST API accepts (its schema's
162
170
  # enum, 2026-10-25; gpus above are in its enum too)
163
171
  datacenters = ["US-CA-2", "US-DE-1", "US-GA-1", "US-GA-2", "US-IL-1", "US-KS-2", "US-KS-3", "US-MD-1", "US-MO-1", "US-MO-2",
@@ -216,6 +224,18 @@ class Cloud:
216
224
  def runpod(self) -> bool:
217
225
  return self.read().get("runpod", False)
218
226
 
227
+ @property
228
+ def runpod_keys(self) -> bool:
229
+ """Whether a whole run on a RunPod pod (pod_run.py) may be given the keys its steps declare: the user's explicit
230
+ yes (`perception cloud keys runpod on`), off until then. Per-step pods are never given any."""
231
+ return self.read().get("runpod_keys", False) is True
232
+
233
+ def set_keys(self, allowed: bool):
234
+ doc = self.read()
235
+ doc["runpod_keys"] = allowed
236
+ self.path.parent.mkdir(parents=True, exist_ok=True)
237
+ self.path.write_text(json.dumps(doc))
238
+
219
239
  def set(self, enabled: bool, provider: str = "aws"):
220
240
  doc = self.read()
221
241
  doc["enabled" if provider == "aws" else provider] = enabled
@@ -20,7 +20,7 @@ from pathlib import Path
20
20
 
21
21
  from ..manifests import Manifest, Runner
22
22
  from ..store import Artifact
23
- from .base import GPU_LEASES, LEASE_FOLDER, OFFLINE, Backend, Capabilities, LeaseRefused, gpu_env, holder, lease_command
23
+ from .base import GPU_LEASES, LEASE_FOLDER, OFFLINE, Backend, Capabilities, LeaseRefused, SubmitRefused, gpu_env, holder, lease_command
24
24
  from .probe import probe
25
25
 
26
26
  from ..release import REPO, env_vars
@@ -88,6 +88,11 @@ class NativeBackend(Backend):
88
88
  command = [*lease_command(str(self.lease_folder()), gpu, manifest.resources.gpu,
89
89
  holder(json.loads(request.read_text()))), *command]
90
90
  if prepare_secrets is not None and manifest.download_secrets and self.downloads(manifest, runner):
91
+ if missing := [k for k in manifest.download_secrets if not (prepare_secrets.get(k) or "").strip()]:
92
+ # refused, not prepared with whatever login this machine has: the plan says so first (missing_secrets),
93
+ # but a resumed run is not planned again (as a host's submit, remote.py)
94
+ raise SubmitRefused("input", f"its weights are downloaded with {', '.join(missing)}, which is not set here "
95
+ f"(perception keys set {missing[0]})")
91
96
  # its prepare first, with them, then the node (with its lease: no card is held while weights download)
92
97
  # without them (prepare_first.py)
93
98
  from .. import prepare_first