Deploy cleaned hand annotation PR and verified minimal worker image
Browse files- deploy.py +5 -2
- flora-worker-image.json +3 -2
- hand-pipeline-catalog.json +3 -3
- hand-worker-image.json +21 -24
- hand_actions.py +56 -1
- pipeline-source.tar.gz +2 -2
- pipeline-version.json +2 -2
- studio.py +1 -3
- test_hand_actions.py +7 -1
deploy.py
CHANGED
|
@@ -63,10 +63,13 @@ def main():
|
|
| 63 |
[
|
| 64 |
sys.executable,
|
| 65 |
"-c",
|
| 66 |
-
"import json; from
|
| 67 |
],
|
| 68 |
cwd=temporary,
|
| 69 |
-
env={
|
|
|
|
|
|
|
|
|
|
| 70 |
text=True,
|
| 71 |
)
|
| 72 |
)
|
|
|
|
| 63 |
[
|
| 64 |
sys.executable,
|
| 65 |
"-c",
|
| 66 |
+
"import json; from hand_actions import public_catalog; print(json.dumps(public_catalog()))",
|
| 67 |
],
|
| 68 |
cwd=temporary,
|
| 69 |
+
env={
|
| 70 |
+
**os.environ,
|
| 71 |
+
"PYTHONPATH": os.pathsep.join([str(Path(temporary) / "src"), str(folder)]),
|
| 72 |
+
},
|
| 73 |
text=True,
|
| 74 |
)
|
| 75 |
)
|
flora-worker-image.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
| 31 |
"flora_batcher.py": "38e03e2032af8d5f3ed34980cc4d89927fe70c9742aa2ce13f4d0ccdd6fba727",
|
| 32 |
"flora_speed_entry.py": "192b18d75b9d876c2a65d88de340c8cea7c8da90543d0510b5ee6865d123d524",
|
| 33 |
"studio_runtime.py": "2b393bf7fafcdead638ad1cea35e49df8c6edce725f5545b76fd68e0a8fedd80",
|
| 34 |
-
"pipeline-version.json": "
|
| 35 |
"flora_vision.py": "a5a4231abf7b615164c40986df5f87047f745a23c55d86385d88f2a09e70f1e8"
|
| 36 |
},
|
| 37 |
"acceptance": {
|
|
@@ -98,6 +98,7 @@
|
|
| 98 |
]
|
| 99 |
},
|
| 100 |
"inference_or_benchmark_rerun": false,
|
| 101 |
-
"core_change_scope": "Hand package only; other src files byte-identical to accepted pipeline bd4c312dac38068440edf59e764ba0c68ab7ce0b"
|
|
|
|
| 102 |
}
|
| 103 |
}
|
|
|
|
| 31 |
"flora_batcher.py": "38e03e2032af8d5f3ed34980cc4d89927fe70c9742aa2ce13f4d0ccdd6fba727",
|
| 32 |
"flora_speed_entry.py": "192b18d75b9d876c2a65d88de340c8cea7c8da90543d0510b5ee6865d123d524",
|
| 33 |
"studio_runtime.py": "2b393bf7fafcdead638ad1cea35e49df8c6edce725f5545b76fd68e0a8fedd80",
|
| 34 |
+
"pipeline-version.json": "c42cab00f33cbe53582b0ad56f384d453cbb747eef6db7ec1ba91803bc469681",
|
| 35 |
"flora_vision.py": "a5a4231abf7b615164c40986df5f87047f745a23c55d86385d88f2a09e70f1e8"
|
| 36 |
},
|
| 37 |
"acceptance": {
|
|
|
|
| 98 |
]
|
| 99 |
},
|
| 100 |
"inference_or_benchmark_rerun": false,
|
| 101 |
+
"core_change_scope": "Hand package only; other src files byte-identical to accepted pipeline bd4c312dac38068440edf59e764ba0c68ab7ce0b",
|
| 102 |
+
"pipeline_revision": "850bb584c545ebe9bfcefdcd9b02391cba1c4638"
|
| 103 |
}
|
| 104 |
}
|
hand-pipeline-catalog.json
CHANGED
|
@@ -29,14 +29,14 @@
|
|
| 29 |
"frames": 1500,
|
| 30 |
"planning_fps": null,
|
| 31 |
"seconds": null,
|
| 32 |
-
"basis": "
|
| 33 |
"reference_fps": [
|
| 34 |
39.41450219151414,
|
| 35 |
41.03790957211554
|
| 36 |
],
|
| 37 |
"reference_compute": "RTX 5090",
|
| 38 |
"reference_scope": "Two fresh ten-clip development samples, 1500 input frames and 1120 accepted frames each. Includes startup, decode, calibration, fresh DROID-W+MoGe, hands and native156D export. Excludes installation and model downloads; not a cold filesystem-cache benchmark.",
|
| 39 |
-
"caveat": "
|
| 40 |
},
|
| 41 |
"licenses": {
|
| 42 |
"egohand": "https://huggingface.co/nvidia/EgoHand-v1",
|
|
@@ -44,5 +44,5 @@
|
|
| 44 |
"soma": "https://huggingface.co/nvidia/SOMA-X",
|
| 45 |
"runtime": "https://github.com/NVlabs/GEM-X/blob/20555befe2fbbe5b2015e1fff7ff214dc18ea9d6/ATTRIBUTIONS.md"
|
| 46 |
},
|
| 47 |
-
"lerobot_revision": "
|
| 48 |
}
|
|
|
|
| 29 |
"frames": 1500,
|
| 30 |
"planning_fps": null,
|
| 31 |
"seconds": null,
|
| 32 |
+
"basis": "Runtime depends on video resolution and GPU",
|
| 33 |
"reference_fps": [
|
| 34 |
39.41450219151414,
|
| 35 |
41.03790957211554
|
| 36 |
],
|
| 37 |
"reference_compute": "RTX 5090",
|
| 38 |
"reference_scope": "Two fresh ten-clip development samples, 1500 input frames and 1120 accepted frames each. Includes startup, decode, calibration, fresh DROID-W+MoGe, hands and native156D export. Excludes installation and model downloads; not a cold filesystem-cache benchmark.",
|
| 39 |
+
"caveat": "Queue, installation, downloads and dataset copying add time."
|
| 40 |
},
|
| 41 |
"licenses": {
|
| 42 |
"egohand": "https://huggingface.co/nvidia/EgoHand-v1",
|
|
|
|
| 44 |
"soma": "https://huggingface.co/nvidia/SOMA-X",
|
| 45 |
"runtime": "https://github.com/NVlabs/GEM-X/blob/20555befe2fbbe5b2015e1fff7ff214dc18ea9d6/ATTRIBUTIONS.md"
|
| 46 |
},
|
| 47 |
+
"lerobot_revision": "850bb584c545ebe9bfcefdcd9b02391cba1c4638"
|
| 48 |
}
|
hand-worker-image.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
{
|
| 2 |
"recipe": "egohand-soma-v3-checkpoint-overlap-v4",
|
| 3 |
"build_verified": true,
|
| 4 |
-
"image_repo": "finerobotics/
|
| 5 |
-
"image_revision": "
|
| 6 |
-
"image": "hf.co/spaces/finerobotics/
|
| 7 |
-
"image_version_sha256": "
|
| 8 |
"assets": {
|
| 9 |
"repo": "finerobotics/egohand-soma-runtime",
|
| 10 |
"revision": "cb082150aed8612fd50a6a78a0e37deebf24b6c3",
|
|
@@ -30,28 +30,25 @@
|
|
| 30 |
"timeout_s": 7200
|
| 31 |
},
|
| 32 |
"gpu_acceptance": {
|
| 33 |
-
"job": "pepijn223/
|
| 34 |
-
"
|
| 35 |
-
"
|
| 36 |
-
"
|
| 37 |
-
"
|
| 38 |
-
"valid_frames": 741,
|
| 39 |
-
"invalid_masked_frames": 10,
|
| 40 |
"native_parameters_finite": true,
|
| 41 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
"calibration_mode": "estimate-moge",
|
| 43 |
"metric_accuracy_validated": false,
|
| 44 |
-
"
|
| 45 |
-
"compute_flavor": "h200",
|
| 46 |
-
"source_resolution": [
|
| 47 |
-
3840,
|
| 48 |
-
2160
|
| 49 |
-
],
|
| 50 |
-
"source_fps": 10,
|
| 51 |
-
"native_annotation_seconds": 82.9781820140197,
|
| 52 |
-
"job_running_seconds": 557,
|
| 53 |
-
"job_wall_seconds": 729,
|
| 54 |
-
"compute_usd_estimated": 0.83333,
|
| 55 |
-
"invoice_available": false
|
| 56 |
}
|
| 57 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"recipe": "egohand-soma-v3-checkpoint-overlap-v4",
|
| 3 |
"build_verified": true,
|
| 4 |
+
"image_repo": "finerobotics/annotation-hands-worker",
|
| 5 |
+
"image_revision": "775ee404b1c327b6f952561f761c5be27d455122",
|
| 6 |
+
"image": "hf.co/spaces/finerobotics/annotation-hands-worker",
|
| 7 |
+
"image_version_sha256": "96757674746f24b8e2031e6d0ca656b8d24681eb6493a2e826edbbd735dbaf6f",
|
| 8 |
"assets": {
|
| 9 |
"repo": "finerobotics/egohand-soma-runtime",
|
| 10 |
"revision": "cb082150aed8612fd50a6a78a0e37deebf24b6c3",
|
|
|
|
| 30 |
"timeout_s": 7200
|
| 31 |
},
|
| 32 |
"gpu_acceptance": {
|
| 33 |
+
"job": "pepijn223/6adb536b046eb82b1ecd4421",
|
| 34 |
+
"core_revision": "850bb584c545ebe9bfcefdcd9b02391cba1c4638",
|
| 35 |
+
"image_revision": "775ee404b1c327b6f952561f761c5be27d455122",
|
| 36 |
+
"retained_frames": 60,
|
| 37 |
+
"valid_frames": 60,
|
|
|
|
|
|
|
| 38 |
"native_parameters_finite": true,
|
| 39 |
+
"camera_depth_prior": {
|
| 40 |
+
"mode": "on",
|
| 41 |
+
"counts": {
|
| 42 |
+
"depth_loads": 1,
|
| 43 |
+
"depth_predictions": 7,
|
| 44 |
+
"depth_bypasses": 0
|
| 45 |
+
},
|
| 46 |
+
"metric_depth_reg": true,
|
| 47 |
+
"source_sha256": "9bb2fe41b6c52cfe8d4d2f584e9ea4d230b83a27bd44afb43c401cd63d0f7f04",
|
| 48 |
+
"gt_scale_fit": false
|
| 49 |
+
},
|
| 50 |
"calibration_mode": "estimate-moge",
|
| 51 |
"metric_accuracy_validated": false,
|
| 52 |
+
"compute_flavor": "h200"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
}
|
| 54 |
}
|
hand_actions.py
CHANGED
|
@@ -1,15 +1,70 @@
|
|
| 1 |
"""Native EgoHand/SOMA actions in the shared annotation wizard and HF Jobs lifecycle."""
|
| 2 |
|
| 3 |
import json
|
|
|
|
| 4 |
import re
|
| 5 |
from dataclasses import asdict
|
| 6 |
from pathlib import Path
|
| 7 |
|
| 8 |
-
from lerobot.annotations.hands.config import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
from lerobot.annotations.hands.intrinsics import METHOD
|
| 10 |
from lerobot.annotations.hands.writer import validate_calibration
|
| 11 |
|
| 12 |
HANDS = "Hand actions"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
def runtime_configuration():
|
|
|
|
| 1 |
"""Native EgoHand/SOMA actions in the shared annotation wizard and HF Jobs lifecycle."""
|
| 2 |
|
| 3 |
import json
|
| 4 |
+
import math
|
| 5 |
import re
|
| 6 |
from dataclasses import asdict
|
| 7 |
from pathlib import Path
|
| 8 |
|
| 9 |
+
from lerobot.annotations.hands.config import (
|
| 10 |
+
ACTION_SCHEMA,
|
| 11 |
+
GEM_REVISION,
|
| 12 |
+
RECIPE,
|
| 13 |
+
RUNTIME_REVISION,
|
| 14 |
+
HandBackendConfig,
|
| 15 |
+
)
|
| 16 |
from lerobot.annotations.hands.intrinsics import METHOD
|
| 17 |
from lerobot.annotations.hands.writer import validate_calibration
|
| 18 |
|
| 19 |
HANDS = "Hand actions"
|
| 20 |
+
COMPUTE = {
|
| 21 |
+
"a100-large": "A100 80 GB",
|
| 22 |
+
"rtx-pro-6000": "RTX PRO 6000 96 GB",
|
| 23 |
+
"h200": "H200 141 GB",
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def estimate(frames, planning_fps=None):
|
| 28 |
+
"""Separate a planning assumption from the qualified development reference."""
|
| 29 |
+
if planning_fps is not None and (not math.isfinite(planning_fps) or planning_fps <= 0):
|
| 30 |
+
raise ValueError("Planning FPS must be finite and positive")
|
| 31 |
+
return {
|
| 32 |
+
"frames": frames,
|
| 33 |
+
"planning_fps": planning_fps,
|
| 34 |
+
"seconds": frames / planning_fps if planning_fps else None,
|
| 35 |
+
"basis": "user planning assumption"
|
| 36 |
+
if planning_fps
|
| 37 |
+
else "Runtime depends on video resolution and GPU",
|
| 38 |
+
"reference_fps": [39.41450219151414, 41.03790957211554],
|
| 39 |
+
"reference_compute": "RTX 5090",
|
| 40 |
+
"reference_scope": "Two fresh ten-clip development samples, 1500 input frames and 1120 accepted "
|
| 41 |
+
"frames each. Includes startup, decode, calibration, fresh DROID-W+MoGe, hands and native156D "
|
| 42 |
+
"export. Excludes installation and model downloads; not a cold filesystem-cache benchmark.",
|
| 43 |
+
"caveat": "Queue, installation, downloads and dataset copying add time.",
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def public_catalog():
|
| 48 |
+
"""Expose Studio's compute choices and model terms alongside the core recipe."""
|
| 49 |
+
return {
|
| 50 |
+
"recipe": RECIPE,
|
| 51 |
+
"runtime_revision": RUNTIME_REVISION,
|
| 52 |
+
"gem_revision": GEM_REVISION,
|
| 53 |
+
"action_schema": ACTION_SCHEMA,
|
| 54 |
+
"action_dimension": 156,
|
| 55 |
+
"camera": "DROID-W + MoGe Small (required)",
|
| 56 |
+
"hand_model": "nvidia/EgoHand-v1",
|
| 57 |
+
"hand_representation": "SOMA/MHR",
|
| 58 |
+
"motion_infilling": False,
|
| 59 |
+
"profiles": [{"key": key, "name": value, "reference_fps": None} for key, value in COMPUTE.items()],
|
| 60 |
+
"reference": estimate(1500),
|
| 61 |
+
"licenses": {
|
| 62 |
+
"egohand": "https://huggingface.co/nvidia/EgoHand-v1",
|
| 63 |
+
"detector": "https://huggingface.co/nvidia/RT-DETR-Hand-Detector-v1",
|
| 64 |
+
"soma": "https://huggingface.co/nvidia/SOMA-X",
|
| 65 |
+
"runtime": f"https://github.com/NVlabs/GEM-X/blob/{GEM_REVISION}/ATTRIBUTIONS.md",
|
| 66 |
+
},
|
| 67 |
+
}
|
| 68 |
|
| 69 |
|
| 70 |
def runtime_configuration():
|
pipeline-source.tar.gz
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ba5218df544d3a6491b97a10cb36506bec38ffddff40ce51a8947560ff52eee4
|
| 3 |
+
size 1869569
|
pipeline-version.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
-
"git_commit": "
|
| 3 |
-
"sha256": "
|
| 4 |
"preset": "abc_phases_schema_with_original_window_recovery",
|
| 5 |
"preset_date": "2026-09-25",
|
| 6 |
"engine": "lerobot.annotations.runtime",
|
|
|
|
| 1 |
{
|
| 2 |
+
"git_commit": "850bb584c545ebe9bfcefdcd9b02391cba1c4638",
|
| 3 |
+
"sha256": "ba5218df544d3a6491b97a10cb36506bec38ffddff40ce51a8947560ff52eee4",
|
| 4 |
"preset": "abc_phases_schema_with_original_window_recovery",
|
| 5 |
"preset_date": "2026-09-25",
|
| 6 |
"engine": "lerobot.annotations.runtime",
|
studio.py
CHANGED
|
@@ -14,7 +14,7 @@ from pathlib import Path, PurePosixPath
|
|
| 14 |
import pyarrow.parquet as pq
|
| 15 |
from flora_captioner import FLORA
|
| 16 |
from flora_image import validated_worker_image
|
| 17 |
-
from hand_actions import HANDS, prepare_hands, runtime_configuration
|
| 18 |
from huggingface_hub import HfApi, hf_hub_download
|
| 19 |
from huggingface_hub.utils import validate_repo_id
|
| 20 |
from model_catalog import (
|
|
@@ -28,8 +28,6 @@ from model_catalog import (
|
|
| 28 |
)
|
| 29 |
from prompt_config import TEMPLATES, validate_prompts
|
| 30 |
|
| 31 |
-
from lerobot.annotations.hands.config import COMPUTE
|
| 32 |
-
|
| 33 |
MODEL = model_profile()["route"]
|
| 34 |
PROVIDER = model_profile()["provider"]
|
| 35 |
FEATURES = {
|
|
|
|
| 14 |
import pyarrow.parquet as pq
|
| 15 |
from flora_captioner import FLORA
|
| 16 |
from flora_image import validated_worker_image
|
| 17 |
+
from hand_actions import COMPUTE, HANDS, prepare_hands, runtime_configuration
|
| 18 |
from huggingface_hub import HfApi, hf_hub_download
|
| 19 |
from huggingface_hub.utils import validate_repo_id
|
| 20 |
from model_catalog import (
|
|
|
|
| 28 |
)
|
| 29 |
from prompt_config import TEMPLATES, validate_prompts
|
| 30 |
|
|
|
|
|
|
|
| 31 |
MODEL = model_profile()["route"]
|
| 32 |
PROVIDER = model_profile()["provider"]
|
| 33 |
FEATURES = {
|
test_hand_actions.py
CHANGED
|
@@ -15,7 +15,7 @@ import pytest
|
|
| 15 |
sys.path.insert(0, str(Path(__file__).parent))
|
| 16 |
import studio
|
| 17 |
import worker
|
| 18 |
-
from hand_actions import HANDS, catalog
|
| 19 |
from studio_runtime import run_episode
|
| 20 |
|
| 21 |
from lerobot.annotations.hands.config import ACTION_SCHEMA
|
|
@@ -23,6 +23,12 @@ from lerobot.annotations.hands.writer import PARAMETERS
|
|
| 23 |
from lerobot.annotations.steerable_pipeline.reader import EpisodeRecord
|
| 24 |
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
def test_unconfigured_runtime_cannot_claim_ready(monkeypatch):
|
| 27 |
monkeypatch.setattr(
|
| 28 |
"hand_actions.runtime_configuration",
|
|
|
|
| 15 |
sys.path.insert(0, str(Path(__file__).parent))
|
| 16 |
import studio
|
| 17 |
import worker
|
| 18 |
+
from hand_actions import COMPUTE, HANDS, catalog, estimate
|
| 19 |
from studio_runtime import run_episode
|
| 20 |
|
| 21 |
from lerobot.annotations.hands.config import ACTION_SCHEMA
|
|
|
|
| 23 |
from lerobot.annotations.steerable_pipeline.reader import EpisodeRecord
|
| 24 |
|
| 25 |
|
| 26 |
+
def test_no_fabricated_hf_throughput():
|
| 27 |
+
assert set(COMPUTE) == {"h200", "a100-large", "rtx-pro-6000"}
|
| 28 |
+
assert estimate(3000)["seconds"] is None
|
| 29 |
+
assert estimate(3000)["reference_fps"] == [39.41450219151414, 41.03790957211554]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
def test_unconfigured_runtime_cannot_claim_ready(monkeypatch):
|
| 33 |
monkeypatch.setattr(
|
| 34 |
"hand_actions.runtime_configuration",
|