hyperstudy-perception 0.1.4__tar.gz → 0.1.5__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.
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/PKG-INFO +1 -1
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/pyproject.toml +1 -1
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/RELEASE.json +7 -1
- hyperstudy_perception-0.1.4/src/perception/_pipeline/graphs/full.yaml → hyperstudy_perception-0.1.5/src/perception/_pipeline/graphs/full-objects-names.yaml +8 -16
- hyperstudy_perception-0.1.5/src/perception/_pipeline/graphs/full.yaml +101 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/haiku.py +10 -8
- hyperstudy_perception-0.1.4/src/perception/_pipeline/nodes/vlm/shot-captions/node.py → hyperstudy_perception-0.1.5/src/perception/_pipeline/nodes/vlm/shot-captions/captioning.py +27 -14
- hyperstudy_perception-0.1.5/src/perception/_pipeline/nodes/vlm/shot-captions/node.py +15 -0
- hyperstudy_perception-0.1.5/src/perception/_pipeline/nodes/vlm/shot-captions/test_captioning.py +38 -0
- hyperstudy_perception-0.1.5/src/perception/_pipeline/nodes/vlm/shot-captions-objects/manifest.yaml +35 -0
- hyperstudy_perception-0.1.5/src/perception/_pipeline/nodes/vlm/shot-captions-objects/node.py +57 -0
- hyperstudy_perception-0.1.5/src/perception/_pipeline/nodes/vlm/shot-captions-objects/test_shot_captions_objects.py +154 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/backends/base.py +3 -3
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/backends/native.py +31 -10
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/backends/remote.py +9 -4
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/cli.py +52 -6
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/manifests.py +13 -1
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/remote_coordinator.py +19 -7
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/remote_run.py +13 -4
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/LICENSE +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/README.md +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/runpod/guard.sh +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/runpod/image/Dockerfile +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/runpod/image/start.sh +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/runpod/image/tag.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/skypilot/gpu-faces.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/skypilot/gpu-tune.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/skypilot/guard.sh +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/skypilot/iam-perception-node.json +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/skypilot/launch.sh +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/skypilot/node-gpu.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/skypilot/node-setup.sh +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/skypilot/sky.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/cloud/skypilot/smoke.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/api/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/api/uv.lock +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/asr-cuda/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/asr-cuda/uv.lock +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/audio/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/audio/uv.lock +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/audio-events/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/audio-events/uv.lock +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/core/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/core/uv.lock +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/faces/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/faces/uv.lock +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/gaze/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/gaze/uv.lock +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/prosody/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/prosody/uv.lock +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/vision/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/envs/vision/uv.lock +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/graphs/decode.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/graphs/faces.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/graphs/mvp-captions.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/graphs/mvp.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/nemo_model.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/nemo_tokens.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/node_nemo.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/raw_tokens.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/test_nemo_model.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/test_nemo_tokens.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/test_raw_tokens.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/test_wav.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/utterances.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/asr/wav.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/diarization_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/diarizer.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/pcm16.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/test_diarization_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/test_diarizer.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/test_pcm16.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/test_voices.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/diarize/voices.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/odyssey.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/praat.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/prosody_layer.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/prosody_measure.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/prosody_tracks.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/prosody_units.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/test_prosody_layer.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/test_prosody_measure.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/test_prosody_tracks.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/test_prosody_units.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/third_party/README.md +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/third_party/odyssey/LICENSE +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody/third_party/odyssey/ser_model.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody-captions/captions_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody-captions/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody-captions/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody-captions/style_captions.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody-captions/test_prosody_captions_node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/prosody-captions/test_style_captions.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/audioset_labels.csv +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/efficientat/LICENSE +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/efficientat/UPSTREAM.md +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/efficientat/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/attention_pooling.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/block_types.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/model.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/efficientat/mn/utils.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/efficientat/preprocess.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/models.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/panns/LICENSE.MIT +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/panns/UPSTREAM.md +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/panns/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/panns/cnn14.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/panns/pytorch_utils.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/sound_events.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/sound_scores.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/soundtrack.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/test_sound_events.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/test_sound_scores.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/sounds/test_soundtrack.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/waveform/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/waveform/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/waveform/test_waveform_peaks.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/audio/waveform/waveform_peaks.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/conftest.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/core/decode/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/core/decode/media.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/core/decode/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/core/decode/shot_scores.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/core/decode/test_media.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/core/decode/test_shot_scores.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/testdata/README.md +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/testdata/rotated-90.mp4 +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/asd_crops.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/asd_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/models.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/speaker_links.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/test_asd_crops.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/test_asd_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/test_speaker_links.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/README.md +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/LICENSE +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/loss.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/model/Classifier.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/model/Encoder.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/third_party/lr_asd/model/Model.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/active-speaker/video25.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/body-pose/bodies.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/body-pose/detections.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/body-pose/keypoints.json +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/body-pose/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/body-pose/models.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/body-pose/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/body-pose/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/body-pose/test_bodies.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/body-pose/test_detections.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/face_detail.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/frames.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/loader.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/pyfeat_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/test_face_detail.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/test_frames.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/test_loader.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/test_pyfeat_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/test_thumbnails.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/thumbnails.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/face-track/tracking.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/facing/facing.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/facing/facing_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/facing/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/facing/models.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/facing/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/facing/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/facing/test_facing.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/facing/test_facing_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/gaze_polygons.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/gaze_targets.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/heatmaps.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/models.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/test_gaze_polygons.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/test_gaze_targets.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/test_heatmaps.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/README.md +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/LICENSE +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/attention.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/block.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/dino_head.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/drop_path.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/layer_scale.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/mlp.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/patch_embed.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/layers/swiglu_ffn.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/models/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/dinov2/dinov2/models/vision_transformer.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/gazelle/LICENSE +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/gazelle/gazelle/backbone.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/gazelle/gazelle/model.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gaze/third_party/gazelle/gazelle/utils.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gestures/gesture_evidence.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gestures/gesture_inputs.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gestures/gesture_rules.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gestures/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gestures/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gestures/test_gesture_evidence.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/gestures/test_gesture_rules.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/hand_detections.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/hand_tracks.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/models.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/scipy_lsap/LICENSE +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/scipy_lsap/UPSTREAM.md +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/scipy_lsap/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/scipy_lsap/rectangular_lsap.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/test_hand_detections.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/test_hand_tracks.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/test_node.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/hands/test_rectangular_lsap.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/budget.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/garments.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/geometry.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/link.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/linking.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/models.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/names.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/object_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/object_tracks.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/person_masks.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/prepare.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/shot_frames.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/test_garments.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/test_models_hub.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/test_object_budget.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/test_object_links.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/test_object_raw.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/test_person_masks.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/test_shot_frames.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/test_split.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/testdata/node_1_1_0.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/track.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects/vocabulary.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects-link/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects-names/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vision/objects-track/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/nodes/vlm/shot-captions/manifest.yaml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/schema/README.md +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/schema/bundle-0.1.schema.json +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/sdk/pyproject.toml +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/sdk/src/perception_node/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/sdk/src/perception_node/weights.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/artifacts.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/backends/__init__.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/backends/cloud.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/backends/probe.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/estimates.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/export.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/hosts.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/ir.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/keycheck.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/keys.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/plan.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/pod_run.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/pod_session.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/prepare_first.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/release.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/remote_driver.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/runpod.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/scheduler.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/setup.py +0 -0
- {hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/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.
|
|
3
|
+
Version: 0.1.5
|
|
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.
|
|
3
|
+
version = "0.1.5"
|
|
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"
|
{hyperstudy_perception-0.1.4 → hyperstudy_perception-0.1.5}/src/perception/_pipeline/RELEASE.json
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
2
|
+
"commit": "b174b9b1d8d56b59dd69d36db67f93bcf0537899",
|
|
3
3
|
"files": [
|
|
4
4
|
"cloud/runpod/guard.sh",
|
|
5
5
|
"cloud/runpod/image/Dockerfile",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"envs/vision/uv.lock",
|
|
35
35
|
"graphs/decode.yaml",
|
|
36
36
|
"graphs/faces.yaml",
|
|
37
|
+
"graphs/full-objects-names.yaml",
|
|
37
38
|
"graphs/full.yaml",
|
|
38
39
|
"graphs/mvp-captions.yaml",
|
|
39
40
|
"graphs/mvp.yaml",
|
|
@@ -245,8 +246,13 @@
|
|
|
245
246
|
"nodes/vision/objects/testdata/node_1_1_0.py",
|
|
246
247
|
"nodes/vision/objects/track.py",
|
|
247
248
|
"nodes/vision/objects/vocabulary.py",
|
|
249
|
+
"nodes/vlm/shot-captions-objects/manifest.yaml",
|
|
250
|
+
"nodes/vlm/shot-captions-objects/node.py",
|
|
251
|
+
"nodes/vlm/shot-captions-objects/test_shot_captions_objects.py",
|
|
252
|
+
"nodes/vlm/shot-captions/captioning.py",
|
|
248
253
|
"nodes/vlm/shot-captions/manifest.yaml",
|
|
249
254
|
"nodes/vlm/shot-captions/node.py",
|
|
255
|
+
"nodes/vlm/shot-captions/test_captioning.py",
|
|
250
256
|
"schema/README.md",
|
|
251
257
|
"schema/bundle-0.1.schema.json",
|
|
252
258
|
"sdk/pyproject.toml",
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# every step that reads it (bodies, gaze, hands...) receives it when placed elsewhere; the run's consent still names it
|
|
8
|
-
# per step. max_cost_usd caps the spend.
|
|
9
|
-
# perception plan graphs/full.yaml --input video=PATH/TO/VIDEO.mp4
|
|
10
|
-
# perception run graphs/full.yaml --input video=PATH --only asr,faces # the local, free features
|
|
11
|
-
# perception run graphs/full.yaml --input video=PATH --only gestures # gestures without objects (its optional
|
|
12
|
-
# input): in a run of everything, a refused or failed objects cancels it
|
|
13
|
-
# perception run graphs/full.yaml --input video=PATH --allow-uploads # everything, captions too
|
|
1
|
+
# The full graph (full.yaml) with the objects named in a step of their own (run-anywhere 1b, A): objects_names names
|
|
2
|
+
# them with Claude Haiku 5.5 from decode's keyframes, a second pass over the keyframes the captions step reads.
|
|
3
|
+
# full.yaml has the captions call name them instead (B), chosen 2026-11-01: on held-out OOO clips 3 and 4 the
|
|
4
|
+
# captions read the same blind, clothing was the same, and A's extra objects were mostly stage lights, which the
|
|
5
|
+
# naming rules exclude (RUN_ANYWHERE_PLAN.md 1b). Kept to compare the two on more clips.
|
|
6
|
+
# perception remote run HOST graphs/full-objects-names.yaml --input video=PATH --allow-uploads
|
|
14
7
|
schema_version: 1
|
|
15
|
-
pipeline: perception-full
|
|
8
|
+
pipeline: perception-full-objects-names
|
|
16
9
|
inputs:
|
|
17
10
|
video: {type: video.v1, uri: '../data/video.mp4'}
|
|
18
11
|
policy:
|
|
@@ -63,8 +56,7 @@ nodes:
|
|
|
63
56
|
# to the API: ANTHROPIC_API_KEY), as soon as decode is done; objects_track outlines and tracks them with SAM 3 and
|
|
64
57
|
# embeds them with DINOv3 (both gated: HF_TOKEN), on the GPU with no API; objects checks each track's crops with Haiku
|
|
65
58
|
# (frames and faces again), finds who wears what (faces, bodies) and links them across shots: the objects layer. The
|
|
66
|
-
# naming duplicates the captions' pass over the same keyframes;
|
|
67
|
-
# is measured (RUN_ANYWHERE_PLAN.md 1b)
|
|
59
|
+
# naming duplicates the captions' pass over the same keyframes; full.yaml has the captions call name them
|
|
68
60
|
- id: objects_names
|
|
69
61
|
uses: vision.objects-names@1.0.0
|
|
70
62
|
inputs: {shots: $nodes.decode.shots, keyframes: $nodes.decode.keyframes, media: $nodes.decode.media}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Everything the pipeline can do: the graph the annotator app runs. The app's feature list picks what runs (--only, with
|
|
2
|
+
# what it needs), settings (--set) and places (--place). Nothing leaves the machine without --allow-uploads, and then
|
|
3
|
+
# only the kinds allow_upload names: frames, faces and transcript for the captions API (whose call also names the
|
|
4
|
+
# objects; frames and faces also for the objects' crop check), audio and transcript for the delivery captions
|
|
5
|
+
# (per-utterance snippets to OpenAI), and video and audio for a step placed on another machine (a lab server, a cloud
|
|
6
|
+
# GPU), which receives its inputs. Embeddings too: the faces layer holds ArcFace identity distances (person_pairs), and
|
|
7
|
+
# every step that reads it (bodies, gaze, hands...) receives it when placed elsewhere; the run's consent still names it
|
|
8
|
+
# per step. max_cost_usd caps the spend.
|
|
9
|
+
# perception plan graphs/full.yaml --input video=PATH/TO/VIDEO.mp4
|
|
10
|
+
# perception run graphs/full.yaml --input video=PATH --only asr,faces # the local, free features
|
|
11
|
+
# perception run graphs/full.yaml --input video=PATH --only gestures # gestures without objects (its optional
|
|
12
|
+
# input): in a run of everything, a refused or failed objects cancels it
|
|
13
|
+
# perception run graphs/full.yaml --input video=PATH --allow-uploads # everything, captions too
|
|
14
|
+
schema_version: 1
|
|
15
|
+
pipeline: perception-full
|
|
16
|
+
inputs:
|
|
17
|
+
video: {type: video.v1, uri: '../data/video.mp4'}
|
|
18
|
+
policy:
|
|
19
|
+
placement: hybrid
|
|
20
|
+
privacy: local-preferred
|
|
21
|
+
allow_upload: [video, audio, frames, faces, transcript, embeddings]
|
|
22
|
+
# Measured on held-out OOO clips 3 and 4 (28 and 39 shots; liquidswords2, 2026-10-30): the captions with the names
|
|
23
|
+
# $0.029 and $0.037 (about $0.001 a shot, the class call included; A's captions alone $0.023 and $0.033), the crop
|
|
24
|
+
# check $0.022 and $0.021, whole runs $0.32 and $0.37 (A's $0.31 and $0.38)
|
|
25
|
+
max_cost_usd: 1.50
|
|
26
|
+
retries: 1
|
|
27
|
+
nodes:
|
|
28
|
+
- id: decode
|
|
29
|
+
uses: core.decode@1.1.0
|
|
30
|
+
inputs: {video: $inputs.video}
|
|
31
|
+
- id: asr
|
|
32
|
+
uses: audio.asr@1.1.0
|
|
33
|
+
inputs: {audio: $nodes.decode.audio, shots: $nodes.decode.shots}
|
|
34
|
+
# refines asr's transcript: the same words and utterances, with their voices (pyannote; gated, HF_TOKEN)
|
|
35
|
+
- id: diarize
|
|
36
|
+
uses: audio.diarize@1.1.0
|
|
37
|
+
inputs: {audio: $nodes.decode.audio, transcript: $nodes.asr.transcript}
|
|
38
|
+
- id: sounds
|
|
39
|
+
uses: audio.sounds@1.1.0
|
|
40
|
+
inputs: {video: $inputs.video}
|
|
41
|
+
- id: waveform
|
|
42
|
+
uses: audio.waveform@1.0.0
|
|
43
|
+
inputs: {audio: $nodes.decode.audio}
|
|
44
|
+
- id: faces
|
|
45
|
+
uses: vision.face-track@1.4.0
|
|
46
|
+
inputs: {video: $inputs.video, media: $nodes.decode.media, shots: $nodes.decode.shots}
|
|
47
|
+
# who on screen speaks each utterance (LR-ASD over the face tracks and the audio), and which person each voice is
|
|
48
|
+
- id: speakers
|
|
49
|
+
uses: vision.active-speaker@1.1.0
|
|
50
|
+
inputs: {video: $inputs.video, audio: $nodes.decode.audio, shots: $nodes.decode.shots, faces: $nodes.faces.faces,
|
|
51
|
+
transcript: $nodes.diarize.transcript}
|
|
52
|
+
- id: bodies
|
|
53
|
+
uses: vision.body-pose@1.1.0
|
|
54
|
+
inputs: {video: $inputs.video, media: $nodes.decode.media, shots: $nodes.decode.shots, faces: $nodes.faces.faces}
|
|
55
|
+
# who faces whom: MediaPipe 3D torso direction for the bodies' own person boxes (no second detector pass)
|
|
56
|
+
- id: facing
|
|
57
|
+
uses: vision.facing@1.1.0
|
|
58
|
+
inputs: {video: $inputs.video, media: $nodes.decode.media, bodies: $nodes.bodies.bodies}
|
|
59
|
+
# hand boxes from the bodies' hand keypoints and a hand detector; each hand's body and face come with the bodies
|
|
60
|
+
- id: hands
|
|
61
|
+
uses: vision.hands@1.1.0
|
|
62
|
+
inputs: {video: $inputs.video, media: $nodes.decode.media, shots: $nodes.decode.shots, bodies: $nodes.bodies.bodies}
|
|
63
|
+
# objects in two steps: objects_track outlines and tracks the things the captions call names (its vocabulary; how
|
|
64
|
+
# this was chosen over a names step of its own is in graphs/full-objects-names.yaml)
|
|
65
|
+
# with SAM 3 and embeds them with DINOv3 (both gated: HF_TOKEN), on the GPU with no API; objects checks each track's
|
|
66
|
+
# crops with Haiku (frames and faces), finds who wears what (faces, bodies) and links them across shots
|
|
67
|
+
- id: objects_track
|
|
68
|
+
uses: vision.objects-track@1.0.0
|
|
69
|
+
inputs: {vocabulary: $nodes.captions.vocabulary, video: $inputs.video, media: $nodes.decode.media,
|
|
70
|
+
shots: $nodes.decode.shots, faces: $nodes.faces.faces, bodies: $nodes.bodies.bodies}
|
|
71
|
+
- id: objects
|
|
72
|
+
uses: vision.objects@2.0.0
|
|
73
|
+
inputs: {tracks: $nodes.objects_track.tracks, shots: $nodes.decode.shots, faces: $nodes.faces.faces,
|
|
74
|
+
bodies: $nodes.bodies.bodies, media: $nodes.decode.media}
|
|
75
|
+
# hand over mouth, self-touch, pointing and raised hands from the hands' keypoints, py-feat's landmarks and the bodies'
|
|
76
|
+
# shoulders. objects is optional: with it, an object can be what a hand points at; leaving objects out (--only)
|
|
77
|
+
# still runs gestures, pointing at faces only
|
|
78
|
+
- id: gestures
|
|
79
|
+
uses: vision.gestures@1.0.0
|
|
80
|
+
inputs: {video: $inputs.video, hands: $nodes.hands.hands, bodies: $nodes.bodies.bodies, faces: $nodes.faces.faces,
|
|
81
|
+
detail: $nodes.faces.detail, objects: $nodes.objects.objects}
|
|
82
|
+
# objects is optional for gaze: when the objects step runs, its tracks are gaze targets too (gaze waits for it); when
|
|
83
|
+
# --only leaves it out, gaze runs on the faces alone
|
|
84
|
+
- id: gaze
|
|
85
|
+
uses: vision.gaze@1.2.0
|
|
86
|
+
inputs: {video: $inputs.video, media: $nodes.decode.media, faces: $nodes.faces.faces, objects: $nodes.objects.objects}
|
|
87
|
+
# the speech it reads is diarized, as the bake-off's captions read it: lines by voice (SPEAKER_00, ...), not one SPEAKER
|
|
88
|
+
- id: captions
|
|
89
|
+
uses: vlm.shot-captions-objects@1.0.0
|
|
90
|
+
inputs: {shots: $nodes.decode.shots, keyframes: $nodes.decode.keyframes, transcript: $nodes.diarize.transcript,
|
|
91
|
+
faces: $nodes.faces.faces, media: $nodes.decode.media}
|
|
92
|
+
# how each utterance is said: Praat's measures against its speaker's pitch range (the speakers layer names the speaker)
|
|
93
|
+
# and the Odyssey baseline's arousal, dominance and valence, all on this machine
|
|
94
|
+
- id: prosody
|
|
95
|
+
uses: audio.prosody@1.0.0
|
|
96
|
+
inputs: {audio: $nodes.decode.audio, transcript: $nodes.diarize.transcript, speakers: $nodes.speakers.speakers}
|
|
97
|
+
# the delivery in words and tags, by gpt-audio-1.5 (each utterance's audio with 1 s of context, and its transcript
|
|
98
|
+
# lines, go to OpenAI: OPENAI_API_KEY), written into the prosody layer. Its own node, so prosody runs without uploads
|
|
99
|
+
- id: prosody_captions
|
|
100
|
+
uses: audio.prosody-captions@1.0.0
|
|
101
|
+
inputs: {audio: $nodes.decode.audio, transcript: $nodes.diarize.transcript, prosody: $nodes.prosody.prosody}
|
|
@@ -44,12 +44,7 @@ VOCAB_SCHEMA = {
|
|
|
44
44
|
"additionalProperties": False,
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
physical objects that are clearly visible in the frames and could be outlined and tracked with a \
|
|
49
|
-
segmentation mask.
|
|
50
|
-
|
|
51
|
-
Rules:
|
|
52
|
-
- Generic nouns a detector would understand: 1-2 words, singular, lower case, the plain name of \
|
|
47
|
+
VOCAB_RULES = """- Generic nouns a detector would understand: 1-2 words, singular, lower case, the plain name of \
|
|
53
48
|
the kind of thing (e.g. "bicycle", "ramp", "mug", "car", "helmet", "chair"), not a description of \
|
|
54
49
|
this one ("helmet", not "full-face helmet"; "cup", not "white paper cup").
|
|
55
50
|
- Never people, faces, hands or other body parts, and never hair, wigs or beards.
|
|
@@ -61,7 +56,14 @@ separately. Other things worn or held are objects (e.g. "helmet", "glove", "earr
|
|
|
61
56
|
"watch", "bag", "microphone").
|
|
62
57
|
- On-screen text and logos are not objects.
|
|
63
58
|
- If an object of the same kind was named in an earlier shot, reuse exactly that phrase.
|
|
64
|
-
- Most prominent first. Return an empty list if there is nothing that fits."""
|
|
59
|
+
- Most prominent first. Return an empty list if there is nothing that fits.""" # also given to the captions call that names the objects (vlm.shot-captions-objects)
|
|
60
|
+
|
|
61
|
+
VOCAB_INSTRUCTIONS = f"""These are three frames from one shot of a video. List up to {MAX_OBJECTS} concrete \
|
|
62
|
+
physical objects that are clearly visible in the frames and could be outlined and tracked with a \
|
|
63
|
+
segmentation mask.
|
|
64
|
+
|
|
65
|
+
Rules:
|
|
66
|
+
{VOCAB_RULES}"""
|
|
65
67
|
|
|
66
68
|
NAMES_SCHEMA = {
|
|
67
69
|
"type": "object",
|
|
@@ -156,7 +158,7 @@ def reply_json(response):
|
|
|
156
158
|
|
|
157
159
|
def fan_out(fn, items, workers: int | None = None) -> list:
|
|
158
160
|
"""[fn(x) for x in items] on `workers` threads (WORKERS when not given), in order, raising the first failure:
|
|
159
|
-
prosody-captions' style_captions.fan_out (a node's
|
|
161
|
+
prosody-captions' style_captions.fan_out (a copy: that node's runner does not share this folder, Runner.shares). At
|
|
160
162
|
most `workers` calls are submitted at a time, the next only once one has finished, so after a failure no call
|
|
161
163
|
starts: those running finish (each billed call reports its spend itself) and the failure is raised. The results are
|
|
162
164
|
in the items' order whatever order the calls finish in."""
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
"""vlm.shot-captions
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
"""The shot captions' code (vlm.shot-captions, node.py), shared with the step that also names the things on screen in the
|
|
2
|
+
same call (vlm.shot-captions-objects, which imports it: its runner shares this folder). Ported from
|
|
3
|
+
pipeline/bakeoff/caption.py (Claude Haiku 5.5 with the face roster: 126 of 135 boxed people named on OOO, about $0.04 a
|
|
4
|
+
clip). Each shot sees its three keyframes, with tracked faces boxed and labelled, the speech that starts inside it, the
|
|
5
|
+
people seen so far and the previous shot's description; never anything from later shots. That chaining is why the calls
|
|
6
|
+
run in order rather than as a Message Batch.
|
|
7
|
+
|
|
8
|
+
run() writes the shots layer with captions (it refines the decode node's shots layer) and the on-screen text layer.
|
|
9
|
+
Another step adds to each call with `more` (text after the prompt, and properties in the reply's schema) and reads each
|
|
10
|
+
reply with `each`.
|
|
9
11
|
"""
|
|
10
12
|
|
|
11
13
|
import base64
|
|
@@ -13,7 +15,7 @@ import io
|
|
|
13
15
|
import json
|
|
14
16
|
import time
|
|
15
17
|
|
|
16
|
-
from perception_node import
|
|
18
|
+
from perception_node import NodeError
|
|
17
19
|
|
|
18
20
|
PRICE_PER_MTOK = {"claude-haiku-5-5": (0.10, 0.50), "claude-opus-5-5": (4.00, 20.00)} # input, output (USD)
|
|
19
21
|
|
|
@@ -109,14 +111,14 @@ def labelled_keyframes(shot, keyframes_dir, faces, width) -> list[bytes]:
|
|
|
109
111
|
return out
|
|
110
112
|
|
|
111
113
|
|
|
112
|
-
def caption(client, model, effort, prompt, images, spend):
|
|
114
|
+
def caption(client, model, effort, prompt, images, spend, schema=None):
|
|
113
115
|
import anthropic
|
|
114
116
|
|
|
115
117
|
content = [{"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": base64.standard_b64encode(b).decode()}}
|
|
116
118
|
for b in images] + [{"type": "text", "text": prompt}]
|
|
117
119
|
try:
|
|
118
120
|
response = client.messages.create(model=model, max_tokens=4000, messages=[{"role": "user", "content": content}],
|
|
119
|
-
output_config={"effort": effort, "format": {"type": "json_schema", "schema": SCHEMA}})
|
|
121
|
+
output_config={"effort": effort, "format": {"type": "json_schema", "schema": schema or SCHEMA}})
|
|
120
122
|
except (anthropic.RateLimitError, anthropic.InternalServerError, anthropic.APIConnectionError) as e:
|
|
121
123
|
raise NodeError("transient", f"{type(e).__name__}: {e}") from None # the SDK already retried twice
|
|
122
124
|
except anthropic.AuthenticationError:
|
|
@@ -124,6 +126,8 @@ def caption(client, model, effort, prompt, images, spend):
|
|
|
124
126
|
spend(response.usage.input_tokens, response.usage.output_tokens) # billed even if the reply is then rejected
|
|
125
127
|
if response.stop_reason == "refusal":
|
|
126
128
|
raise NodeError("input", f"refusal: {response.stop_details}")
|
|
129
|
+
if response.stop_reason == "max_tokens": # as haiku.reply_json: the JSON is cut off
|
|
130
|
+
raise NodeError("internal", f"{model}'s caption reached max_tokens: its JSON is cut off")
|
|
127
131
|
text = next((b.text for b in response.content if b.type == "text"), None)
|
|
128
132
|
if text is None:
|
|
129
133
|
raise NodeError("internal", f"no text block in the response (stop_reason {response.stop_reason})")
|
|
@@ -135,7 +139,13 @@ def spent(model, tokens_in, tokens_out):
|
|
|
135
139
|
return round((tokens_in * price[0] + tokens_out * price[1]) / 1e6, 4)
|
|
136
140
|
|
|
137
141
|
|
|
138
|
-
|
|
142
|
+
def run(node, more=None, each=None, schema_more=None):
|
|
143
|
+
"""Caption every shot and write the shots and text layers. `more(shot)`: text added at the end of the shot's prompt;
|
|
144
|
+
`schema_more`: properties added to the reply's schema (each required); `each(shot, reply)`: called with every
|
|
145
|
+
reply, in order. Returns the captions' spend: USD and tokens (usd, tokens_in, tokens_out), which is what the step
|
|
146
|
+
reports; a step that makes other calls adds its own to them."""
|
|
147
|
+
schema = SCHEMA if not schema_more else {**SCHEMA, "properties": {**SCHEMA["properties"], **schema_more},
|
|
148
|
+
"required": SCHEMA["required"] + list(schema_more)}
|
|
139
149
|
import anthropic
|
|
140
150
|
|
|
141
151
|
c = node.config
|
|
@@ -163,8 +173,10 @@ with Node() as node:
|
|
|
163
173
|
f"Keyframes are at {', '.join(str(k['t']) + 's' for k in shot['keyframes'])}.\n\n"
|
|
164
174
|
f"Speech during this shot:\n{shot_speech(shot, words)}\n\nPeople seen in earlier shots:\n{roster_text}\n\n"
|
|
165
175
|
f"Previous shot: {previous or '(this is the first shot)'}\n\n{FACES_INSTRUCTIONS}\n\nFace roster so far:\n"
|
|
166
|
-
+ ("\n".join(f"- {s}" for s in seen) or "(nobody yet)"))
|
|
167
|
-
cap = caption(client, model, effort, prompt, labelled_keyframes(shot, keyframes_dir, faces, width), spend)
|
|
176
|
+
+ ("\n".join(f"- {s}" for s in seen) or "(nobody yet)")) + (more(shot) if more else "")
|
|
177
|
+
cap = caption(client, model, effort, prompt, labelled_keyframes(shot, keyframes_dir, faces, width), spend, schema)
|
|
178
|
+
if each:
|
|
179
|
+
each(shot, cap)
|
|
168
180
|
for person in cap["people"]:
|
|
169
181
|
roster.setdefault(person["label"], person["appearance"])
|
|
170
182
|
previous = cap["description"]
|
|
@@ -183,3 +195,4 @@ with Node() as node:
|
|
|
183
195
|
node.write_json("shots", "shots.json", {**shots_doc, "producer": node.producer(timings=timings, device="Anthropic API"), "shots": shots})
|
|
184
196
|
node.write_json("text", "text.json", {"layer": "text", "schema_version": "0.1.0",
|
|
185
197
|
"producer": node.producer(timings=timings, device="Anthropic API"), "items": texts})
|
|
198
|
+
return {"usd": spent(model, totals["in"], totals["out"]), "tokens_in": totals["in"], "tokens_out": totals["out"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""vlm.shot-captions: one description per shot from its keyframes and speech, with the face roster.
|
|
2
|
+
|
|
3
|
+
Ported from pipeline/bakeoff/caption.py (Claude Haiku 5.5 with the face roster: 126 of 135 boxed people named on
|
|
4
|
+
OOO, about $0.04 a clip). Each shot sees its three keyframes, with tracked faces boxed and labelled, the speech that
|
|
5
|
+
starts inside it, the people seen so far and the previous shot's description; never anything from later shots.
|
|
6
|
+
That chaining is why the calls run in order rather than as a Message Batch.
|
|
7
|
+
|
|
8
|
+
Writes the shots layer with captions (it refines the decode node's shots layer) and the on-screen text layer.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from perception_node import Node
|
|
12
|
+
from captioning import run
|
|
13
|
+
|
|
14
|
+
with Node() as node:
|
|
15
|
+
run(node)
|
hyperstudy_perception-0.1.5/src/perception/_pipeline/nodes/vlm/shot-captions/test_captioning.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""The captions call (captioning.caption) with Claude stood in for: a reply cut off at max_tokens is said so, not
|
|
2
|
+
parsed (its JSON is incomplete), and is still billed.
|
|
3
|
+
Run: uv run --no-project --with pytest --with-editable ./sdk pytest -q nodes/vlm/shot-captions"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import types
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from types import SimpleNamespace
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def anthropic(monkeypatch):
|
|
17
|
+
m = types.ModuleType("anthropic")
|
|
18
|
+
for name in ("RateLimitError", "InternalServerError", "APIConnectionError", "AuthenticationError"):
|
|
19
|
+
setattr(m, name, type(name, (Exception,), {}))
|
|
20
|
+
monkeypatch.setitem(sys.modules, "anthropic", m)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def client(stop_reason: str, text: str):
|
|
24
|
+
reply = SimpleNamespace(stop_reason=stop_reason, stop_details=None, content=[SimpleNamespace(type="text", text=text)],
|
|
25
|
+
usage=SimpleNamespace(input_tokens=1000, output_tokens=4000))
|
|
26
|
+
return SimpleNamespace(messages=SimpleNamespace(create=lambda **request: reply))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_a_reply_cut_off_at_max_tokens_is_said_so_and_billed(anthropic):
|
|
30
|
+
import captioning
|
|
31
|
+
from perception_node import NodeError
|
|
32
|
+
billed = []
|
|
33
|
+
with pytest.raises(NodeError, match="max_tokens") as e:
|
|
34
|
+
captioning.caption(client("max_tokens", '{"description": "Two people'), "claude-haiku-5-5", "low", "prompt", [],
|
|
35
|
+
lambda n_in, n_out: billed.append((n_in, n_out)))
|
|
36
|
+
assert e.value.code == "internal" and billed == [(1000, 4000)]
|
|
37
|
+
assert captioning.caption(client("end_turn", '{"description": "x"}'), "claude-haiku-5-5", "low", "prompt", [],
|
|
38
|
+
lambda *_: None) == {"description": "x"}
|
hyperstudy_perception-0.1.5/src/perception/_pipeline/nodes/vlm/shot-captions-objects/manifest.yaml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Run-anywhere 1b, B: the captions call names the things on screen too (one response, two parts), and writes the
|
|
2
|
+
# vocabulary the objects' track step reads, instead of vision.objects-names (A). The full graph's since 2026-11-01,
|
|
3
|
+
# measured against A on held-out clips (RUN_ANYWHERE_PLAN.md 1b). Its code is the captions' (nodes/vlm/shot-captions)
|
|
4
|
+
# and the objects' naming rules (nodes/vision/objects), which its runner shares.
|
|
5
|
+
id: vlm.shot-captions-objects
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
stage: extract+caption
|
|
8
|
+
title: Shot captions
|
|
9
|
+
service: Anthropic API
|
|
10
|
+
description: Per-shot descriptions from keyframes (faces boxed and labelled), speech and the face roster, with the things on screen named in the same call for the object tracks (up to 8 a shot, in generic detector nouns); on-screen text
|
|
11
|
+
inputs:
|
|
12
|
+
shots: bundle.shots@0.1
|
|
13
|
+
keyframes: assets.v1
|
|
14
|
+
transcript: bundle.transcript@0.1
|
|
15
|
+
faces: bundle.faces@0.1
|
|
16
|
+
media: media.v1
|
|
17
|
+
outputs: {shots: bundle.shots@0.1, text: bundle.text@0.1, vocabulary: vocabulary.v1}
|
|
18
|
+
config:
|
|
19
|
+
model: {enum: [claude-haiku-5-5, claude-opus-5-5], default: claude-haiku-5-5, description: "bake-off choice: Haiku 5.5 with the face roster"}
|
|
20
|
+
effort: {enum: [low, medium, high], default: medium}
|
|
21
|
+
runners:
|
|
22
|
+
# envs/gaze, the objects steps': the naming rules need numpy (linking.py), and it has anthropic and pillow
|
|
23
|
+
- {kind: native, env: envs/gaze, entry: node.py, accelerator: cpu, shares: [nodes/vlm/shot-captions, nodes/vision/objects]}
|
|
24
|
+
resources: {cpu_cores: 1, memory_gb: 1, gpu: none}
|
|
25
|
+
# Sent to Anthropic: keyframes with face boxes and labels, and the transcript lines of each shot (the captions'); then
|
|
26
|
+
# the clip's object names, as text, for their classes.
|
|
27
|
+
privacy: {external_upload: [frames, faces, transcript]}
|
|
28
|
+
secrets: [ANTHROPIC_API_KEY]
|
|
29
|
+
# Upper estimate per shot, per model: the captions' (vlm.shot-captions), with room for the names in each reply and the
|
|
30
|
+
# one call (Haiku) that classes them.
|
|
31
|
+
cost: {usd: {claude-haiku-5-5: 0.004, claude-opus-5-5: 0.13}, by: model, per: shot, input: shots}
|
|
32
|
+
models:
|
|
33
|
+
- {id: claude-haiku-5-5, licence: Anthropic API terms, licence_mode: api-terms}
|
|
34
|
+
- {id: claude-opus-5-5, licence: Anthropic API terms, licence_mode: api-terms}
|
|
35
|
+
- {id: PySceneDetect AdaptiveDetector, licence: BSD-3-Clause, licence_mode: commercial-ok}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""vlm.shot-captions-objects: the shot captions (vlm.shot-captions), with the things on screen named in the same call,
|
|
2
|
+
as the vocabulary the objects' track step reads (run-anywhere 1b, B; A is vision.objects-names, a call of its own).
|
|
3
|
+
|
|
4
|
+
Each shot's captions call (captioning.run) also returns `objects`: up to 8 names, under the names step's rules (haiku.py's
|
|
5
|
+
VOCAB_RULES, told the names used in earlier shots), cleaned and split as that step does (linking.clean_names,
|
|
6
|
+
garments.session_names). The captions' keyframes have the faces boxed and labelled; the names are of the things in
|
|
7
|
+
them, never the boxes. Then one call (Haiku 5.5, as the names step) classes the clip's names (haiku.name_classes), and
|
|
8
|
+
the vocabulary is written as the names step writes it (vocabulary.document), with the captions' model as its namer.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
here = Path(__file__).resolve().parent
|
|
15
|
+
sys.path[:0] = [str(here.parent / "shot-captions"), str(here.parents[1] / "vision" / "objects")] # its runner shares them
|
|
16
|
+
|
|
17
|
+
from perception_node import Node, NodeError # noqa: E402
|
|
18
|
+
import captioning # noqa: E402
|
|
19
|
+
import vocabulary # noqa: E402
|
|
20
|
+
from garments import session_names # noqa: E402
|
|
21
|
+
from haiku import MAX_OBJECTS, MODEL as HAIKU, VOCAB_RULES as RULES, Haiku, spent as haiku_spent # noqa: E402
|
|
22
|
+
from linking import check_classes, clean_names # noqa: E402
|
|
23
|
+
|
|
24
|
+
with Node() as node:
|
|
25
|
+
prompts, body_named, earlier = {}, {}, set()
|
|
26
|
+
|
|
27
|
+
def more(shot):
|
|
28
|
+
return (f"\n\nobjects: up to {MAX_OBJECTS} concrete physical objects clearly visible in the keyframes that could be "
|
|
29
|
+
f"outlined and tracked with a segmentation mask. The face boxes and labels are not objects.\nRules for "
|
|
30
|
+
f"objects:\n{RULES}\nNames used in earlier shots: {', '.join(sorted(earlier)) or '(none yet)'}")
|
|
31
|
+
|
|
32
|
+
def each(shot, reply):
|
|
33
|
+
named = clean_names(reply["objects"], MAX_OBJECTS)
|
|
34
|
+
earlier.update(named)
|
|
35
|
+
prompts[shot["index"]], body_named[shot["index"]] = session_names(named)
|
|
36
|
+
|
|
37
|
+
captions = captioning.run(node, more=more, each=each,
|
|
38
|
+
schema_more={"objects": {"type": "array", "items": {"type": "string"}}})
|
|
39
|
+
shots = node.input_json("shots")["shots"]
|
|
40
|
+
totals = {"in": 0, "out": 0, "calls": 0}
|
|
41
|
+
|
|
42
|
+
def spend(n_in, n_out):
|
|
43
|
+
totals.update({"in": totals["in"] + n_in, "out": totals["out"] + n_out, "calls": totals["calls"] + 1})
|
|
44
|
+
node.usage(usd=round(captions["usd"] + haiku_spent(totals["in"], totals["out"]), 4), class_calls=totals["calls"],
|
|
45
|
+
tokens_in=captions["tokens_in"] + totals["in"], tokens_out=captions["tokens_out"] + totals["out"])
|
|
46
|
+
|
|
47
|
+
names = sorted({n for shot_names in prompts.values() for n in shot_names})
|
|
48
|
+
if names: # the class call is Haiku's, whichever model captions
|
|
49
|
+
node.model(HAIKU, licence_mode="api-terms", licence="Anthropic API terms")
|
|
50
|
+
try:
|
|
51
|
+
classes = check_classes(names, Haiku(node.secret("ANTHROPIC_API_KEY"), spend).name_classes(names)) if names else {}
|
|
52
|
+
except ValueError as e:
|
|
53
|
+
raise NodeError("internal", str(e)) from None
|
|
54
|
+
node.write_json("vocabulary", "vocabulary.json",
|
|
55
|
+
vocabulary.document(shots, prompts, body_named, classes, node.config["model"]))
|
|
56
|
+
node.usage(vocabulary={str(k): v for k, v in prompts.items()}, classes=classes,
|
|
57
|
+
body_named={str(k): v for k, v in body_named.items() if v})
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""vlm.shot-captions-objects (run-anywhere 1b, B) against the two steps it would replace, with Claude stood in for: for
|
|
2
|
+
the same answers, its vocabulary is the names step's (vision.objects-names) byte for byte, and its shots and text
|
|
3
|
+
layers are the captions step's (vlm.shot-captions). The stand-in answers by what it is asked (the shot, from the
|
|
4
|
+
prompt or the keyframes' bytes), never by when.
|
|
5
|
+
Run: uv run --no-project --with pytest --with numpy --with pillow --with-editable ./sdk pytest -q nodes/vlm/shot-captions-objects"""
|
|
6
|
+
|
|
7
|
+
import base64
|
|
8
|
+
import io
|
|
9
|
+
import json
|
|
10
|
+
import runpy
|
|
11
|
+
import sys
|
|
12
|
+
import types
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from types import SimpleNamespace
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
HERE = Path(__file__).resolve().parent
|
|
19
|
+
CAPTIONS = HERE.parent / "shot-captions"
|
|
20
|
+
OBJECTS = HERE.parents[1] / "vision" / "objects"
|
|
21
|
+
|
|
22
|
+
# what Claude names in each shot: repeats across shots, a garment and "person" (the body pass's own), a blank
|
|
23
|
+
NAMED = {0: ["Chair", "mug", "jacket", "helmet", " "], 1: ["chair", "poster", "person", "helmet", "chair"]}
|
|
24
|
+
CLASSES = {"chair": ("chair", "individual", False), "mug": ("cup", "individual", False),
|
|
25
|
+
"helmet": ("helmet", "individual", True), "poster": ("poster", "individual", False)}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def caption_of(shot: int) -> dict:
|
|
29
|
+
return {"description": f"Two people talk in shot {shot}.", "setting": "a studio", "mood": "calm",
|
|
30
|
+
"people": [{"label": "0", "appearance": "a red top", "action": "talking", "speaking": True}],
|
|
31
|
+
"onscreen_text": ["OOO"] if shot == 0 else []}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Anthropic:
|
|
35
|
+
"""The client: a caption (with the shot's names when its schema asks for objects), the names alone (the names
|
|
36
|
+
step: the shot found by its keyframes' bytes), or the names' classes. calls: every request, in order."""
|
|
37
|
+
calls: list = []
|
|
38
|
+
shot_of_image: dict = {}
|
|
39
|
+
|
|
40
|
+
def __init__(self, api_key):
|
|
41
|
+
assert api_key == "key"
|
|
42
|
+
self.messages = self
|
|
43
|
+
|
|
44
|
+
def create(self, **request):
|
|
45
|
+
self.calls.append(request)
|
|
46
|
+
content = request["messages"][0]["content"]
|
|
47
|
+
text = content[-1]["text"]
|
|
48
|
+
schema = request["output_config"]["format"]["schema"]["properties"]
|
|
49
|
+
if "description" in schema:
|
|
50
|
+
shot = int(text.split("Shot ")[1].split(" ")[0])
|
|
51
|
+
answer = {**caption_of(shot), **({"objects": NAMED[shot]} if "objects" in schema else {})}
|
|
52
|
+
elif "objects" in schema:
|
|
53
|
+
images = [base64.b64decode(b["source"]["data"]) for b in content if b["type"] == "image"]
|
|
54
|
+
answer = {"objects": NAMED[self.shot_of_image[images[0]]]}
|
|
55
|
+
else:
|
|
56
|
+
names = text.split("Names:\n")[1].split("\n")
|
|
57
|
+
answer = {"names": [dict(zip(("name", "family", "kind", "worn"), (n, *CLASSES[n]))) for n in names]}
|
|
58
|
+
return SimpleNamespace(stop_reason="end_turn", stop_details=None,
|
|
59
|
+
content=[SimpleNamespace(type="text", text=json.dumps(answer))],
|
|
60
|
+
usage=SimpleNamespace(input_tokens=1000, output_tokens=50))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.fixture
|
|
64
|
+
def stand_in(monkeypatch):
|
|
65
|
+
m = types.ModuleType("anthropic")
|
|
66
|
+
m.Anthropic = Anthropic
|
|
67
|
+
for name in ("RateLimitError", "InternalServerError", "APIConnectionError", "AuthenticationError"):
|
|
68
|
+
setattr(m, name, type(name, (Exception,), {}))
|
|
69
|
+
monkeypatch.setitem(sys.modules, "anthropic", m)
|
|
70
|
+
monkeypatch.setattr(Anthropic, "calls", [])
|
|
71
|
+
monkeypatch.setattr(Anthropic, "shot_of_image", {})
|
|
72
|
+
monkeypatch.setenv("ANTHROPIC_API_KEY", "key")
|
|
73
|
+
for folder in (CAPTIONS, OBJECTS):
|
|
74
|
+
monkeypatch.syspath_prepend(str(folder))
|
|
75
|
+
return Anthropic
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def inputs(folder: Path) -> dict[str, Path]:
|
|
79
|
+
"""Two shots, each with two real keyframes, one face track a shot (person 0), and a word of speech in each."""
|
|
80
|
+
from PIL import Image
|
|
81
|
+
(folder / "keyframes" / "keyframes").mkdir(parents=True)
|
|
82
|
+
shots = []
|
|
83
|
+
for k in range(2):
|
|
84
|
+
frames = []
|
|
85
|
+
for i, x in enumerate("ab"):
|
|
86
|
+
buf = io.BytesIO()
|
|
87
|
+
Image.new("RGB", (64, 48), (40 * k + 20 * i, 90, 160)).save(buf, format="JPEG")
|
|
88
|
+
(folder / "keyframes" / "keyframes" / f"{k}_{x}.jpg").write_bytes(buf.getvalue())
|
|
89
|
+
Anthropic.shot_of_image[buf.getvalue()] = k
|
|
90
|
+
frames.append({"file": f"assets/keyframes/{k}_{x}.jpg", "t": k + 0.25 + 0.5 * i})
|
|
91
|
+
shots.append({"id": f"shot:{k}", "index": k, "start": float(k), "end": float(k + 1), "keyframes": frames})
|
|
92
|
+
docs = {"media": {"sha256": "0" * 64, "duration": 2.0, "width": 64, "height": 48, "fps": 10},
|
|
93
|
+
"shots": {"layer": "shots", "schema_version": "0.1.0", "shots": shots},
|
|
94
|
+
"faces": {"layer": "faces", "people": [{"id": "person:0", "first_seen": 0.1}],
|
|
95
|
+
"tracks": [{"id": f"face:{k}", "shot": f"shot:{k}", "person": "person:0",
|
|
96
|
+
"samples": [{"t": k + 0.25, "box": [10, 8, 12, 14]}]} for k in range(2)]},
|
|
97
|
+
"transcript": {"words": [{"w": "hello", "start": 0.3, "end": 0.6, "voice": "voice:0"},
|
|
98
|
+
{"w": "again", "start": 1.4, "end": 1.7, "voice": "voice:0"}]}}
|
|
99
|
+
for name, doc in docs.items():
|
|
100
|
+
(folder / f"{name}.json").write_text(json.dumps(doc))
|
|
101
|
+
return {"keyframes": folder / "keyframes", **{name: folder / f"{name}.json" for name in docs}}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def run(script: Path, folder: Path, given: dict, config: dict, monkeypatch) -> dict:
|
|
105
|
+
"""A node script's response, run here as the coordinator runs it (its request, its own folder)."""
|
|
106
|
+
folder.mkdir(parents=True)
|
|
107
|
+
request = folder / "request.json"
|
|
108
|
+
request.write_text(json.dumps({"protocol": 1, "out_dir": str(folder / "out"), "accelerator": "cpu", "config": config,
|
|
109
|
+
"secrets": ["ANTHROPIC_API_KEY"], "producer": {"stage": "captions", "stage_version": "1.0.0"},
|
|
110
|
+
"inputs": {name: {"path": str(path)} for name, path in given.items()}}))
|
|
111
|
+
monkeypatch.setattr(sys, "argv", [script.name, "--request", str(request)])
|
|
112
|
+
try:
|
|
113
|
+
runpy.run_path(str(script), run_name="__main__")
|
|
114
|
+
except SystemExit: # how a node ends after writing a failed response
|
|
115
|
+
pass
|
|
116
|
+
response = json.loads((folder / "response.json").read_text())
|
|
117
|
+
assert response["ok"], response["error"]
|
|
118
|
+
return response
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
CONFIG = {"model": "claude-haiku-5-5", "effort": "medium"}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_its_vocabulary_is_the_names_steps_and_its_layers_are_the_captions_steps(tmp_path, monkeypatch, stand_in):
|
|
125
|
+
given = inputs(tmp_path / "in")
|
|
126
|
+
run(OBJECTS / "names.py", tmp_path / "names", {k: given[k] for k in ("shots", "keyframes", "media")}, {}, monkeypatch)
|
|
127
|
+
run(CAPTIONS / "node.py", tmp_path / "captions", given, CONFIG, monkeypatch)
|
|
128
|
+
plain = [c for c in stand_in.calls if "description" in c["output_config"]["format"]["schema"]["properties"]]
|
|
129
|
+
stand_in.calls.clear()
|
|
130
|
+
both = run(HERE / "node.py", tmp_path / "both", given, CONFIG, monkeypatch)
|
|
131
|
+
out = lambda step, name: (tmp_path / step / "out" / name).read_bytes()
|
|
132
|
+
assert out("both", "vocabulary.json") == out("names", "vocabulary.json")
|
|
133
|
+
vocabulary = json.loads(out("both", "vocabulary.json"))
|
|
134
|
+
assert vocabulary["shots"][1]["names"] == ["chair", "poster", "helmet"] and vocabulary["shots"][1]["body_named"] == ["person"]
|
|
135
|
+
assert out("both", "shots.json") == out("captions", "shots.json") and out("both", "text.json") == out("captions", "text.json")
|
|
136
|
+
# one call a shot, as the captions make, with the names part added; then the names' classes
|
|
137
|
+
calls = stand_in.calls
|
|
138
|
+
assert len(calls) == 3 and "names" in calls[2]["output_config"]["format"]["schema"]["properties"]
|
|
139
|
+
for mine, theirs in zip(calls[:2], plain):
|
|
140
|
+
assert mine["messages"][0]["content"][:-1] == theirs["messages"][0]["content"][:-1] # the same keyframes
|
|
141
|
+
assert mine["messages"][0]["content"][-1]["text"].startswith(theirs["messages"][0]["content"][-1]["text"])
|
|
142
|
+
assert "Names used in earlier shots: (none yet)" in calls[0]["messages"][0]["content"][-1]["text"]
|
|
143
|
+
# every name given before (cleaned; a garment too, as the names step tells it), never a later shot's
|
|
144
|
+
assert "Names used in earlier shots: chair, helmet, jacket, mug" in calls[1]["messages"][0]["content"][-1]["text"]
|
|
145
|
+
assert both["usage"]["calls"] == 2 and both["usage"]["class_calls"] == 1 and both["usage"]["usd"] > 0
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_its_usage_counts_the_class_call_and_names_haiku_with_any_captions_model(tmp_path, monkeypatch, stand_in):
|
|
149
|
+
"""The class call is Haiku's whatever model captions: it is among the models used, and its tokens are counted."""
|
|
150
|
+
given = inputs(tmp_path / "in")
|
|
151
|
+
both = run(HERE / "node.py", tmp_path / "both", given, {**CONFIG, "model": "claude-opus-5-5"}, monkeypatch)
|
|
152
|
+
assert {m["id"] for m in both["models"]} >= {"claude-opus-5-5", "claude-haiku-5-5"}
|
|
153
|
+
usage = both["usage"] # the stand-in bills 1000 tokens in, 50 out a call: two shots' captions, one class call
|
|
154
|
+
assert (usage["tokens_in"], usage["tokens_out"], usage["calls"], usage["class_calls"]) == (3000, 150, 2, 1)
|