neurabridge 0.1.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.
- neurabridge-0.1.0/CHANGELOG.md +28 -0
- neurabridge-0.1.0/LICENSE +21 -0
- neurabridge-0.1.0/PKG-INFO +263 -0
- neurabridge-0.1.0/README.md +232 -0
- neurabridge-0.1.0/neurabridge/__init__.py +32 -0
- neurabridge-0.1.0/neurabridge/api/__init__.py +3 -0
- neurabridge-0.1.0/neurabridge/api/client.py +91 -0
- neurabridge-0.1.0/neurabridge/api/transport.py +103 -0
- neurabridge-0.1.0/neurabridge/errors/__init__.py +32 -0
- neurabridge-0.1.0/neurabridge/experiment/__init__.py +13 -0
- neurabridge-0.1.0/neurabridge/experiment/catalog.py +24 -0
- neurabridge-0.1.0/neurabridge/experiment/downloads.py +121 -0
- neurabridge-0.1.0/neurabridge/experiment/loaders.py +242 -0
- neurabridge-0.1.0/neurabridge/experiment/models.py +226 -0
- neurabridge-0.1.0/neurabridge/session/__init__.py +3 -0
- neurabridge-0.1.0/neurabridge/session/analysis.py +71 -0
- neurabridge-0.1.0/neurabridge/session/lifecycle.py +34 -0
- neurabridge-0.1.0/neurabridge/session/manifest.py +68 -0
- neurabridge-0.1.0/neurabridge/session/workspace.py +49 -0
- neurabridge-0.1.0/neurabridge/version.py +1 -0
- neurabridge-0.1.0/pyproject.toml +48 -0
- neurabridge-0.1.0/tests/test.ipynb +593 -0
- neurabridge-0.1.0/tests/test_client.py +65 -0
- neurabridge-0.1.0/tests/test_download.py +338 -0
- neurabridge-0.1.0/tests/test_loaders.py +153 -0
- neurabridge-0.1.0/tests/test_session.py +185 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2024-06-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial public release of the Neurabridge Python SDK
|
|
12
|
+
- Client authentication and API access
|
|
13
|
+
- Experiment discovery and run listing
|
|
14
|
+
- Run download and caching functionality
|
|
15
|
+
- Signal data loading as pandas DataFrames
|
|
16
|
+
- Image artifact loading with Pillow support
|
|
17
|
+
- Local analysis session management with manifest support
|
|
18
|
+
- Support for bulk downloading across multiple experiments
|
|
19
|
+
- Channel name metadata support in dataset loading
|
|
20
|
+
- Progress tracking for long-running operations
|
|
21
|
+
- Comprehensive test suite with pytest
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
- `Client` class for API interaction
|
|
25
|
+
- `Experiment`, `Run`, and `LocalRun` classes for data representation
|
|
26
|
+
- `ExperimentCollection` and `LocalRunCollection` for batch operations
|
|
27
|
+
- `AnalysisSession` for reproducible local analysis workflows
|
|
28
|
+
- Full test coverage for core functionality
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Neurabridge
|
|
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.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neurabridge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Neurabridge Python SDK for experiment access and analysis workflows
|
|
5
|
+
Project-URL: Homepage, https://neurabridge.io
|
|
6
|
+
Project-URL: Repository, https://github.com/neurabridge-io/neurabridge_sdk
|
|
7
|
+
Project-URL: Documentation, https://neurabridge.io
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/neurabridge-io/neurabridge_sdk/issues
|
|
9
|
+
Author: Neurabridge
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: brain-computer-interface,data-analysis,eeg,neuroscience,signal-processing
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: numpy>=1.26
|
|
25
|
+
Requires-Dist: pandas>=2.0
|
|
26
|
+
Requires-Dist: pillow>=10
|
|
27
|
+
Requires-Dist: tqdm>=4.66
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# neurabridge
|
|
33
|
+
|
|
34
|
+
Neurabridge.io is the software layer for turning brain signal data into usable models and APIs.
|
|
35
|
+
|
|
36
|
+
It brings hardware-integrated research workflows, analysis pipelines, and application development into one system so teams can move from signal collection to deployable intelligence faster.
|
|
37
|
+
|
|
38
|
+
`neurabridge` is the official Python SDK for accessing that workflow programmatically.
|
|
39
|
+
It provides a focused interface for discovering experiments, downloading run artifacts, decoding datasets, and organizing reproducible local analysis sessions.
|
|
40
|
+
|
|
41
|
+
Neurabridge is built for engineers, researchers, institutions, and hardware-adjacent product teams working with EEG and related brain signals. The platform is designed to support model development for studies, regressors, classifiers, and intent or activity segmentation systems, then expose those outputs through software interfaces that manufacturers and entrepreneurs can build on.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
For local development:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install -e .[dev]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## What The SDK Covers
|
|
56
|
+
|
|
57
|
+
- Authenticate against a Neurabridge API environment
|
|
58
|
+
- List experiments and runs visible to the current token
|
|
59
|
+
- Download a single run or all runs in a collection
|
|
60
|
+
- Load decoded signal datasets as pandas DataFrames
|
|
61
|
+
- Load associated image artifacts with Pillow
|
|
62
|
+
- Create portable local analysis sessions with a manifest
|
|
63
|
+
|
|
64
|
+
## Why It Exists
|
|
65
|
+
|
|
66
|
+
Brain-computer application development is fragmented across vendors, protocols, experimental setups, device integrations, raw data handling, one-off analysis scripts, and ad hoc model pipelines.
|
|
67
|
+
Neurabridge consolidates those steps into a software-first foundation that makes brain signal data easier to inspect, organize, model, and expose through downstream products.
|
|
68
|
+
|
|
69
|
+
For technical teams, that means less time spent stitching together infrastructure and more time spent building useful systems on top of brain data.
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import neurabridge as nb
|
|
75
|
+
|
|
76
|
+
client = nb.Client(
|
|
77
|
+
base_url="https://api.neurabridge.dev",
|
|
78
|
+
token="nbk_live_xxxxx",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
whoami = client.whoami()
|
|
82
|
+
experiments = client.experiments()
|
|
83
|
+
|
|
84
|
+
experiment = experiments[0]
|
|
85
|
+
run = client.runs(experiment.id)[0]
|
|
86
|
+
local_run = client.download(experiment.id, run.id)
|
|
87
|
+
|
|
88
|
+
dataset = local_run.dataset
|
|
89
|
+
metadata_named_dataset = local_run.load_dataset(use_metadata_channel_names=True)
|
|
90
|
+
images = local_run.images()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Standard Workflow
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import neurabridge as nb
|
|
97
|
+
from neurabridge import session
|
|
98
|
+
|
|
99
|
+
client = nb.Client(base_url="https://api.neurabridge.dev", token="nbk_live_xxxxx")
|
|
100
|
+
|
|
101
|
+
experiments = client.experiments()
|
|
102
|
+
selected_experiment = experiments[0]
|
|
103
|
+
selected_run = client.runs(selected_experiment.id)[0]
|
|
104
|
+
|
|
105
|
+
local_run = client.download(selected_experiment.id, selected_run.id, show_progress=False)
|
|
106
|
+
|
|
107
|
+
analysis_session = session.AnalysisSession.create(
|
|
108
|
+
name="example-session",
|
|
109
|
+
runs=[local_run],
|
|
110
|
+
base_dir="./analysis",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
signals = analysis_session.load_signals(
|
|
114
|
+
exp_id=local_run.experiment_id,
|
|
115
|
+
run_id=local_run.run_id,
|
|
116
|
+
)
|
|
117
|
+
images = analysis_session.load_images(
|
|
118
|
+
exp_id=local_run.experiment_id,
|
|
119
|
+
run_id=local_run.run_id,
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Working Across Runs
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
client = nb.Client(base_url="https://api.neurabridge.dev", token="nbk_live_xxxxx")
|
|
127
|
+
|
|
128
|
+
experiments = client.experiments()
|
|
129
|
+
all_runs = experiments.download_all(show_progress=False)
|
|
130
|
+
stacked_dataset = all_runs.dataset
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Platform Fit
|
|
134
|
+
|
|
135
|
+
Neurabridge does not build hardware devices today. It is intended to sit above hardware and unify the software layer around it: experiment data access, signal decoding, analysis workflows, model development, and API-ready outputs.
|
|
136
|
+
|
|
137
|
+
That makes it useful both for internal research teams and for external partners who want a faster path from hardware-generated brain data to applications.
|
|
138
|
+
|
|
139
|
+
## Public API
|
|
140
|
+
|
|
141
|
+
The root package exposes the main stable entry points:
|
|
142
|
+
|
|
143
|
+
- `neurabridge.Client`
|
|
144
|
+
- `neurabridge.AnalysisSession`
|
|
145
|
+
- `neurabridge.Experiment`
|
|
146
|
+
- `neurabridge.Run`
|
|
147
|
+
- `neurabridge.LocalRun`
|
|
148
|
+
- `neurabridge.ExperimentCollection`
|
|
149
|
+
- `neurabridge.LocalRunCollection`
|
|
150
|
+
|
|
151
|
+
Segmented namespaces are also available when you want to keep imports explicit:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from neurabridge import experiment, session
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Client
|
|
158
|
+
|
|
159
|
+
Main entry point for interacting with the Neurabridge API.
|
|
160
|
+
|
|
161
|
+
| Method | Parameters | Returns | Description |
|
|
162
|
+
|--------|-----------|---------|-------------|
|
|
163
|
+
| `whoami()` | — | `dict` | Get the authenticated user information |
|
|
164
|
+
| `experiments()` | — | `ExperimentCollection` | List all experiments accessible to the token |
|
|
165
|
+
| `runs(exp_id)` | `exp_id: str` | `list[Run]` | Get all runs for a specific experiment |
|
|
166
|
+
| `download(exp_id, run_id, show_progress)` | `exp_id: str`, `run_id: str`, `show_progress: bool = True` | `LocalRun` | Download a single run to local cache |
|
|
167
|
+
| `download_all(show_progress)` | `show_progress: bool = True` | `LocalRunCollection` | Download all runs from all experiments |
|
|
168
|
+
| `delete_run(exp_id, run_id)` | `exp_id: str`, `run_id: str` | `int` | Delete a specific run from the server |
|
|
169
|
+
| `delete_experiment(exp_id)` | `exp_id: str` | `int` | Delete an entire experiment from the server |
|
|
170
|
+
| `close()` | — | — | Close the client and clean up resources |
|
|
171
|
+
|
|
172
|
+
### Experiment
|
|
173
|
+
|
|
174
|
+
Metadata and stats about a remote experiment.
|
|
175
|
+
|
|
176
|
+
| Property | Type | Description |
|
|
177
|
+
|----------|------|-------------|
|
|
178
|
+
| `id` | `str` | Unique experiment identifier |
|
|
179
|
+
| `path` | `str` | Experiment path/location on server |
|
|
180
|
+
| `run_count` | `int` | Number of runs in this experiment |
|
|
181
|
+
| `updated_at` | `str \| None` | Last update timestamp |
|
|
182
|
+
|
|
183
|
+
### Run
|
|
184
|
+
|
|
185
|
+
Metadata about a remote run (before download).
|
|
186
|
+
|
|
187
|
+
| Property | Type | Description |
|
|
188
|
+
|----------|------|-------------|
|
|
189
|
+
| `id` | `str` | Unique run identifier |
|
|
190
|
+
| `experiment_id` | `str` | ID of parent experiment |
|
|
191
|
+
| `path` | `str` | Run path/location on server |
|
|
192
|
+
| `artifact_count` | `int` | Number of artifacts (signals, images, etc.) |
|
|
193
|
+
| `size_bytes` | `int` | Total size of run data in bytes |
|
|
194
|
+
| `updated_at` | `str \| None` | Last update timestamp |
|
|
195
|
+
|
|
196
|
+
### LocalRun
|
|
197
|
+
|
|
198
|
+
A downloaded run with local file access and data loading capabilities.
|
|
199
|
+
|
|
200
|
+
| Property/Method | Returns | Description |
|
|
201
|
+
|---------|---------|-------------|
|
|
202
|
+
| `id` | `str` | Alias for `run_id` |
|
|
203
|
+
| `run_id` | `str` | The run's unique identifier |
|
|
204
|
+
| `experiment_id` | `str` | Parent experiment ID |
|
|
205
|
+
| `path` | `Path` | Local filesystem path to downloaded run |
|
|
206
|
+
| `signal_files` | `list[Path]` | Paths to signal artifact files |
|
|
207
|
+
| `image_files` | `list[Path]` | Paths to image artifact files |
|
|
208
|
+
| `dataset` | `DataFrame` | Cached property: all signal data as pandas DataFrame |
|
|
209
|
+
| `metadata` | `dict` | Experiment metadata (channel names, sampling rates, etc.) |
|
|
210
|
+
| `image` | `Image` | First image artifact as PIL Image object |
|
|
211
|
+
| `images` | `list[tuple[dict, Image]]` | All image artifacts with metadata |
|
|
212
|
+
| `load_dataset(use_metadata_channel_names)` | `DataFrame` | Load signal data with optional metadata-based column names |
|
|
213
|
+
|
|
214
|
+
### ExperimentCollection
|
|
215
|
+
|
|
216
|
+
A list-like collection of `Experiment` objects with batch operations.
|
|
217
|
+
|
|
218
|
+
| Method | Parameters | Returns | Description |
|
|
219
|
+
|--------|-----------|---------|-------------|
|
|
220
|
+
| `download_all(show_progress)` | `show_progress: bool = True` | `LocalRunCollection` | Download all runs from all experiments in collection |
|
|
221
|
+
|
|
222
|
+
### LocalRunCollection
|
|
223
|
+
|
|
224
|
+
A list-like collection of `LocalRun` objects with aggregation operations.
|
|
225
|
+
|
|
226
|
+
| Property/Method | Returns | Description |
|
|
227
|
+
|---------|---------|-------------|
|
|
228
|
+
| `dataset` | `DataFrame` | Cached property: stacked signal data from all runs |
|
|
229
|
+
| `load_dataset(use_metadata_channel_names, force_reload)` | `DataFrame` | Load and concatenate all run datasets with optional cache control |
|
|
230
|
+
| `profile_dataset_load(use_metadata_channel_names, force_reload)` | `dict` | Load all datasets while measuring timing, row/column counts per run |
|
|
231
|
+
|
|
232
|
+
### AnalysisSession
|
|
233
|
+
|
|
234
|
+
Local writable workspace for reproducible analysis across downloaded runs.
|
|
235
|
+
|
|
236
|
+
| Method | Parameters | Returns | Description |
|
|
237
|
+
|--------|-----------|---------|-------------|
|
|
238
|
+
| `create(name, runs, base_dir)` | `name: str`, `runs: Iterable[LocalRun]`, `base_dir: str \| Path` | `AnalysisSession` | Create a new analysis session workspace with run data |
|
|
239
|
+
| `open(session_path)` | `session_path: str \| Path` | `AnalysisSession` | Open an existing session from disk |
|
|
240
|
+
| `list(base_dir)` | `base_dir: str \| Path` | `list[AnalysisSession]` | List all valid sessions under a directory |
|
|
241
|
+
| `load_signals(exp_id, run_id, use_metadata_channel_names)` | `exp_id: str`, `run_id: str`, `use_metadata_channel_names: bool = False` | `DataFrame` | Load signal data from session workspace |
|
|
242
|
+
| `load_images(exp_id, run_id)` | `exp_id: str`, `run_id: str` | `list[tuple[dict, Image]]` | Load image artifacts from session workspace |
|
|
243
|
+
| `delete()` | — | — | Delete the session directory from disk |
|
|
244
|
+
|
|
245
|
+
### Session Properties
|
|
246
|
+
|
|
247
|
+
| Property | Type | Description |
|
|
248
|
+
|----------|------|-------------|
|
|
249
|
+
| `name` | `str` | Session name |
|
|
250
|
+
| `path` | `Path` | Local filesystem path to session directory |
|
|
251
|
+
| `manifest` | `dict` | Session metadata manifest (JSON-serializable dict with run info) |
|
|
252
|
+
|
|
253
|
+
## Notes
|
|
254
|
+
|
|
255
|
+
- Signal data is returned as pandas DataFrames
|
|
256
|
+
- Image artifacts are returned as Pillow images
|
|
257
|
+
- Downloads are cached locally under the SDK cache directory
|
|
258
|
+
- Analysis sessions store a lightweight manifest so downloaded runs can be reopened consistently
|
|
259
|
+
- The SDK is intentionally read-oriented and centered on analysis and downstream application workflows
|
|
260
|
+
|
|
261
|
+
## Validation Notebook
|
|
262
|
+
|
|
263
|
+
The repository includes a smoke-test notebook that exercises the main SDK flow from authentication through dataset loading, manifest decoding, image rendering, and bulk download.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# neurabridge
|
|
2
|
+
|
|
3
|
+
Neurabridge.io is the software layer for turning brain signal data into usable models and APIs.
|
|
4
|
+
|
|
5
|
+
It brings hardware-integrated research workflows, analysis pipelines, and application development into one system so teams can move from signal collection to deployable intelligence faster.
|
|
6
|
+
|
|
7
|
+
`neurabridge` is the official Python SDK for accessing that workflow programmatically.
|
|
8
|
+
It provides a focused interface for discovering experiments, downloading run artifacts, decoding datasets, and organizing reproducible local analysis sessions.
|
|
9
|
+
|
|
10
|
+
Neurabridge is built for engineers, researchers, institutions, and hardware-adjacent product teams working with EEG and related brain signals. The platform is designed to support model development for studies, regressors, classifiers, and intent or activity segmentation systems, then expose those outputs through software interfaces that manufacturers and entrepreneurs can build on.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install .
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For local development:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install -e .[dev]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What The SDK Covers
|
|
25
|
+
|
|
26
|
+
- Authenticate against a Neurabridge API environment
|
|
27
|
+
- List experiments and runs visible to the current token
|
|
28
|
+
- Download a single run or all runs in a collection
|
|
29
|
+
- Load decoded signal datasets as pandas DataFrames
|
|
30
|
+
- Load associated image artifacts with Pillow
|
|
31
|
+
- Create portable local analysis sessions with a manifest
|
|
32
|
+
|
|
33
|
+
## Why It Exists
|
|
34
|
+
|
|
35
|
+
Brain-computer application development is fragmented across vendors, protocols, experimental setups, device integrations, raw data handling, one-off analysis scripts, and ad hoc model pipelines.
|
|
36
|
+
Neurabridge consolidates those steps into a software-first foundation that makes brain signal data easier to inspect, organize, model, and expose through downstream products.
|
|
37
|
+
|
|
38
|
+
For technical teams, that means less time spent stitching together infrastructure and more time spent building useful systems on top of brain data.
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
import neurabridge as nb
|
|
44
|
+
|
|
45
|
+
client = nb.Client(
|
|
46
|
+
base_url="https://api.neurabridge.dev",
|
|
47
|
+
token="nbk_live_xxxxx",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
whoami = client.whoami()
|
|
51
|
+
experiments = client.experiments()
|
|
52
|
+
|
|
53
|
+
experiment = experiments[0]
|
|
54
|
+
run = client.runs(experiment.id)[0]
|
|
55
|
+
local_run = client.download(experiment.id, run.id)
|
|
56
|
+
|
|
57
|
+
dataset = local_run.dataset
|
|
58
|
+
metadata_named_dataset = local_run.load_dataset(use_metadata_channel_names=True)
|
|
59
|
+
images = local_run.images()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Standard Workflow
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import neurabridge as nb
|
|
66
|
+
from neurabridge import session
|
|
67
|
+
|
|
68
|
+
client = nb.Client(base_url="https://api.neurabridge.dev", token="nbk_live_xxxxx")
|
|
69
|
+
|
|
70
|
+
experiments = client.experiments()
|
|
71
|
+
selected_experiment = experiments[0]
|
|
72
|
+
selected_run = client.runs(selected_experiment.id)[0]
|
|
73
|
+
|
|
74
|
+
local_run = client.download(selected_experiment.id, selected_run.id, show_progress=False)
|
|
75
|
+
|
|
76
|
+
analysis_session = session.AnalysisSession.create(
|
|
77
|
+
name="example-session",
|
|
78
|
+
runs=[local_run],
|
|
79
|
+
base_dir="./analysis",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
signals = analysis_session.load_signals(
|
|
83
|
+
exp_id=local_run.experiment_id,
|
|
84
|
+
run_id=local_run.run_id,
|
|
85
|
+
)
|
|
86
|
+
images = analysis_session.load_images(
|
|
87
|
+
exp_id=local_run.experiment_id,
|
|
88
|
+
run_id=local_run.run_id,
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Working Across Runs
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
client = nb.Client(base_url="https://api.neurabridge.dev", token="nbk_live_xxxxx")
|
|
96
|
+
|
|
97
|
+
experiments = client.experiments()
|
|
98
|
+
all_runs = experiments.download_all(show_progress=False)
|
|
99
|
+
stacked_dataset = all_runs.dataset
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Platform Fit
|
|
103
|
+
|
|
104
|
+
Neurabridge does not build hardware devices today. It is intended to sit above hardware and unify the software layer around it: experiment data access, signal decoding, analysis workflows, model development, and API-ready outputs.
|
|
105
|
+
|
|
106
|
+
That makes it useful both for internal research teams and for external partners who want a faster path from hardware-generated brain data to applications.
|
|
107
|
+
|
|
108
|
+
## Public API
|
|
109
|
+
|
|
110
|
+
The root package exposes the main stable entry points:
|
|
111
|
+
|
|
112
|
+
- `neurabridge.Client`
|
|
113
|
+
- `neurabridge.AnalysisSession`
|
|
114
|
+
- `neurabridge.Experiment`
|
|
115
|
+
- `neurabridge.Run`
|
|
116
|
+
- `neurabridge.LocalRun`
|
|
117
|
+
- `neurabridge.ExperimentCollection`
|
|
118
|
+
- `neurabridge.LocalRunCollection`
|
|
119
|
+
|
|
120
|
+
Segmented namespaces are also available when you want to keep imports explicit:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from neurabridge import experiment, session
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Client
|
|
127
|
+
|
|
128
|
+
Main entry point for interacting with the Neurabridge API.
|
|
129
|
+
|
|
130
|
+
| Method | Parameters | Returns | Description |
|
|
131
|
+
|--------|-----------|---------|-------------|
|
|
132
|
+
| `whoami()` | — | `dict` | Get the authenticated user information |
|
|
133
|
+
| `experiments()` | — | `ExperimentCollection` | List all experiments accessible to the token |
|
|
134
|
+
| `runs(exp_id)` | `exp_id: str` | `list[Run]` | Get all runs for a specific experiment |
|
|
135
|
+
| `download(exp_id, run_id, show_progress)` | `exp_id: str`, `run_id: str`, `show_progress: bool = True` | `LocalRun` | Download a single run to local cache |
|
|
136
|
+
| `download_all(show_progress)` | `show_progress: bool = True` | `LocalRunCollection` | Download all runs from all experiments |
|
|
137
|
+
| `delete_run(exp_id, run_id)` | `exp_id: str`, `run_id: str` | `int` | Delete a specific run from the server |
|
|
138
|
+
| `delete_experiment(exp_id)` | `exp_id: str` | `int` | Delete an entire experiment from the server |
|
|
139
|
+
| `close()` | — | — | Close the client and clean up resources |
|
|
140
|
+
|
|
141
|
+
### Experiment
|
|
142
|
+
|
|
143
|
+
Metadata and stats about a remote experiment.
|
|
144
|
+
|
|
145
|
+
| Property | Type | Description |
|
|
146
|
+
|----------|------|-------------|
|
|
147
|
+
| `id` | `str` | Unique experiment identifier |
|
|
148
|
+
| `path` | `str` | Experiment path/location on server |
|
|
149
|
+
| `run_count` | `int` | Number of runs in this experiment |
|
|
150
|
+
| `updated_at` | `str \| None` | Last update timestamp |
|
|
151
|
+
|
|
152
|
+
### Run
|
|
153
|
+
|
|
154
|
+
Metadata about a remote run (before download).
|
|
155
|
+
|
|
156
|
+
| Property | Type | Description |
|
|
157
|
+
|----------|------|-------------|
|
|
158
|
+
| `id` | `str` | Unique run identifier |
|
|
159
|
+
| `experiment_id` | `str` | ID of parent experiment |
|
|
160
|
+
| `path` | `str` | Run path/location on server |
|
|
161
|
+
| `artifact_count` | `int` | Number of artifacts (signals, images, etc.) |
|
|
162
|
+
| `size_bytes` | `int` | Total size of run data in bytes |
|
|
163
|
+
| `updated_at` | `str \| None` | Last update timestamp |
|
|
164
|
+
|
|
165
|
+
### LocalRun
|
|
166
|
+
|
|
167
|
+
A downloaded run with local file access and data loading capabilities.
|
|
168
|
+
|
|
169
|
+
| Property/Method | Returns | Description |
|
|
170
|
+
|---------|---------|-------------|
|
|
171
|
+
| `id` | `str` | Alias for `run_id` |
|
|
172
|
+
| `run_id` | `str` | The run's unique identifier |
|
|
173
|
+
| `experiment_id` | `str` | Parent experiment ID |
|
|
174
|
+
| `path` | `Path` | Local filesystem path to downloaded run |
|
|
175
|
+
| `signal_files` | `list[Path]` | Paths to signal artifact files |
|
|
176
|
+
| `image_files` | `list[Path]` | Paths to image artifact files |
|
|
177
|
+
| `dataset` | `DataFrame` | Cached property: all signal data as pandas DataFrame |
|
|
178
|
+
| `metadata` | `dict` | Experiment metadata (channel names, sampling rates, etc.) |
|
|
179
|
+
| `image` | `Image` | First image artifact as PIL Image object |
|
|
180
|
+
| `images` | `list[tuple[dict, Image]]` | All image artifacts with metadata |
|
|
181
|
+
| `load_dataset(use_metadata_channel_names)` | `DataFrame` | Load signal data with optional metadata-based column names |
|
|
182
|
+
|
|
183
|
+
### ExperimentCollection
|
|
184
|
+
|
|
185
|
+
A list-like collection of `Experiment` objects with batch operations.
|
|
186
|
+
|
|
187
|
+
| Method | Parameters | Returns | Description |
|
|
188
|
+
|--------|-----------|---------|-------------|
|
|
189
|
+
| `download_all(show_progress)` | `show_progress: bool = True` | `LocalRunCollection` | Download all runs from all experiments in collection |
|
|
190
|
+
|
|
191
|
+
### LocalRunCollection
|
|
192
|
+
|
|
193
|
+
A list-like collection of `LocalRun` objects with aggregation operations.
|
|
194
|
+
|
|
195
|
+
| Property/Method | Returns | Description |
|
|
196
|
+
|---------|---------|-------------|
|
|
197
|
+
| `dataset` | `DataFrame` | Cached property: stacked signal data from all runs |
|
|
198
|
+
| `load_dataset(use_metadata_channel_names, force_reload)` | `DataFrame` | Load and concatenate all run datasets with optional cache control |
|
|
199
|
+
| `profile_dataset_load(use_metadata_channel_names, force_reload)` | `dict` | Load all datasets while measuring timing, row/column counts per run |
|
|
200
|
+
|
|
201
|
+
### AnalysisSession
|
|
202
|
+
|
|
203
|
+
Local writable workspace for reproducible analysis across downloaded runs.
|
|
204
|
+
|
|
205
|
+
| Method | Parameters | Returns | Description |
|
|
206
|
+
|--------|-----------|---------|-------------|
|
|
207
|
+
| `create(name, runs, base_dir)` | `name: str`, `runs: Iterable[LocalRun]`, `base_dir: str \| Path` | `AnalysisSession` | Create a new analysis session workspace with run data |
|
|
208
|
+
| `open(session_path)` | `session_path: str \| Path` | `AnalysisSession` | Open an existing session from disk |
|
|
209
|
+
| `list(base_dir)` | `base_dir: str \| Path` | `list[AnalysisSession]` | List all valid sessions under a directory |
|
|
210
|
+
| `load_signals(exp_id, run_id, use_metadata_channel_names)` | `exp_id: str`, `run_id: str`, `use_metadata_channel_names: bool = False` | `DataFrame` | Load signal data from session workspace |
|
|
211
|
+
| `load_images(exp_id, run_id)` | `exp_id: str`, `run_id: str` | `list[tuple[dict, Image]]` | Load image artifacts from session workspace |
|
|
212
|
+
| `delete()` | — | — | Delete the session directory from disk |
|
|
213
|
+
|
|
214
|
+
### Session Properties
|
|
215
|
+
|
|
216
|
+
| Property | Type | Description |
|
|
217
|
+
|----------|------|-------------|
|
|
218
|
+
| `name` | `str` | Session name |
|
|
219
|
+
| `path` | `Path` | Local filesystem path to session directory |
|
|
220
|
+
| `manifest` | `dict` | Session metadata manifest (JSON-serializable dict with run info) |
|
|
221
|
+
|
|
222
|
+
## Notes
|
|
223
|
+
|
|
224
|
+
- Signal data is returned as pandas DataFrames
|
|
225
|
+
- Image artifacts are returned as Pillow images
|
|
226
|
+
- Downloads are cached locally under the SDK cache directory
|
|
227
|
+
- Analysis sessions store a lightweight manifest so downloaded runs can be reopened consistently
|
|
228
|
+
- The SDK is intentionally read-oriented and centered on analysis and downstream application workflows
|
|
229
|
+
|
|
230
|
+
## Validation Notebook
|
|
231
|
+
|
|
232
|
+
The repository includes a smoke-test notebook that exercises the main SDK flow from authentication through dataset loading, manifest decoding, image rendering, and bulk download.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from . import experiment, session
|
|
2
|
+
from .api import Client
|
|
3
|
+
from .experiment import Experiment, ExperimentCollection, LocalRun, LocalRunCollection, Run
|
|
4
|
+
from .session import AnalysisSession
|
|
5
|
+
from .version import __version__
|
|
6
|
+
from .errors import (
|
|
7
|
+
AuthError,
|
|
8
|
+
DownloadError,
|
|
9
|
+
NeurabridgeError,
|
|
10
|
+
NotFoundError,
|
|
11
|
+
PermissionError,
|
|
12
|
+
SessionError,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"__version__",
|
|
17
|
+
"experiment",
|
|
18
|
+
"session",
|
|
19
|
+
"Client",
|
|
20
|
+
"AnalysisSession",
|
|
21
|
+
"Experiment",
|
|
22
|
+
"ExperimentCollection",
|
|
23
|
+
"Run",
|
|
24
|
+
"LocalRun",
|
|
25
|
+
"LocalRunCollection",
|
|
26
|
+
"NeurabridgeError",
|
|
27
|
+
"AuthError",
|
|
28
|
+
"PermissionError",
|
|
29
|
+
"NotFoundError",
|
|
30
|
+
"DownloadError",
|
|
31
|
+
"SessionError",
|
|
32
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from ..experiment.catalog import delete_experiment, delete_run, list_experiments, list_runs
|
|
8
|
+
from ..experiment.downloads import download_run
|
|
9
|
+
from ..experiment.models import ExperimentCollection, LocalRun, LocalRunCollection, Run
|
|
10
|
+
from .transport import ApiTransport
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Client:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
*,
|
|
17
|
+
token: str | None = None,
|
|
18
|
+
base_url: str | None = None,
|
|
19
|
+
cache_dir: str | Path | None = None,
|
|
20
|
+
timeout: float = 30.0,
|
|
21
|
+
max_retries: int = 2,
|
|
22
|
+
retry_backoff_seconds: float = 0.3,
|
|
23
|
+
transport: httpx.BaseTransport | None = None,
|
|
24
|
+
download_transport: httpx.BaseTransport | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
self._transport = ApiTransport(
|
|
27
|
+
token=token,
|
|
28
|
+
base_url=base_url,
|
|
29
|
+
cache_dir=cache_dir,
|
|
30
|
+
timeout=timeout,
|
|
31
|
+
max_retries=max_retries,
|
|
32
|
+
retry_backoff_seconds=retry_backoff_seconds,
|
|
33
|
+
transport=transport,
|
|
34
|
+
download_transport=download_transport,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def base_url(self) -> str:
|
|
39
|
+
return self._transport.base_url
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def token(self) -> str:
|
|
43
|
+
return self._transport.token
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def timeout(self) -> float:
|
|
47
|
+
return self._transport.timeout
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def max_retries(self) -> int:
|
|
51
|
+
return self._transport.max_retries
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def retry_backoff_seconds(self) -> float:
|
|
55
|
+
return self._transport.retry_backoff_seconds
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def cache_dir(self) -> Path:
|
|
59
|
+
return self._transport.cache_dir
|
|
60
|
+
|
|
61
|
+
def close(self) -> None:
|
|
62
|
+
self._transport.close()
|
|
63
|
+
|
|
64
|
+
def __enter__(self) -> "Client":
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def __exit__(self, exc_type: object, exc: object, tb: object) -> None:
|
|
68
|
+
self.close()
|
|
69
|
+
|
|
70
|
+
def whoami(self) -> dict[str, object]:
|
|
71
|
+
data = self._transport.request_json("GET", "/v1/whoami")
|
|
72
|
+
user = data.get("user", data)
|
|
73
|
+
return user if isinstance(user, dict) else {"user": user}
|
|
74
|
+
|
|
75
|
+
def experiments(self) -> ExperimentCollection:
|
|
76
|
+
return list_experiments(self)
|
|
77
|
+
|
|
78
|
+
def runs(self, exp_id: str) -> list[Run]:
|
|
79
|
+
return list_runs(self, exp_id)
|
|
80
|
+
|
|
81
|
+
def download(self, exp_id: str, run_id: str, *, show_progress: bool = True) -> LocalRun:
|
|
82
|
+
return download_run(self, exp_id=exp_id, run_id=run_id, show_progress=show_progress)
|
|
83
|
+
|
|
84
|
+
def download_all(self, *, show_progress: bool = True) -> LocalRunCollection:
|
|
85
|
+
return self.experiments().download_all(show_progress=show_progress)
|
|
86
|
+
|
|
87
|
+
def delete_run(self, exp_id: str, run_id: str) -> int:
|
|
88
|
+
return delete_run(self, exp_id, run_id)
|
|
89
|
+
|
|
90
|
+
def delete_experiment(self, exp_id: str) -> int:
|
|
91
|
+
return delete_experiment(self, exp_id)
|