evalmc 0.0.1__py3-none-any.whl
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.
- evalmc/__init__.py +0 -0
- evalmc/__main__.py +4 -0
- evalmc/check_challenge.py +124 -0
- evalmc/check_registered.py +50 -0
- evalmc/host.py +74 -0
- evalmc/login.py +124 -0
- evalmc/main.py +39 -0
- evalmc/push_helpers.py +136 -0
- evalmc/register.py +47 -0
- evalmc/set_token.py +56 -0
- evalmc/status.py +63 -0
- evalmc/submission.py +558 -0
- evalmc/submit.py +88 -0
- evalmc/submit_onnx.py +62 -0
- evalmc/utils.py +220 -0
- evalmc-0.0.1.dist-info/METADATA +89 -0
- evalmc-0.0.1.dist-info/RECORD +21 -0
- evalmc-0.0.1.dist-info/WHEEL +5 -0
- evalmc-0.0.1.dist-info/entry_points.txt +2 -0
- evalmc-0.0.1.dist-info/licenses/LICENSE +190 -0
- evalmc-0.0.1.dist-info/top_level.txt +1 -0
evalmc/__init__.py
ADDED
|
File without changes
|
evalmc/__main__.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def check_task_phase(api_url, task, phase):
|
|
7
|
+
"""Check if a specific task-phase exists and get its details."""
|
|
8
|
+
try:
|
|
9
|
+
response = requests.get(f"{api_url}/task-phase/{task}/{phase}")
|
|
10
|
+
if response.status_code == 200:
|
|
11
|
+
data = response.json()
|
|
12
|
+
return {
|
|
13
|
+
"exists": True,
|
|
14
|
+
"id": data.get("id"),
|
|
15
|
+
"task": data.get("task"),
|
|
16
|
+
"phase": data.get("phase"),
|
|
17
|
+
"num_scores": data.get("num_scores"),
|
|
18
|
+
"score_names": data.get("score_names"),
|
|
19
|
+
"display_name": data.get("display_name"),
|
|
20
|
+
"visible": data.get("visible"),
|
|
21
|
+
"created_at": data.get("created_at"),
|
|
22
|
+
}
|
|
23
|
+
else:
|
|
24
|
+
return {"exists": False, "error": f"HTTP {response.status_code}"}
|
|
25
|
+
except requests.exceptions.RequestException as e:
|
|
26
|
+
return {"exists": False, "error": str(e)}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def list_visible_task_phases(api_url):
|
|
30
|
+
"""Fetch visible task-phases from the API."""
|
|
31
|
+
try:
|
|
32
|
+
response = requests.get(f"{api_url}/task-phases", params={"visible": True})
|
|
33
|
+
if response.status_code == 200:
|
|
34
|
+
return response.json()
|
|
35
|
+
return []
|
|
36
|
+
except requests.exceptions.RequestException:
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_leaderboard(api_url, task, phase):
|
|
41
|
+
"""Check if there's a leaderboard for a specific task-phase."""
|
|
42
|
+
try:
|
|
43
|
+
response = requests.get(f"{api_url}/leaderboard/{task}/{phase}")
|
|
44
|
+
if response.status_code == 200:
|
|
45
|
+
data = response.json()
|
|
46
|
+
return {
|
|
47
|
+
"exists": True,
|
|
48
|
+
"submission_count": len(data.get("leaderboard", [])),
|
|
49
|
+
"leaderboard": data.get("leaderboard", []),
|
|
50
|
+
}
|
|
51
|
+
else:
|
|
52
|
+
return {"exists": False, "error": f"HTTP {response.status_code}"}
|
|
53
|
+
except requests.exceptions.RequestException as e:
|
|
54
|
+
return {"exists": False, "error": str(e)}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_competition_stats(api_url):
|
|
58
|
+
"""Get overall competition statistics."""
|
|
59
|
+
stats = {}
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
response = requests.get(f"{api_url}/users")
|
|
63
|
+
if response.status_code == 200:
|
|
64
|
+
data = response.json()
|
|
65
|
+
stats["total_teams"] = data.get("total_count", 0)
|
|
66
|
+
stats["teams"] = data.get("users", [])
|
|
67
|
+
else:
|
|
68
|
+
stats["teams_error"] = f"HTTP {response.status_code}"
|
|
69
|
+
except requests.exceptions.RequestException as e:
|
|
70
|
+
stats["teams_error"] = str(e)
|
|
71
|
+
|
|
72
|
+
return stats
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def main():
|
|
76
|
+
ap = argparse.ArgumentParser(
|
|
77
|
+
description="Check the active phases and tasks for the MyoChallenge competition"
|
|
78
|
+
)
|
|
79
|
+
ap.add_argument(
|
|
80
|
+
"--api",
|
|
81
|
+
required=True,
|
|
82
|
+
help="API endpoint URL (e.g., https://api.mc.myosuite.org)",
|
|
83
|
+
)
|
|
84
|
+
args = ap.parse_args()
|
|
85
|
+
|
|
86
|
+
print("š MyoChallenge Competition Status Check")
|
|
87
|
+
print("=" * 60)
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
info = requests.get(f"{args.api}/challenge-info", timeout=10)
|
|
91
|
+
if info.status_code == 200:
|
|
92
|
+
data = info.json()
|
|
93
|
+
print(f"ā
{data.get('title', 'MyoChallenge')} (year {data.get('year')})")
|
|
94
|
+
else:
|
|
95
|
+
print("ā ļø Could not load challenge-info")
|
|
96
|
+
except requests.exceptions.RequestException as e:
|
|
97
|
+
print(f"ā Cannot connect to API server: {e}")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
print()
|
|
101
|
+
|
|
102
|
+
visible = list_visible_task_phases(args.api)
|
|
103
|
+
print("š Visible Task-Phase Configurations:")
|
|
104
|
+
print("-" * 40)
|
|
105
|
+
|
|
106
|
+
if not visible:
|
|
107
|
+
print("ā No visible task-phases configured")
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
total_score_metrics = 0
|
|
111
|
+
for tp in visible:
|
|
112
|
+
total_score_metrics += tp.get("num_scores", 0)
|
|
113
|
+
label = tp.get("display_name") or f"{tp['task']} phase {tp['phase']}"
|
|
114
|
+
print(f"ā
{label}: {tp['num_scores']} score metrics")
|
|
115
|
+
print(f" Score Names: {', '.join(tp.get('score_names', []))}")
|
|
116
|
+
print()
|
|
117
|
+
|
|
118
|
+
print(
|
|
119
|
+
f"š Summary: {len(visible)} visible task-phases with {total_score_metrics} total score metrics"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
main()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
# Example: python check_registered.py --api https://api.mc.myosuite.org --email foo@bar.com
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
ap = argparse.ArgumentParser(
|
|
10
|
+
description="Check if a team is registered for the MyoChallenge competition"
|
|
11
|
+
)
|
|
12
|
+
ap.add_argument(
|
|
13
|
+
"--api",
|
|
14
|
+
required=True,
|
|
15
|
+
help="API endpoint URL (e.g., https://api.mc.myosuite.org)",
|
|
16
|
+
)
|
|
17
|
+
ap.add_argument("--team", required=True, help="Team name to check")
|
|
18
|
+
args = ap.parse_args()
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
r = requests.post(f"{args.api}/check", json={"team_name": args.team})
|
|
22
|
+
r.raise_for_status()
|
|
23
|
+
result = r.json()
|
|
24
|
+
|
|
25
|
+
if result["exists"]:
|
|
26
|
+
print("\nā
Team found!")
|
|
27
|
+
if result.get("id"):
|
|
28
|
+
print(f"ID: {result['id']}")
|
|
29
|
+
print(f"Team Name: {result['team_name']}")
|
|
30
|
+
print(f"Email: {result['email']}")
|
|
31
|
+
if result.get("registered_at"):
|
|
32
|
+
print(f"Registered: {result['registered_at']}")
|
|
33
|
+
print()
|
|
34
|
+
else:
|
|
35
|
+
print(f"\nā Team '{args.team}' is not registered.\n")
|
|
36
|
+
|
|
37
|
+
except requests.exceptions.RequestException as e:
|
|
38
|
+
if hasattr(e, "response") and e.response is not None:
|
|
39
|
+
try:
|
|
40
|
+
error_detail = e.response.json().get("detail", str(e))
|
|
41
|
+
except ValueError:
|
|
42
|
+
error_detail = e.response.text or str(e)
|
|
43
|
+
print(f"\nā Check failed: {error_detail}")
|
|
44
|
+
else:
|
|
45
|
+
print(f"\nā Check failed: {str(e)}")
|
|
46
|
+
exit(1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
main()
|
evalmc/host.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MyoChallenge Host - Configure API host URL
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
|
|
11
|
+
# Configuration file paths
|
|
12
|
+
CONFIG_DIR = Path.home() / ".myochallenge"
|
|
13
|
+
HOST_URL_FILE = CONFIG_DIR / "host_url"
|
|
14
|
+
|
|
15
|
+
# Ensure config directory exists
|
|
16
|
+
CONFIG_DIR.mkdir(exist_ok=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.command()
|
|
20
|
+
@click.option(
|
|
21
|
+
"-sh", "--set-host", help="Set the Host URL of the MyoChallenge API Instance."
|
|
22
|
+
)
|
|
23
|
+
def host(set_host):
|
|
24
|
+
"""
|
|
25
|
+
View and configure the Host URL.
|
|
26
|
+
"""
|
|
27
|
+
if set_host is not None:
|
|
28
|
+
# Basic URL validation
|
|
29
|
+
if set_host.startswith(("http://", "https://")):
|
|
30
|
+
try:
|
|
31
|
+
HOST_URL_FILE.write_text(set_host)
|
|
32
|
+
click.echo(
|
|
33
|
+
click.style(
|
|
34
|
+
f"{set_host} is set as the host url.",
|
|
35
|
+
bold=True,
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
except (OSError, IOError) as e:
|
|
39
|
+
click.echo(f"Error saving host URL: {e}")
|
|
40
|
+
sys.exit(1)
|
|
41
|
+
else:
|
|
42
|
+
click.echo(
|
|
43
|
+
click.style(
|
|
44
|
+
"Sorry, please enter a valid url.\n"
|
|
45
|
+
"Example: https://api.mc.myosuite.org or https://myochallenge.com",
|
|
46
|
+
bold=True,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
else:
|
|
51
|
+
if not HOST_URL_FILE.exists():
|
|
52
|
+
click.echo(
|
|
53
|
+
click.style(
|
|
54
|
+
"You haven't configured a Host URL for the CLI.\n"
|
|
55
|
+
"The CLI would be using https://api.mc.myosuite.org as the default url.",
|
|
56
|
+
bold=True,
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
try:
|
|
61
|
+
data = HOST_URL_FILE.read_text().strip()
|
|
62
|
+
click.echo(
|
|
63
|
+
click.style(
|
|
64
|
+
f"{data} is the Host URL of MyoChallenge.",
|
|
65
|
+
bold=True,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
except (OSError, IOError) as e:
|
|
69
|
+
click.echo(f"Error reading host URL: {e}")
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
host() # type: ignore
|
evalmc/login.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MyoChallenge Login - Interactive authentication and credential storage
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
# Configuration file paths
|
|
14
|
+
CONFIG_DIR = Path.home() / ".myochallenge"
|
|
15
|
+
HOST_URL_FILE = CONFIG_DIR / "host_url"
|
|
16
|
+
AUTH_TOKEN_FILE = CONFIG_DIR / "auth_token.json"
|
|
17
|
+
|
|
18
|
+
# Ensure config directory exists
|
|
19
|
+
CONFIG_DIR.mkdir(exist_ok=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_default_host():
|
|
23
|
+
"""Get the default host URL from config file."""
|
|
24
|
+
if HOST_URL_FILE.exists():
|
|
25
|
+
try:
|
|
26
|
+
return HOST_URL_FILE.read_text().strip()
|
|
27
|
+
except (OSError, IOError):
|
|
28
|
+
pass
|
|
29
|
+
return "https://api.mc.myosuite.org"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_stored_auth():
|
|
33
|
+
"""Get stored authentication credentials."""
|
|
34
|
+
if AUTH_TOKEN_FILE.exists():
|
|
35
|
+
try:
|
|
36
|
+
with open(AUTH_TOKEN_FILE, "r", encoding="utf-8") as f:
|
|
37
|
+
return json.load(f)
|
|
38
|
+
except (OSError, IOError, json.JSONDecodeError):
|
|
39
|
+
pass
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def authenticate_and_save(email, auth_token, host_url):
|
|
44
|
+
"""Authenticate with the API and save credentials."""
|
|
45
|
+
try:
|
|
46
|
+
response = requests.post(
|
|
47
|
+
f"{host_url}/login",
|
|
48
|
+
json={"email": email, "auth_token": auth_token},
|
|
49
|
+
timeout=10,
|
|
50
|
+
)
|
|
51
|
+
response.raise_for_status()
|
|
52
|
+
auth_data = response.json()
|
|
53
|
+
|
|
54
|
+
if auth_data.get("success"):
|
|
55
|
+
# Store credentials
|
|
56
|
+
credentials = {
|
|
57
|
+
"email": email,
|
|
58
|
+
"auth_token": auth_token,
|
|
59
|
+
"team_name": auth_data.get("team_name"),
|
|
60
|
+
"user_id": auth_data.get("user_id"),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
with open(AUTH_TOKEN_FILE, "w", encoding="utf-8") as f:
|
|
65
|
+
json.dump(credentials, f)
|
|
66
|
+
click.echo(click.style("\nLogged in successfully!", bold=True))
|
|
67
|
+
click.echo(f"Team: {auth_data.get('team_name')}")
|
|
68
|
+
click.echo(f"User ID: {auth_data.get('user_id')}")
|
|
69
|
+
return True
|
|
70
|
+
except (OSError, IOError) as e:
|
|
71
|
+
click.echo(f"Error saving credentials: {e}")
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
else:
|
|
74
|
+
error_message = auth_data.get("message", "Unknown error")
|
|
75
|
+
click.echo(
|
|
76
|
+
click.style(
|
|
77
|
+
f"Login failed: {error_message}",
|
|
78
|
+
fg="red",
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
return False
|
|
82
|
+
except requests.exceptions.RequestException as e:
|
|
83
|
+
click.echo(click.style(f"Connection error: {e}", fg="red"))
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@click.command()
|
|
88
|
+
@click.option(
|
|
89
|
+
"--relogin", is_flag=True, help="Force re-login even if credentials exist"
|
|
90
|
+
)
|
|
91
|
+
def login(relogin):
|
|
92
|
+
"""
|
|
93
|
+
Login to MyoChallenge and save credentials.
|
|
94
|
+
|
|
95
|
+
By default, if credentials already exist, they will be used.
|
|
96
|
+
Use --relogin to force entering new credentials.
|
|
97
|
+
"""
|
|
98
|
+
host_url = get_default_host()
|
|
99
|
+
|
|
100
|
+
# Check if credentials already exist and relogin is not forced
|
|
101
|
+
if not relogin:
|
|
102
|
+
stored_auth = get_stored_auth()
|
|
103
|
+
if stored_auth:
|
|
104
|
+
click.echo("š Found existing credentials. Testing authentication...")
|
|
105
|
+
|
|
106
|
+
# Test existing credentials
|
|
107
|
+
if authenticate_and_save(
|
|
108
|
+
stored_auth["email"], stored_auth["auth_token"], host_url
|
|
109
|
+
):
|
|
110
|
+
click.echo("ā
Using existing credentials - authentication successful!")
|
|
111
|
+
return
|
|
112
|
+
else:
|
|
113
|
+
click.echo("ā Existing credentials failed. Please re-login.")
|
|
114
|
+
# Fall through to prompt for new credentials
|
|
115
|
+
|
|
116
|
+
# Prompt for new credentials
|
|
117
|
+
email = click.prompt("Email", type=str)
|
|
118
|
+
auth_token = click.prompt("Auth Token (Password)", type=str, hide_input=True)
|
|
119
|
+
|
|
120
|
+
authenticate_and_save(email, auth_token, host_url)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
login()
|
evalmc/main.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MyoChallenge CLI - Main entry point for all commands
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from .host import host
|
|
9
|
+
from .login import login
|
|
10
|
+
from .set_token import set_token
|
|
11
|
+
from .status import status
|
|
12
|
+
from .submission import push
|
|
13
|
+
from .submit import submit
|
|
14
|
+
from .submit_onnx import submit_onnx
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group()
|
|
18
|
+
@click.version_option()
|
|
19
|
+
def main():
|
|
20
|
+
"""
|
|
21
|
+
MyoChallenge submission CLI.
|
|
22
|
+
|
|
23
|
+
Two submission paths are available:
|
|
24
|
+
|
|
25
|
+
\b
|
|
26
|
+
evalmc submit policy.zip -p 1 -t tabletennis # zip upload (recommended)
|
|
27
|
+
evalmc submit-onnx model.onnx -p 1 -t elbowflexion # ONNX upload
|
|
28
|
+
evalmc push my-image:tag -p 1 -t tabletennis # Docker/ECR image push
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
main.add_command(host)
|
|
34
|
+
main.add_command(login)
|
|
35
|
+
main.add_command(set_token)
|
|
36
|
+
main.add_command(push)
|
|
37
|
+
main.add_command(submit)
|
|
38
|
+
main.add_command(submit_onnx)
|
|
39
|
+
main.add_command(status)
|
evalmc/push_helpers.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Helpers for evalmc push ā validation and user-facing error explanations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
POLICY_PATHS = (
|
|
8
|
+
"/policy/policy.py",
|
|
9
|
+
"/policy/model.onnx",
|
|
10
|
+
"/app/policy.py",
|
|
11
|
+
"/app/model.onnx",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_local_api(api_base: str) -> bool:
|
|
16
|
+
host = (urlparse(api_base).hostname or "").lower()
|
|
17
|
+
return host in ("localhost", "127.0.0.1", "0.0.0.0")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def http_error_detail(response) -> str:
|
|
21
|
+
try:
|
|
22
|
+
body = response.json()
|
|
23
|
+
if isinstance(body, dict):
|
|
24
|
+
return body.get("detail") or body.get("message") or body.get("error") or str(body)
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
text = (response.text or "").strip()
|
|
28
|
+
return text[:500] if text else f"HTTP {response.status_code}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def image_not_found_help(image: str, build_hint: str | None = None) -> str:
|
|
32
|
+
lines = [
|
|
33
|
+
f"Docker image '{image}' is not available on this machine.",
|
|
34
|
+
"",
|
|
35
|
+
"The image must exist locally before you can push/submit it.",
|
|
36
|
+
"",
|
|
37
|
+
"Try:",
|
|
38
|
+
f" docker images | grep {image.split(':')[0]}",
|
|
39
|
+
]
|
|
40
|
+
if build_hint:
|
|
41
|
+
lines.extend(["", "Build example:", f" {build_hint}"])
|
|
42
|
+
else:
|
|
43
|
+
lines.extend(
|
|
44
|
+
[
|
|
45
|
+
"",
|
|
46
|
+
"Build example (elbow flexion):",
|
|
47
|
+
" cd mcEvalProd/evaluation/elbowflexion/agent",
|
|
48
|
+
" docker build -t my-elbow-agent:latest .",
|
|
49
|
+
]
|
|
50
|
+
)
|
|
51
|
+
lines.extend(
|
|
52
|
+
[
|
|
53
|
+
"",
|
|
54
|
+
"For elbow flexion, zip/ONNX submission is usually simpler:",
|
|
55
|
+
" evalmc submit-onnx model.onnx -t elbowflexion -p 1",
|
|
56
|
+
]
|
|
57
|
+
)
|
|
58
|
+
return "\n".join(lines)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def upload_mode_summary(upload_type: str, api_base: str, repository_uri: str | None) -> str:
|
|
62
|
+
if upload_type == "local":
|
|
63
|
+
if is_local_api(api_base):
|
|
64
|
+
return (
|
|
65
|
+
"Server upload mode: local (dev)\n"
|
|
66
|
+
" The eval worker on the same machine must have the tagged image in Docker."
|
|
67
|
+
)
|
|
68
|
+
return (
|
|
69
|
+
"Server upload mode: local storage (misconfigured for remote Docker push)\n"
|
|
70
|
+
" The API will record a local Docker tag, but the eval worker runs on the\n"
|
|
71
|
+
" server ā it cannot see images that exist only on your laptop.\n"
|
|
72
|
+
" Evaluation will almost certainly fail with status 'failed'."
|
|
73
|
+
)
|
|
74
|
+
if upload_type == "ecr_manual":
|
|
75
|
+
return (
|
|
76
|
+
f"Server upload mode: manual ECR\n"
|
|
77
|
+
f" Repository: {repository_uri or '(unknown)'}\n"
|
|
78
|
+
" You must docker push to ECR yourself before evaluation can run."
|
|
79
|
+
)
|
|
80
|
+
if upload_type == "ecr":
|
|
81
|
+
return (
|
|
82
|
+
f"Server upload mode: ECR (automatic)\n"
|
|
83
|
+
f" Repository: {repository_uri or '(unknown)'}\n"
|
|
84
|
+
" The CLI will build, push to ECR, and record the submission."
|
|
85
|
+
)
|
|
86
|
+
return f"Server upload mode: {upload_type}"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def local_push_remote_warning(api_base: str, local_tag: str) -> str:
|
|
90
|
+
return (
|
|
91
|
+
f"ā ļø Remote API ({api_base}) is in local-storage mode.\n"
|
|
92
|
+
f"\n"
|
|
93
|
+
f"Your image will only be tagged locally as: {local_tag}\n"
|
|
94
|
+
f"The production eval worker cannot pull or see that tag from your machine.\n"
|
|
95
|
+
f"\n"
|
|
96
|
+
f"Expected outcome: submission status 'submitted' ā 'failed' (no leaderboard entry).\n"
|
|
97
|
+
f"\n"
|
|
98
|
+
f"Recommended alternatives for elbow flexion:\n"
|
|
99
|
+
f" evalmc submit-onnx model.onnx -t elbowflexion -p 1\n"
|
|
100
|
+
f" evalmc submit policy.zip -t elbowflexion -p 1\n"
|
|
101
|
+
f"\n"
|
|
102
|
+
f"For Docker on production, the server needs USE_LOCAL_STORAGE=false and ECR push.\n"
|
|
103
|
+
f"Contact the organizers if ECR upload is required."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def policy_missing_help(image: str) -> str:
|
|
108
|
+
paths = ", ".join(POLICY_PATHS)
|
|
109
|
+
return (
|
|
110
|
+
f"Agent image '{image}' does not contain a policy at any of:\n"
|
|
111
|
+
f" {paths}\n"
|
|
112
|
+
f"\n"
|
|
113
|
+
f"Harness-based tasks (e.g. elbowflexion) require policy files inside the image.\n"
|
|
114
|
+
f"\n"
|
|
115
|
+
f"Example Dockerfile:\n"
|
|
116
|
+
f" WORKDIR /policy\n"
|
|
117
|
+
f" COPY policy.py policy_utils.py task_config.json .\n"
|
|
118
|
+
f"\n"
|
|
119
|
+
f"Verify locally:\n"
|
|
120
|
+
f" docker run --rm {image} ls -la /policy"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def post_submit_local_remote_note(submission_id, local_tag: str, api_base: str) -> str:
|
|
125
|
+
return (
|
|
126
|
+
f"\nā ļø Submission {submission_id} recorded, but evaluation may fail on the server.\n"
|
|
127
|
+
f" Image URI stored: {local_tag}\n"
|
|
128
|
+
f" API host: {api_base}\n"
|
|
129
|
+
f"\n"
|
|
130
|
+
f"Check status:\n"
|
|
131
|
+
f" evalmc status\n"
|
|
132
|
+
f" curl -s {api_base}/submissions/team/YOUR_TEAM | python3 -m json.tool\n"
|
|
133
|
+
f"\n"
|
|
134
|
+
f"If status becomes 'failed', the server could not run your local Docker tag.\n"
|
|
135
|
+
f"Use evalmc submit-onnx or evalmc submit (zip) instead."
|
|
136
|
+
)
|
evalmc/register.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
# Example: python register.py --api https://api.mc.myosuite.org --email foo@bar.com --team "Cool Team"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
ap = argparse.ArgumentParser(
|
|
10
|
+
description="Register a new team for the MyoChallenge competition"
|
|
11
|
+
)
|
|
12
|
+
ap.add_argument(
|
|
13
|
+
"--api",
|
|
14
|
+
required=True,
|
|
15
|
+
help="API endpoint URL (e.g., https://api.mc.myosuite.org)",
|
|
16
|
+
)
|
|
17
|
+
ap.add_argument("--email", required=True, help="Team contact email")
|
|
18
|
+
ap.add_argument("--team", required=True, help="Team name (3-100 characters)")
|
|
19
|
+
args = ap.parse_args()
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
r = requests.post(
|
|
23
|
+
f"{args.api}/register", json={"email": args.email, "team_name": args.team}
|
|
24
|
+
)
|
|
25
|
+
r.raise_for_status()
|
|
26
|
+
result = r.json()
|
|
27
|
+
|
|
28
|
+
print("\nRegistration successful! š")
|
|
29
|
+
print(f"Team Name: {result['team_name']}")
|
|
30
|
+
print(f"Email: {result['email']}")
|
|
31
|
+
print("\nā ļø IMPORTANT: Save your auth token, you'll need it to submit results!")
|
|
32
|
+
print(f"Auth Token: {result['auth_token']}\n")
|
|
33
|
+
|
|
34
|
+
except requests.exceptions.RequestException as e:
|
|
35
|
+
if hasattr(e, "response") and e.response is not None:
|
|
36
|
+
try:
|
|
37
|
+
error_detail = e.response.json().get("detail", str(e))
|
|
38
|
+
except ValueError:
|
|
39
|
+
error_detail = e.response.text or str(e)
|
|
40
|
+
print(f"\nā Registration failed: {error_detail}")
|
|
41
|
+
else:
|
|
42
|
+
print(f"\nā Registration failed: {str(e)}")
|
|
43
|
+
exit(1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
main()
|
evalmc/set_token.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MyoChallenge Set Token - Directly configure authentication token
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
# Configuration file paths
|
|
13
|
+
CONFIG_DIR = Path.home() / ".myochallenge"
|
|
14
|
+
AUTH_TOKEN_FILE = CONFIG_DIR / "auth_token.json"
|
|
15
|
+
|
|
16
|
+
# Ensure config directory exists
|
|
17
|
+
CONFIG_DIR.mkdir(exist_ok=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.command()
|
|
21
|
+
@click.argument("auth_token")
|
|
22
|
+
def set_token(auth_token):
|
|
23
|
+
"""
|
|
24
|
+
Configure MyoChallenge authentication token directly.
|
|
25
|
+
|
|
26
|
+
AUTH_TOKEN should be your team's authentication token (password).
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
# Store the token with minimal information
|
|
30
|
+
credentials = {
|
|
31
|
+
"auth_token": auth_token,
|
|
32
|
+
"email": "manual_set", # Placeholder for manual token
|
|
33
|
+
"team_name": "manual_set", # Placeholder for manual token
|
|
34
|
+
"user_id": None,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
with open(AUTH_TOKEN_FILE, "w") as f:
|
|
38
|
+
json.dump(credentials, f)
|
|
39
|
+
|
|
40
|
+
click.echo(
|
|
41
|
+
click.style(
|
|
42
|
+
"Success: Authentication token is successfully set.",
|
|
43
|
+
bold=True,
|
|
44
|
+
fg="green",
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
click.echo("š” Note: This token will be used for API requests")
|
|
48
|
+
click.echo("š” Use 'python status.py' to verify configuration")
|
|
49
|
+
|
|
50
|
+
except (OSError, IOError) as e:
|
|
51
|
+
click.echo(f"ā Error saving token: {e}")
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
set_token() # type: ignore
|