neuracore 1.0.0__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.
- neuracore-1.0.0/LICENSE +21 -0
- neuracore-1.0.0/PKG-INFO +96 -0
- neuracore-1.0.0/README.md +74 -0
- neuracore-1.0.0/neuracore/__init__.py +3 -0
- neuracore-1.0.0/neuracore/auth.py +125 -0
- neuracore-1.0.0/neuracore/const.py +3 -0
- neuracore-1.0.0/neuracore/core.py +250 -0
- neuracore-1.0.0/neuracore/dataset.py +46 -0
- neuracore-1.0.0/neuracore/endpoint.py +196 -0
- neuracore-1.0.0/neuracore/exceptions.py +229 -0
- neuracore-1.0.0/neuracore/generate_api_key.py +44 -0
- neuracore-1.0.0/neuracore/robot.py +195 -0
- neuracore-1.0.0/neuracore/streaming.py +453 -0
- neuracore-1.0.0/neuracore.egg-info/PKG-INFO +96 -0
- neuracore-1.0.0/neuracore.egg-info/SOURCES.txt +19 -0
- neuracore-1.0.0/neuracore.egg-info/dependency_links.txt +1 -0
- neuracore-1.0.0/neuracore.egg-info/entry_points.txt +2 -0
- neuracore-1.0.0/neuracore.egg-info/requires.txt +26 -0
- neuracore-1.0.0/neuracore.egg-info/top_level.txt +1 -0
- neuracore-1.0.0/setup.cfg +4 -0
- neuracore-1.0.0/setup.py +70 -0
neuracore-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Neuraco
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
neuracore-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: neuracore
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Neuracore Client Library
|
|
5
|
+
Home-page: https://github.com/neuraco/neuracore
|
|
6
|
+
Author: Stephen James
|
|
7
|
+
Author-email: stephen@neuraco.com
|
|
8
|
+
Keywords: robotics machine-learning ai client-library
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Provides-Extra: examples
|
|
19
|
+
Provides-Extra: local_endpoint
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
|
|
23
|
+
# Neuracore Python Client
|
|
24
|
+
|
|
25
|
+
Neuracore is a powerful robotics and machine learning client library for seamless robot data collection, model deployment, and interaction.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- Easy robot initialization and connection
|
|
30
|
+
- Streaming data logging
|
|
31
|
+
- Model endpoint management
|
|
32
|
+
- Local and remote model support
|
|
33
|
+
- Flexible dataset creation
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install neuracore
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
Ensure you have an account at (https://www.neuracore.app/)[https://www.neuracore.app/]
|
|
44
|
+
|
|
45
|
+
### Authentication
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import neuracore as nc
|
|
49
|
+
|
|
50
|
+
# This will save your API key locally
|
|
51
|
+
nc.login()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Robot Connection
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# Connect to a robot
|
|
58
|
+
nc.connect_robot(
|
|
59
|
+
robot_name="MyRobot",
|
|
60
|
+
urdf_path="/path/to/robot.urdf"
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Data Logging
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# Log joint positions
|
|
68
|
+
nc.log_joints({
|
|
69
|
+
'joint1': 0.5,
|
|
70
|
+
'joint2': -0.3
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
# Log RGB camera image
|
|
74
|
+
nc.log_rgb("top_camera", image_array)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Development
|
|
78
|
+
|
|
79
|
+
To set up for development:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/neuraco/neuracore
|
|
83
|
+
cd neuracore
|
|
84
|
+
pip install -e .[dev]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Testing
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
export NEURACORE_API_URL=http://localhost:8000/api
|
|
91
|
+
pytest tests/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Contributing
|
|
95
|
+
|
|
96
|
+
Contributions are welcome!
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Neuracore Python Client
|
|
2
|
+
|
|
3
|
+
Neuracore is a powerful robotics and machine learning client library for seamless robot data collection, model deployment, and interaction.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Easy robot initialization and connection
|
|
8
|
+
- Streaming data logging
|
|
9
|
+
- Model endpoint management
|
|
10
|
+
- Local and remote model support
|
|
11
|
+
- Flexible dataset creation
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install neuracore
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
Ensure you have an account at (https://www.neuracore.app/)[https://www.neuracore.app/]
|
|
22
|
+
|
|
23
|
+
### Authentication
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import neuracore as nc
|
|
27
|
+
|
|
28
|
+
# This will save your API key locally
|
|
29
|
+
nc.login()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Robot Connection
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# Connect to a robot
|
|
36
|
+
nc.connect_robot(
|
|
37
|
+
robot_name="MyRobot",
|
|
38
|
+
urdf_path="/path/to/robot.urdf"
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Data Logging
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# Log joint positions
|
|
46
|
+
nc.log_joints({
|
|
47
|
+
'joint1': 0.5,
|
|
48
|
+
'joint2': -0.3
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
# Log RGB camera image
|
|
52
|
+
nc.log_rgb("top_camera", image_array)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Development
|
|
56
|
+
|
|
57
|
+
To set up for development:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/neuraco/neuracore
|
|
61
|
+
cd neuracore
|
|
62
|
+
pip install -e .[dev]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Testing
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
export NEURACORE_API_URL=http://localhost:8000/api
|
|
69
|
+
pytest tests/
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Contributing
|
|
73
|
+
|
|
74
|
+
Contributions are welcome!
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from neuracore.const import API_URL
|
|
8
|
+
|
|
9
|
+
from .exceptions import AuthenticationError
|
|
10
|
+
from .generate_api_key import generate_api_key
|
|
11
|
+
|
|
12
|
+
CONFIG_DIR = Path.home() / ".neuracore"
|
|
13
|
+
CONFIG_FILE = "config.json"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Auth:
|
|
17
|
+
_instance = None
|
|
18
|
+
_api_key: str | None = None
|
|
19
|
+
|
|
20
|
+
def __new__(cls):
|
|
21
|
+
if cls._instance is None:
|
|
22
|
+
cls._instance = super().__new__(cls)
|
|
23
|
+
return cls._instance
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self._load_config()
|
|
27
|
+
self._access_token = None
|
|
28
|
+
|
|
29
|
+
def _load_config(self) -> None:
|
|
30
|
+
"""Load configuration from disk if it exists."""
|
|
31
|
+
config_file = CONFIG_DIR / CONFIG_FILE
|
|
32
|
+
if config_file.exists():
|
|
33
|
+
with open(config_file) as f:
|
|
34
|
+
config = json.load(f)
|
|
35
|
+
self._api_key = config.get("api_key")
|
|
36
|
+
|
|
37
|
+
def _save_config(self) -> None:
|
|
38
|
+
"""Save current configuration to disk."""
|
|
39
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
config_file = CONFIG_DIR / CONFIG_FILE
|
|
41
|
+
with open(config_file, "w") as f:
|
|
42
|
+
json.dump({"api_key": self._api_key}, f)
|
|
43
|
+
|
|
44
|
+
def login(self, api_key: str | None = None) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Authenticate with the NeuraCore server using an API key.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
api_key: Optional API key. If not provided, will try to use environment
|
|
50
|
+
variable NEURACORE_API_KEY or previously saved config.
|
|
51
|
+
"""
|
|
52
|
+
self._api_key = api_key or os.environ.get("NEURACORE_API_KEY") or self._api_key
|
|
53
|
+
|
|
54
|
+
if not self._api_key:
|
|
55
|
+
print("No API key provided. Attempting to log you in...")
|
|
56
|
+
self._api_key = generate_api_key()
|
|
57
|
+
|
|
58
|
+
# Verify API key with server and get access token
|
|
59
|
+
try:
|
|
60
|
+
response = requests.post(
|
|
61
|
+
f"{API_URL}/auth/verify-api-key",
|
|
62
|
+
json={"api_key": self._api_key},
|
|
63
|
+
)
|
|
64
|
+
if response.status_code != 200:
|
|
65
|
+
raise AuthenticationError(
|
|
66
|
+
f"Failed to authenticate: {response.json().get('detail')}"
|
|
67
|
+
)
|
|
68
|
+
token_data = response.json()
|
|
69
|
+
self._access_token = token_data["access_token"]
|
|
70
|
+
except requests.exceptions.RequestException as e:
|
|
71
|
+
raise AuthenticationError(f"Failed to authenticate: {str(e)}")
|
|
72
|
+
|
|
73
|
+
# Save configuration if verification successful
|
|
74
|
+
self._save_config()
|
|
75
|
+
|
|
76
|
+
def logout(self) -> None:
|
|
77
|
+
"""Clear authentication state."""
|
|
78
|
+
self._api_key = None
|
|
79
|
+
self._access_token = None
|
|
80
|
+
config_file = CONFIG_DIR / CONFIG_FILE
|
|
81
|
+
if config_file.exists():
|
|
82
|
+
config_file.unlink()
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def api_key(self) -> str | None:
|
|
86
|
+
"""Get the current API key."""
|
|
87
|
+
return self._api_key
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def access_token(self) -> str | None:
|
|
91
|
+
"""Get the current access token."""
|
|
92
|
+
return self._access_token
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def is_authenticated(self) -> bool:
|
|
96
|
+
"""Check if currently authenticated."""
|
|
97
|
+
return self._api_key is not None and self._access_token is not None
|
|
98
|
+
|
|
99
|
+
def get_headers(self) -> dict:
|
|
100
|
+
"""Get headers for authenticated requests."""
|
|
101
|
+
if not self.is_authenticated:
|
|
102
|
+
raise AuthenticationError("Not authenticated. Please call login() first.")
|
|
103
|
+
return {
|
|
104
|
+
"Authorization": f"Bearer {self._access_token}",
|
|
105
|
+
# "Content-Type": "application/json",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Global instance
|
|
110
|
+
_auth = Auth()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def login(api_key: str | None = None) -> None:
|
|
114
|
+
"""Global login function."""
|
|
115
|
+
_auth.login(api_key)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def logout() -> None:
|
|
119
|
+
"""Global logout function."""
|
|
120
|
+
_auth.logout()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_auth() -> Auth:
|
|
124
|
+
"""Get the global Auth instance."""
|
|
125
|
+
return _auth
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from .auth import login as _login
|
|
6
|
+
from .auth import logout as _logout
|
|
7
|
+
from .dataset import Dataset
|
|
8
|
+
from .endpoint import EndpointPolicy
|
|
9
|
+
from .endpoint import connect_endpoint as _connect_endpoint
|
|
10
|
+
from .endpoint import connect_local_endpoint as _connect_local_endpoint
|
|
11
|
+
from .exceptions import RobotError
|
|
12
|
+
from .robot import Robot, get_robot
|
|
13
|
+
from .robot import init as _init_robot
|
|
14
|
+
from .streaming import log_action as _log_action
|
|
15
|
+
from .streaming import log_depth as _log_depth
|
|
16
|
+
from .streaming import log_joints as _log_joints
|
|
17
|
+
from .streaming import log_rgb as _log_rgb
|
|
18
|
+
from .streaming import stop_all_streams as _stop_all_streams
|
|
19
|
+
from .streaming import stop_streaming as _stop_streaming
|
|
20
|
+
from .streaming import wait_until_stream_empty
|
|
21
|
+
|
|
22
|
+
# Global active robot ID - allows us to avoid passing robot_name to every call
|
|
23
|
+
_active_robot: Robot | None = None
|
|
24
|
+
_active_dataset_id: str | None = None
|
|
25
|
+
_active_recording_id: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def login(api_key: str | None = None) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Authenticate with NeuraCore server.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
api_key: Optional API key. If not provided, will look for NEURACORE_API_KEY
|
|
34
|
+
environment variable or previously saved configuration.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
AuthenticationError: If authentication fails
|
|
38
|
+
"""
|
|
39
|
+
_login(api_key)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def logout() -> None:
|
|
43
|
+
"""Clear authentication state."""
|
|
44
|
+
_logout()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def connect_robot(
|
|
48
|
+
robot_name: str, urdf_path: str | None = None, overwrite: bool = False
|
|
49
|
+
) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Initialize a robot connection.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
robot_name: Unique identifier for the robot
|
|
55
|
+
urdf_path: Optional path to robot's URDF file
|
|
56
|
+
"""
|
|
57
|
+
global _active_robot
|
|
58
|
+
_active_robot = _init_robot(robot_name, urdf_path, overwrite)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _get_robot(robot_name: str) -> Robot:
|
|
62
|
+
"""Get a robot by name."""
|
|
63
|
+
robot: Robot = _active_robot
|
|
64
|
+
if robot_name is None:
|
|
65
|
+
if _active_robot is None:
|
|
66
|
+
raise RobotError(
|
|
67
|
+
"No active robot. Call init() first or provide robot_name."
|
|
68
|
+
)
|
|
69
|
+
else:
|
|
70
|
+
robot = get_robot(robot_name)
|
|
71
|
+
return robot
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def log_joints(positions: dict[str, float], robot_name: str | None = None) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Log joint positions for a robot.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
positions: Dictionary mapping joint names to positions (in radians)
|
|
80
|
+
robot_name: Optional robot ID. If not provided, uses the last initialized robot
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
RobotError: If no robot is active and no robot_name provided
|
|
84
|
+
StreamingError: If logging fails
|
|
85
|
+
"""
|
|
86
|
+
_log_joints(_get_robot(robot_name), positions)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def log_action(action: dict[str, float], robot_name: str | None = None) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Log action for a robot.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
action: Dictionary mapping joint names to positions (in radians)
|
|
95
|
+
robot_name: Optional robot ID. If not provided, uses the last initialized robot
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
RobotError: If no robot is active and no robot_name provided
|
|
99
|
+
StreamingError: If logging fails
|
|
100
|
+
"""
|
|
101
|
+
_log_action(_get_robot(robot_name), action)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def log_rgb(camera_id: str, image: np.ndarray, robot_name: str | None = None) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Log RGB image from a camera.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
camera_id: Unique identifier for the camera
|
|
110
|
+
image: RGB image as numpy array (HxWx3, dtype=uint8 or float32)
|
|
111
|
+
robot_name: Optional robot ID. If not provided, uses the last initialized robot
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
RobotError: If no robot is active and no robot_name provided
|
|
115
|
+
StreamingError: If logging fails
|
|
116
|
+
ValueError: If image format is invalid
|
|
117
|
+
"""
|
|
118
|
+
_log_rgb(_get_robot(robot_name), camera_id, image)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def log_depth(camera_id: str, depth: np.ndarray, robot_name: str | None = None) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Log depth image from a camera.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
camera_id: Unique identifier for the camera
|
|
127
|
+
depth: Depth image as numpy array (HxW, dtype=float32, in meters)
|
|
128
|
+
robot_name: Optional robot ID. If not provided, uses the last initialized robot
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
RobotError: If no robot is active and no robot_name provided
|
|
132
|
+
StreamingError: If logging fails
|
|
133
|
+
ValueError: If depth format is invalid
|
|
134
|
+
"""
|
|
135
|
+
_log_depth(_get_robot(robot_name), camera_id, depth)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def start_recording(robot_name: str | None = None) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Start recording data for a specific robot.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
robot_name: Optional robot ID. If not provided, uses the last initialized robot
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
RobotError: If no robot is active and no robot_name provided
|
|
147
|
+
"""
|
|
148
|
+
global _active_recording_id
|
|
149
|
+
if _active_recording_id is not None:
|
|
150
|
+
raise RobotError("Recording already in progress. Call stop_recording() first.")
|
|
151
|
+
robot = _get_robot(robot_name)
|
|
152
|
+
if _active_dataset_id is None:
|
|
153
|
+
raise RobotError("No active dataset. Call create_dataset() first.")
|
|
154
|
+
_active_recording_id = robot.start_recording(_active_dataset_id)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def stop_recording(robot_name: str | None = None) -> None:
|
|
158
|
+
"""
|
|
159
|
+
Stop recording data for a specific robot.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
robot_name: Optional robot ID. If not provided, uses the last initialized robot
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
RobotError: If no robot is active and no robot_name provided
|
|
166
|
+
"""
|
|
167
|
+
global _active_recording_id
|
|
168
|
+
robot = _get_robot(robot_name)
|
|
169
|
+
if _active_recording_id is None:
|
|
170
|
+
raise RobotError("No active recording. Call start_recording() first.")
|
|
171
|
+
wait_until_stream_empty(robot)
|
|
172
|
+
robot.stop_recording(_active_recording_id)
|
|
173
|
+
_active_recording_id = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def create_dataset(
|
|
177
|
+
name: str, description: str | None = None, tags: list[str] | None = None
|
|
178
|
+
) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Create a new dataset for robot demonstrations.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
name: Dataset name
|
|
184
|
+
description: Optional description
|
|
185
|
+
tags: Optional list of tags
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
DatasetError: If dataset creation fails
|
|
189
|
+
"""
|
|
190
|
+
global _active_dataset_id
|
|
191
|
+
_active_dataset = Dataset(name, description, tags)
|
|
192
|
+
_active_dataset_id = _active_dataset.id
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def connect_endpoint(name: str) -> EndpointPolicy:
|
|
196
|
+
"""
|
|
197
|
+
Connect to a deployed model endpoint.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
name: Name of the deployed endpoint
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
EndpointPolicy: Policy object that can be used for predictions
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
EndpointError: If endpoint connection fails
|
|
207
|
+
"""
|
|
208
|
+
return _connect_endpoint(name)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def connect_local_endpoint(path_to_model: str) -> EndpointPolicy:
|
|
212
|
+
"""
|
|
213
|
+
Connect to a local model endpoint.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
path_to_model: Path to the local model endpoint
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
EndpointPolicy: Policy object that can be used for predictions
|
|
220
|
+
|
|
221
|
+
Raises:
|
|
222
|
+
EndpointError: If endpoint connection fails
|
|
223
|
+
"""
|
|
224
|
+
return _connect_local_endpoint(path_to_model)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def stop(robot_name: str | None = None) -> None:
|
|
228
|
+
"""
|
|
229
|
+
Stop streaming for a specific robot.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
robot_name: Optional robot ID. If not provided, uses the last initialized robot
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
RobotError: If no robot is active and no robot_name provided
|
|
236
|
+
"""
|
|
237
|
+
global _active_robot
|
|
238
|
+
_stop_streaming(_get_robot(robot_name))
|
|
239
|
+
if robot_name == _active_robot.name:
|
|
240
|
+
_active_robot = None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def stop_all() -> None:
|
|
244
|
+
"""Stop all active data streams."""
|
|
245
|
+
global _active_robot
|
|
246
|
+
_stop_all_streams()
|
|
247
|
+
_active_robot = None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
atexit.register(stop_all)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from neuracore.const import API_URL
|
|
6
|
+
|
|
7
|
+
from .auth import Auth, get_auth
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Dataset:
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self, name: str, description: str | None = None, tags: list[str] | None = None
|
|
16
|
+
):
|
|
17
|
+
datasets = self._get_datasets()
|
|
18
|
+
for dataset in datasets:
|
|
19
|
+
if dataset["name"] == name:
|
|
20
|
+
self.id = dataset["id"]
|
|
21
|
+
logger.info(f"Dataset '{name}' already exist.")
|
|
22
|
+
return
|
|
23
|
+
id = self._create_dataset(name, description, tags)
|
|
24
|
+
self.id = id
|
|
25
|
+
|
|
26
|
+
def _create_dataset(
|
|
27
|
+
self, name: str, description: str | None = None, tags: list[str] | None = None
|
|
28
|
+
):
|
|
29
|
+
auth: Auth = get_auth()
|
|
30
|
+
response = requests.post(
|
|
31
|
+
f"{API_URL}/api/datasets",
|
|
32
|
+
headers=auth.get_headers(),
|
|
33
|
+
json={
|
|
34
|
+
"name": name,
|
|
35
|
+
"description": description,
|
|
36
|
+
},
|
|
37
|
+
)
|
|
38
|
+
response.raise_for_status()
|
|
39
|
+
dataset_json = response.json()
|
|
40
|
+
return dataset_json["id"]
|
|
41
|
+
|
|
42
|
+
def _get_datasets(self):
|
|
43
|
+
auth: Auth = get_auth()
|
|
44
|
+
response = requests.get(f"{API_URL}/api/datasets", headers=auth.get_headers())
|
|
45
|
+
response.raise_for_status()
|
|
46
|
+
return response.json()
|