grid-cortex-client 0.3.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.
- grid_cortex_client-0.3.0/.gitignore +12 -0
- grid_cortex_client-0.3.0/CLAUDE.md +109 -0
- grid_cortex_client-0.3.0/PKG-INFO +143 -0
- grid_cortex_client-0.3.0/README.md +121 -0
- grid_cortex_client-0.3.0/pyproject.toml +56 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/__init__.py +30 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/client.py +262 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/cortex_client.py +661 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/cortex_hub_client.py +523 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/model_type.py +621 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/__init__.py +40 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/base_model.py +106 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/da3metric.py +144 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/fast_foundation_stereo.py +163 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/finetune_pi05.py +176 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/foundation_stereo.py +161 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/graspgen.py +206 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/gsam2.py +182 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/locate_anything.py +107 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/metric3d.py +95 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/moondream.py +131 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/oneformer.py +117 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/owlv2.py +167 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/pi05.py +288 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/qwen_vl.py +112 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/sam2.py +141 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/sam3.py +267 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/ur5e_pyroki_collision_batch.py +360 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/ur5e_pyroki_fk.py +122 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/models/zoedepth.py +145 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/preprocessing.py +113 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/tools/__init__.py +1 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/tools/generate_enum.py +219 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/tools/registry.py +104 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/utils.py +33 -0
- grid_cortex_client-0.3.0/src/grid_cortex_client/ws.py +74 -0
- grid_cortex_client-0.3.0/tests/README.md +73 -0
- grid_cortex_client-0.3.0/tests/__init__.py +1 -0
- grid_cortex_client-0.3.0/tests/conftest.py +275 -0
- grid_cortex_client-0.3.0/tests/test_cortex_hub_client.py +470 -0
- grid_cortex_client-0.3.0/tests/test_da3metric.py +119 -0
- grid_cortex_client-0.3.0/tests/test_fast_foundation_stereo.py +287 -0
- grid_cortex_client-0.3.0/tests/test_finetune_pi05.py +428 -0
- grid_cortex_client-0.3.0/tests/test_foundation_stereo.py +278 -0
- grid_cortex_client-0.3.0/tests/test_graspgen.py +378 -0
- grid_cortex_client-0.3.0/tests/test_gsam2.py +201 -0
- grid_cortex_client-0.3.0/tests/test_locate_anything.py +199 -0
- grid_cortex_client-0.3.0/tests/test_metric3d.py +128 -0
- grid_cortex_client-0.3.0/tests/test_moondream.py +238 -0
- grid_cortex_client-0.3.0/tests/test_oneformer.py +206 -0
- grid_cortex_client-0.3.0/tests/test_owlv2.py +234 -0
- grid_cortex_client-0.3.0/tests/test_pi05.py +212 -0
- grid_cortex_client-0.3.0/tests/test_qwen_vl.py +180 -0
- grid_cortex_client-0.3.0/tests/test_sam2.py +225 -0
- grid_cortex_client-0.3.0/tests/test_sam3.py +313 -0
- grid_cortex_client-0.3.0/tests/test_ur5e_pyroki_collision_batch.py +252 -0
- grid_cortex_client-0.3.0/tests/test_ur5e_pyroki_fk.py +267 -0
- grid_cortex_client-0.3.0/tests/test_zoedepth.py +125 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# grid-cortex-client
|
|
2
|
+
|
|
3
|
+
Python client for the Cortex ML inference API. Published to **public PyPI**.
|
|
4
|
+
|
|
5
|
+
## Build & Test
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv run --package grid-cortex-client pytest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- Build tool: hatchling + hatch-vcs
|
|
12
|
+
- Version: set via `BUILD_VERSION` env (auto-bumped on main from squash-commit type — see `knowledge/golden-rules/cortex/client-versioning.md`)
|
|
13
|
+
- PyPI history: pre-PR-#44 versions up to `0.2.118` were published by the old pipeline; the new auto-pipeline starts at `0.3.0` and uses `cortex-client/v*` git tags as the version source of truth (first auto-publish lands via PR #45's `feat(cortex):` merge once its review feedback is addressed)
|
|
14
|
+
|
|
15
|
+
## Key Details
|
|
16
|
+
|
|
17
|
+
- Public API: `CortexClient`, `AsyncCortexClient`, `CortexHubClient`, `ModelType`, `registry`
|
|
18
|
+
- `ModelType` enum is **auto-generated** by the `generate-enum` pre-commit hook — do not edit manually
|
|
19
|
+
- One model handler file per Cortex model in `src/grid_cortex_client/models/`
|
|
20
|
+
- Tests mirror model structure: `tests/test_<model>.py`. CI runs them against a live Ray Serve + CortexHub via `.ci/cortex/run_model_tests.py`.
|
|
21
|
+
|
|
22
|
+
## Endpoints
|
|
23
|
+
|
|
24
|
+
| Env | REST base | CortexHub WebSocket |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Local | `http://localhost:8000/cortex` | `ws://localhost:8000/cortex/ws/ws` |
|
|
27
|
+
| Stage | `https://cortex-stage.generalrobotics.dev/cortex` | `wss://cortex-stage.generalrobotics.dev/cortex/ws/ws` |
|
|
28
|
+
| Prod | `https://cortex-prod.generalrobotics.dev/cortex` | `wss://cortex-prod.generalrobotics.dev/cortex/ws/ws` |
|
|
29
|
+
|
|
30
|
+
Override via `GRID_CORTEX_BASE_URL` (REST) and `GRID_CORTEX_WS_URL` (WebSocket). Default is prod.
|
|
31
|
+
|
|
32
|
+
## Publishing
|
|
33
|
+
|
|
34
|
+
Client and server release on different cadences. Server deploys touch `cortex/services/ray-serve/**`; client publishes touch `cortex/packages/grid-cortex-client/**`. The two never piggyback — a server-only PR doesn't bump the client, so PyPI users only see versions when the client API actually changed.
|
|
35
|
+
|
|
36
|
+
### Auto path (MINOR / PATCH)
|
|
37
|
+
|
|
38
|
+
The squash-merge title's conventional-commit type drives the bump:
|
|
39
|
+
|
|
40
|
+
| Subject prefix | Bump | Example |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `feat:` (incl. `feat(scope):`) | MINOR | `feat(cortex-client): add ZoeDepth method` → `0.3.4 → 0.4.0` |
|
|
43
|
+
| `fix:` / `perf:` | PATCH | `fix(cortex-client): retry transient 503s` → `0.3.4 → 0.3.5` |
|
|
44
|
+
| `chore:` / `docs:` / `ci:` / `refactor:` / `test:` / `build:` / `style:` / `revert:` | none | no release fires |
|
|
45
|
+
| `feat!:` / `fix!:` / `BREAKING CHANGE:` footer | none (manual) | see Manual MAJOR path below |
|
|
46
|
+
|
|
47
|
+
Flow when a release-bumping client PR lands on main:
|
|
48
|
+
|
|
49
|
+
1. `cortex-main.yaml` fires (path filter matches `cortex/packages/grid-cortex-client/**`)
|
|
50
|
+
2. `resolve` parses the commit subject, computes the new version
|
|
51
|
+
3. `build-wheel` builds with `BUILD_VERSION=<new>`, uploads as an artifact
|
|
52
|
+
4. `validate-stage` + `validate-prod` install the wheel in fresh venvs, discover deployed models via `CortexClient.get_info()`, run the per-model integration tests against the live env
|
|
53
|
+
5. `publish-client` (gated on **validate-prod green only** — stage is advisory) uploads to PyPI using `PYPI_API_TOKEN`
|
|
54
|
+
6. `tag-client` creates and pushes `cortex-client/v<new>` after the wheel is on PyPI
|
|
55
|
+
|
|
56
|
+
### Manual MAJOR path
|
|
57
|
+
|
|
58
|
+
Auto-publish refuses to bump MAJOR. When you're ready:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gh release create cortex-client/v<X>.0.0 \
|
|
62
|
+
--target main \
|
|
63
|
+
--notes-file cortex/packages/grid-cortex-client/MIGRATION.md
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The `release: published` event fires `release.yaml` → routes to `release-cortex-client` → `cortex-publish-client.yaml` builds from the tag and uploads. **Validate against deployed prod manually before cutting the release** — the manual path does not gate.
|
|
67
|
+
|
|
68
|
+
### Implementation details
|
|
69
|
+
|
|
70
|
+
- `pyproject.toml` declares `dynamic = ["version"]`; hatchling reads `BUILD_VERSION` from the workflow env.
|
|
71
|
+
- Wheel is built once in `build-wheel`, then passed by artifact to `validate-{stage,prod}` and `publish-client` so all three use identical bits.
|
|
72
|
+
- Tag creation happens only after a confirmed successful PyPI upload — `cortex-client/v*` means "this version is on PyPI." Recoverable if the tag-push step fails: `git tag cortex-client/v<x> <sha> && git push origin cortex-client/v<x>`.
|
|
73
|
+
|
|
74
|
+
### Validate workflow shape (`.github/workflows/cortex-validate-client.yaml`)
|
|
75
|
+
|
|
76
|
+
One job per env (no matrix). The job:
|
|
77
|
+
|
|
78
|
+
1. Calls `CortexClient.get_info()` once via a Python heredoc that imports `CLIENT_HANDLER_OVERRIDES` + `SKIP_DEPLOYED` from `.ci/cortex/_client_overrides.py` (kept stdlib-only so the validate venv doesn't need pyyaml).
|
|
79
|
+
2. Python writes a bash-sourceable `/tmp/discover.sh` with `DEPLOYED`, `SKIP_SET`, `OVERRIDES` — we do not parse JSON in bash because `cortex-aws-gpu` has no `jq`.
|
|
80
|
+
3. Loops over `$DEPLOYED` sequentially. Per server name: SKIP entries short-circuit; missing test files become `NO-TEST` rows; remaining models run `pytest tests/test_<client>.py --tb=short`.
|
|
81
|
+
4. `::group::pytest output (<client>)` block surfaces the full pytest log on FAIL so on-call doesn't have to reach the runner's `/tmp`.
|
|
82
|
+
5. Exits 1 if discover python errors, if `DEPLOYED` is empty, or if `tested == 0` (the publish-gate invariant: validate must exercise at least one model before `publish-client` can run).
|
|
83
|
+
|
|
84
|
+
Replaces the earlier discover+matrix shape, which fired N env-reviewer prompts per env without delivering real parallelism (single shared runner).
|
|
85
|
+
|
|
86
|
+
### One-time repo setup
|
|
87
|
+
|
|
88
|
+
1. **GitHub environment** `prod` — already used by `cortex-deploy.yaml`. Required reviewers on this environment also gate `cortex-publish-client`.
|
|
89
|
+
2. **Repo secret `PYPI_API_TOKEN`** — project-scoped PyPI token for `grid-cortex-client`. Created on pypi.org → manage project → API tokens → create.
|
|
90
|
+
3. **Repo secret `CORTEX_VALIDATE_API_KEY`** — an API key the workflow uses to talk to deployed stage/prod for the validate step. Must work on both envs (or, if scoped, use the prod-capable one — validate-prod is the gating call).
|
|
91
|
+
|
|
92
|
+
### `MIN_SERVER_VERSION`
|
|
93
|
+
|
|
94
|
+
`MIN_SERVER_VERSION` is a constant exported from this package; the client checks `/health` on construction and refuses to talk to a server older than its declared minimum. Bumping it = MAJOR client release. *Not yet implemented — tracked as a follow-up to the publish wiring.*
|
|
95
|
+
|
|
96
|
+
## Consumed By
|
|
97
|
+
|
|
98
|
+
- `nexus/packages/zenoh-dataflow` (imports grid_cortex_client)
|
|
99
|
+
- `nexus/packages/grid-robot-api` (imports grid_cortex_client)
|
|
100
|
+
- `cortex/services/ray-serve` (the service this client talks to — code-shared via `ModelType`)
|
|
101
|
+
- External users via PyPI
|
|
102
|
+
|
|
103
|
+
## Adding a New Model Handler
|
|
104
|
+
|
|
105
|
+
1. Add handler in `src/grid_cortex_client/models/<model>.py` (copy `zoedepth.py`/`owlv2.py`/`pi05.py` as starting point)
|
|
106
|
+
2. Add test in `tests/test_<model>.py` (copy the matching test_*.py)
|
|
107
|
+
3. The `generate-enum` hook updates `ModelType` automatically
|
|
108
|
+
|
|
109
|
+
The `/add-new-model` skill does all of the above end-to-end. See `.claude/skills/add-new-model/SKILL.md`.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grid-cortex-client
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python client for Grid Cortex
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Requires-Dist: httpx>=0.28.1
|
|
14
|
+
Requires-Dist: msgpack-numpy>=0.4.0
|
|
15
|
+
Requires-Dist: msgpack>=1.0.0
|
|
16
|
+
Requires-Dist: numpy<2
|
|
17
|
+
Requires-Dist: pillow>=10.0.0
|
|
18
|
+
Requires-Dist: requests>=2.20.0
|
|
19
|
+
Requires-Dist: rerun-sdk==0.22.1
|
|
20
|
+
Requires-Dist: websockets>=12.0
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Grid Cortex Client
|
|
24
|
+
|
|
25
|
+
[](https://pypi.org/project/grid-cortex-client/)
|
|
26
|
+
[](https://pypi.org/project/grid-cortex-client/)
|
|
27
|
+
|
|
28
|
+
Python client for [GRID Cortex](https://cortex.generalrobotics.dev).
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install grid-cortex-client
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from grid_cortex_client import CortexClient, ModelType
|
|
40
|
+
|
|
41
|
+
client = CortexClient(api_key="your-api-key")
|
|
42
|
+
|
|
43
|
+
# Monocular depth estimation
|
|
44
|
+
depth_map = client.run(ModelType.ZOEDEPTH, image_input="path/to/image.jpg")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
Pass your API key and base URL directly, or set them as environment variables:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
export GRID_CORTEX_API_KEY="your-api-key"
|
|
53
|
+
export GRID_CORTEX_BASE_URL="https://cortex-prod.generalrobotics.dev/cortex"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# Explicit configuration
|
|
58
|
+
client = CortexClient(api_key="your-key", base_url="https://...")
|
|
59
|
+
|
|
60
|
+
# Or rely on environment variables
|
|
61
|
+
client = CortexClient()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Input Formats
|
|
65
|
+
|
|
66
|
+
All image-based models accept multiple input types:
|
|
67
|
+
|
|
68
|
+
- **File path:** `"path/to/image.jpg"`
|
|
69
|
+
- **URL:** `"https://example.com/image.jpg"`
|
|
70
|
+
- **PIL Image:** `Image.open("image.jpg")`
|
|
71
|
+
- **NumPy array:** `np.ndarray` with shape `(H, W, 3)`
|
|
72
|
+
|
|
73
|
+
## Async & Concurrent Inference
|
|
74
|
+
|
|
75
|
+
The async client lets you call multiple models concurrently so total latency equals the **slowest** model, not the sum of all of them.
|
|
76
|
+
|
|
77
|
+
### Concurrent multi-model example
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import asyncio
|
|
81
|
+
import numpy as np
|
|
82
|
+
from grid_cortex_client import AsyncCortexClient, ModelType
|
|
83
|
+
|
|
84
|
+
async def run_perception_pipeline(image: np.ndarray):
|
|
85
|
+
"""Run depth, detection, and segmentation concurrently on the same frame."""
|
|
86
|
+
async with AsyncCortexClient() as client:
|
|
87
|
+
depth, detections, mask = await asyncio.gather(
|
|
88
|
+
client.run(ModelType.ZOEDEPTH, image_input=image),
|
|
89
|
+
client.run(ModelType.OWLV2, image_input=image, prompt="bottle"),
|
|
90
|
+
client.run(ModelType.GSAM2, image_input=image, prompt="bottle"),
|
|
91
|
+
)
|
|
92
|
+
return depth, detections, mask
|
|
93
|
+
|
|
94
|
+
depth, detections, mask = asyncio.run(
|
|
95
|
+
run_perception_pipeline(np.array(Image.open("scene.jpg")))
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### High-throughput streaming with pub/sub
|
|
100
|
+
|
|
101
|
+
For continuous streams (e.g. camera feeds), the `CortexHubClient` uses WebSockets to overlap sending and receiving. While frame N's result is being returned, frame N+1 is already being processed server-side:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import asyncio
|
|
105
|
+
import numpy as np
|
|
106
|
+
from grid_cortex_client import CortexHubClient, ModelType
|
|
107
|
+
|
|
108
|
+
async def publisher(hub: CortexHubClient, frames: list[np.ndarray]):
|
|
109
|
+
"""Send frames as fast as possible."""
|
|
110
|
+
for i, frame in enumerate(frames):
|
|
111
|
+
await hub.publish(ModelType.ZOEDEPTH, request_id=f"frame_{i}", image_input=frame)
|
|
112
|
+
|
|
113
|
+
async def subscriber(hub: CortexHubClient, num_frames: int):
|
|
114
|
+
"""Receive results as they arrive."""
|
|
115
|
+
count = 0
|
|
116
|
+
async for result in hub.subscribe():
|
|
117
|
+
if result.ok:
|
|
118
|
+
print(f"{result.request_id}: shape={result.data.shape}")
|
|
119
|
+
count += 1
|
|
120
|
+
if count >= num_frames:
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
async def main():
|
|
124
|
+
frames = [np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)] * 100
|
|
125
|
+
|
|
126
|
+
async with CortexHubClient() as hub:
|
|
127
|
+
await asyncio.gather(
|
|
128
|
+
publisher(hub, frames),
|
|
129
|
+
subscriber(hub, len(frames)),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
asyncio.run(main())
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Documentation
|
|
136
|
+
|
|
137
|
+
For model-specific usage examples, parameter references, and detailed guides, see the full documentation:
|
|
138
|
+
|
|
139
|
+
**[docs.generalrobotics.dev/models/cortex](https://docs.generalrobotics.dev/models/cortex)**
|
|
140
|
+
|
|
141
|
+
## Requirements
|
|
142
|
+
|
|
143
|
+
- Python >= 3.8
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Grid Cortex Client
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/grid-cortex-client/)
|
|
4
|
+
[](https://pypi.org/project/grid-cortex-client/)
|
|
5
|
+
|
|
6
|
+
Python client for [GRID Cortex](https://cortex.generalrobotics.dev).
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install grid-cortex-client
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from grid_cortex_client import CortexClient, ModelType
|
|
18
|
+
|
|
19
|
+
client = CortexClient(api_key="your-api-key")
|
|
20
|
+
|
|
21
|
+
# Monocular depth estimation
|
|
22
|
+
depth_map = client.run(ModelType.ZOEDEPTH, image_input="path/to/image.jpg")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
Pass your API key and base URL directly, or set them as environment variables:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export GRID_CORTEX_API_KEY="your-api-key"
|
|
31
|
+
export GRID_CORTEX_BASE_URL="https://cortex-prod.generalrobotics.dev/cortex"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# Explicit configuration
|
|
36
|
+
client = CortexClient(api_key="your-key", base_url="https://...")
|
|
37
|
+
|
|
38
|
+
# Or rely on environment variables
|
|
39
|
+
client = CortexClient()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Input Formats
|
|
43
|
+
|
|
44
|
+
All image-based models accept multiple input types:
|
|
45
|
+
|
|
46
|
+
- **File path:** `"path/to/image.jpg"`
|
|
47
|
+
- **URL:** `"https://example.com/image.jpg"`
|
|
48
|
+
- **PIL Image:** `Image.open("image.jpg")`
|
|
49
|
+
- **NumPy array:** `np.ndarray` with shape `(H, W, 3)`
|
|
50
|
+
|
|
51
|
+
## Async & Concurrent Inference
|
|
52
|
+
|
|
53
|
+
The async client lets you call multiple models concurrently so total latency equals the **slowest** model, not the sum of all of them.
|
|
54
|
+
|
|
55
|
+
### Concurrent multi-model example
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import asyncio
|
|
59
|
+
import numpy as np
|
|
60
|
+
from grid_cortex_client import AsyncCortexClient, ModelType
|
|
61
|
+
|
|
62
|
+
async def run_perception_pipeline(image: np.ndarray):
|
|
63
|
+
"""Run depth, detection, and segmentation concurrently on the same frame."""
|
|
64
|
+
async with AsyncCortexClient() as client:
|
|
65
|
+
depth, detections, mask = await asyncio.gather(
|
|
66
|
+
client.run(ModelType.ZOEDEPTH, image_input=image),
|
|
67
|
+
client.run(ModelType.OWLV2, image_input=image, prompt="bottle"),
|
|
68
|
+
client.run(ModelType.GSAM2, image_input=image, prompt="bottle"),
|
|
69
|
+
)
|
|
70
|
+
return depth, detections, mask
|
|
71
|
+
|
|
72
|
+
depth, detections, mask = asyncio.run(
|
|
73
|
+
run_perception_pipeline(np.array(Image.open("scene.jpg")))
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### High-throughput streaming with pub/sub
|
|
78
|
+
|
|
79
|
+
For continuous streams (e.g. camera feeds), the `CortexHubClient` uses WebSockets to overlap sending and receiving. While frame N's result is being returned, frame N+1 is already being processed server-side:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import asyncio
|
|
83
|
+
import numpy as np
|
|
84
|
+
from grid_cortex_client import CortexHubClient, ModelType
|
|
85
|
+
|
|
86
|
+
async def publisher(hub: CortexHubClient, frames: list[np.ndarray]):
|
|
87
|
+
"""Send frames as fast as possible."""
|
|
88
|
+
for i, frame in enumerate(frames):
|
|
89
|
+
await hub.publish(ModelType.ZOEDEPTH, request_id=f"frame_{i}", image_input=frame)
|
|
90
|
+
|
|
91
|
+
async def subscriber(hub: CortexHubClient, num_frames: int):
|
|
92
|
+
"""Receive results as they arrive."""
|
|
93
|
+
count = 0
|
|
94
|
+
async for result in hub.subscribe():
|
|
95
|
+
if result.ok:
|
|
96
|
+
print(f"{result.request_id}: shape={result.data.shape}")
|
|
97
|
+
count += 1
|
|
98
|
+
if count >= num_frames:
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
async def main():
|
|
102
|
+
frames = [np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8)] * 100
|
|
103
|
+
|
|
104
|
+
async with CortexHubClient() as hub:
|
|
105
|
+
await asyncio.gather(
|
|
106
|
+
publisher(hub, frames),
|
|
107
|
+
subscriber(hub, len(frames)),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
asyncio.run(main())
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Documentation
|
|
114
|
+
|
|
115
|
+
For model-specific usage examples, parameter references, and detailed guides, see the full documentation:
|
|
116
|
+
|
|
117
|
+
**[docs.generalrobotics.dev/models/cortex](https://docs.generalrobotics.dev/models/cortex)**
|
|
118
|
+
|
|
119
|
+
## Requirements
|
|
120
|
+
|
|
121
|
+
- Python >= 3.8
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "grid-cortex-client"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Python client for Grid Cortex"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.8"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Programming Language :: Python :: 3",
|
|
9
|
+
"Programming Language :: Python :: 3.8",
|
|
10
|
+
"Programming Language :: Python :: 3.9",
|
|
11
|
+
"Programming Language :: Python :: 3.10",
|
|
12
|
+
"Programming Language :: Python :: 3.11",
|
|
13
|
+
"Programming Language :: Python :: 3.12",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
|
+
]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"httpx>=0.28.1",
|
|
18
|
+
"numpy<2",
|
|
19
|
+
"rerun-sdk==0.22.1",
|
|
20
|
+
"Pillow>=10.0.0",
|
|
21
|
+
"requests>=2.20.0",
|
|
22
|
+
"websockets>=12.0",
|
|
23
|
+
"msgpack>=1.0.0",
|
|
24
|
+
"msgpack-numpy>=0.4.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
29
|
+
build-backend = "hatchling.build"
|
|
30
|
+
|
|
31
|
+
[tool.uv]
|
|
32
|
+
required-version = ">=0.7.20"
|
|
33
|
+
exclude-newer = "7 days"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.version]
|
|
36
|
+
source = "env"
|
|
37
|
+
variable = "BUILD_VERSION"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.version.raw-options]
|
|
40
|
+
root = "../.."
|
|
41
|
+
local_scheme = "no-local-version"
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
python_files = ["test_*.py"]
|
|
46
|
+
python_classes = ["Test*"]
|
|
47
|
+
python_functions = ["test_*"]
|
|
48
|
+
markers = [
|
|
49
|
+
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
50
|
+
"integration: marks tests as integration tests",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[dependency-groups]
|
|
54
|
+
dev = [
|
|
55
|
+
"pytest>=8.3.5",
|
|
56
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Grid Cortex Python client.
|
|
2
|
+
|
|
3
|
+
This package provides :class:`CortexClient` for high-level model inference.
|
|
4
|
+
Use :class:`ModelType` enum to specify which model to run.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
# Public API first (ruff E402)
|
|
10
|
+
from .model_type import ModelType # re-export static enum
|
|
11
|
+
from .tools.registry import registry # shared model registry instance
|
|
12
|
+
|
|
13
|
+
if os.environ.get("GRID_CORTEX_SKIP_PUBLIC_API") != "1":
|
|
14
|
+
from .cortex_client import AsyncCortexClient, CortexClient
|
|
15
|
+
from .cortex_hub_client import CortexHubClient, CortexHubError, HubResult
|
|
16
|
+
|
|
17
|
+
# Configure the library logger to be silent by default **after** imports to satisfy E402.
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"AsyncCortexClient",
|
|
24
|
+
"CortexClient",
|
|
25
|
+
"CortexHubClient",
|
|
26
|
+
"CortexHubError",
|
|
27
|
+
"HubResult",
|
|
28
|
+
"ModelType",
|
|
29
|
+
"registry",
|
|
30
|
+
]
|