grid-cortex-client 0.4.0__tar.gz → 0.5.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.4.0 → grid_cortex_client-0.5.0}/CLAUDE.md +1 -1
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/PKG-INFO +1 -1
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/model_type.py +38 -0
- grid_cortex_client-0.5.0/src/grid_cortex_client/models/contact_graspnet.py +155 -0
- grid_cortex_client-0.5.0/src/grid_cortex_client/models/metric3d.py +129 -0
- grid_cortex_client-0.5.0/tests/test_contact_graspnet.py +282 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_metric3d.py +20 -0
- grid_cortex_client-0.4.0/src/grid_cortex_client/models/metric3d.py +0 -95
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/.gitignore +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/README.md +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/pyproject.toml +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/__init__.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/client.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/cortex_client.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/cortex_hub_client.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/__init__.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/base_model.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/da3metric.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/fast_foundation_stereo.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/finetune_pi05.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/foundation_stereo.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/graspgen.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/graspgenx.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/gsam2.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/locate_anything.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/moondream.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/oneformer.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/owlv2.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/pi05.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/qwen_vl.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/sam2.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/sam3.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/ur5e_pyroki_collision_batch.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/ur5e_pyroki_fk.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/zoedepth.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/preprocessing.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/tools/__init__.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/tools/generate_enum.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/tools/registry.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/utils.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/ws.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/README.md +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/__init__.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/conftest.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_cortex_hub_client.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_da3metric.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_fast_foundation_stereo.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_finetune_pi05.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_foundation_stereo.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_graspgen.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_graspgenx.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_gsam2.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_locate_anything.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_moondream.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_oneformer.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_owlv2.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_pi05.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_qwen_vl.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_sam2.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_sam3.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_ur5e_pyroki_collision_batch.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_ur5e_pyroki_fk.py +0 -0
- {grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_zoedepth.py +0 -0
|
@@ -10,7 +10,7 @@ uv run --package grid-cortex-client pytest
|
|
|
10
10
|
|
|
11
11
|
- Build tool: hatchling + hatch-vcs
|
|
12
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
|
|
13
|
+
- PyPI history: pre-PR-#44 versions up to `0.2.118` were published by the old pipeline; the auto-pipeline (live since PR #45) starts at `0.3.0` and uses `cortex-client/v*` git tags as the version source of truth
|
|
14
14
|
|
|
15
15
|
## Key Details
|
|
16
16
|
|
|
@@ -13,6 +13,44 @@ from enum import Enum
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class ModelType(Enum):
|
|
16
|
+
CONTACT_GRASPNET = "contact-graspnet"
|
|
17
|
+
"""
|
|
18
|
+
Generate 6-DoF grasp poses from a depth image + camera intrinsics
|
|
19
|
+
(+ optional segmentation mask) using Contact-GraspNet (PyTorch port).
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
depth_image (Union[str, Image.Image, np.ndarray]): Depth image (path/URL/PIL/ndarray; float32 H×W).
|
|
23
|
+
camera_intrinsics (Union[str, np.ndarray]): 3×3 intrinsics matrix.
|
|
24
|
+
seg_image (Union[str, Image.Image, np.ndarray, None]): Optional segmentation mask (uint8 H×W).
|
|
25
|
+
When supplied + aux_args["filter_grasps"] = True, grasps are restricted to the masked region.
|
|
26
|
+
aux_args (Dict[str, Any]): Optional dict. Keys:
|
|
27
|
+
- "top_k" (int, default 64): cap on returned grasps after score-sort.
|
|
28
|
+
- "z_range" ([float, float], default [0.2, 1.8]): z-axis crop in metres.
|
|
29
|
+
- "local_regions" (bool, default False): crop 3D regions around segments.
|
|
30
|
+
- "filter_grasps" (bool, default False): restrict grasps to segmap; requires seg_image.
|
|
31
|
+
- "forward_passes" (int, default 1): how many forward passes to ensemble.
|
|
32
|
+
timeout (float | None): Optional HTTP timeout.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dict with:
|
|
36
|
+
- grasps: Array of 4×4 grasp poses in camera frame, shape (N, 4, 4), N <= top_k.
|
|
37
|
+
- confidence: Array of confidence scores in [0, 1], shape (N,).
|
|
38
|
+
- latency_ms: Optional server-reported latency in milliseconds.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If depth or intrinsics cannot be loaded.
|
|
42
|
+
RuntimeError: If no HTTP transport is configured.
|
|
43
|
+
Exception: If the HTTP request fails.
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
>>> from grid_cortex_client import CortexClient, ModelType
|
|
47
|
+
>>> import numpy as np
|
|
48
|
+
>>> client = CortexClient()
|
|
49
|
+
>>> depth = np.load("depth.npy")
|
|
50
|
+
>>> K = np.array([[615,0,320],[0,615,240],[0,0,1]], dtype=np.float64)
|
|
51
|
+
>>> out = client.run(ModelType.CONTACT_GRASPNET, depth_image=depth, camera_intrinsics=K, aux_args={"top_k": 32})
|
|
52
|
+
>>> print(out["grasps"].shape) # (<=32, 4, 4)
|
|
53
|
+
"""
|
|
16
54
|
DA3METRIC = "da3metric"
|
|
17
55
|
"""
|
|
18
56
|
Estimate metric depth from single RGB image.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# grid_cortex_client/src/grid_cortex_client/models/contact_graspnet.py
|
|
2
|
+
"""ContactGraspNet wrapper.
|
|
3
|
+
|
|
4
|
+
6-DoF grasp pose generation from a depth image + camera intrinsics, with an
|
|
5
|
+
optional segmentation mask to restrict the working region.
|
|
6
|
+
|
|
7
|
+
Successor to the legacy `graspnet` (TF) model; runs on the PyTorch port via
|
|
8
|
+
the cortex `contact_graspnet` Ray Serve deployment. Wire format mirrors
|
|
9
|
+
`graspgen` so swapping models in client code is just changing the ModelType.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Any, Dict, Union
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
from PIL import Image
|
|
18
|
+
|
|
19
|
+
from ..preprocessing import array_to_npy_bytes, load_image
|
|
20
|
+
from .base_model import BaseModel
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ContactGraspNet(BaseModel):
|
|
24
|
+
"""6-DoF grasp generation from depth + intrinsics (+ optional seg).
|
|
25
|
+
|
|
26
|
+
Preferred usage
|
|
27
|
+
---------------
|
|
28
|
+
```pycon
|
|
29
|
+
>>> grasps, conf = CortexClient().run(
|
|
30
|
+
... "contact_graspnet",
|
|
31
|
+
... depth_image=depth, camera_intrinsics=K,
|
|
32
|
+
... seg_image=seg, aux_args={"top_k": 64}
|
|
33
|
+
... )
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
name: str = "contact-graspnet"
|
|
38
|
+
model_id: str = "contact-graspnet"
|
|
39
|
+
|
|
40
|
+
def preprocess(
|
|
41
|
+
self,
|
|
42
|
+
*,
|
|
43
|
+
depth_image: Union[str, Image.Image, np.ndarray, None] = None,
|
|
44
|
+
camera_intrinsics: Union[str, np.ndarray, None] = None,
|
|
45
|
+
seg_image: Union[str, Image.Image, np.ndarray, None] = None,
|
|
46
|
+
aux_args: Dict[str, Any] | None = None,
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
"""Encode depth + intrinsics (+ optional seg) for the server.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
depth_image: Depth image (path/URL/PIL/ndarray; float32 H×W).
|
|
52
|
+
camera_intrinsics: 3×3 intrinsics matrix (path/ndarray).
|
|
53
|
+
seg_image: Optional segmentation mask (path/PIL/ndarray; uint8 H×W).
|
|
54
|
+
When supplied + ``aux_args["filter_grasps"] = True``, grasps are
|
|
55
|
+
restricted to the masked region (see model README for details).
|
|
56
|
+
aux_args: Optional dict with ``top_k`` (default 64), ``z_range``
|
|
57
|
+
(default [0.2, 1.8]), ``local_regions``, ``filter_grasps``,
|
|
58
|
+
``forward_passes``.
|
|
59
|
+
"""
|
|
60
|
+
if depth_image is None or camera_intrinsics is None:
|
|
61
|
+
raise ValueError("depth_image and camera_intrinsics are required.")
|
|
62
|
+
|
|
63
|
+
# Accept .npy paths in addition to image formats. PIL.Image.open chokes
|
|
64
|
+
# on raw NumPy files; np.load is the right call for them. .png/.exr
|
|
65
|
+
# depth maps still flow through load_image as before.
|
|
66
|
+
if isinstance(depth_image, str):
|
|
67
|
+
if depth_image.endswith(".npy"):
|
|
68
|
+
depth_array = np.load(depth_image)
|
|
69
|
+
else:
|
|
70
|
+
depth_array = np.array(load_image(depth_image))
|
|
71
|
+
elif isinstance(depth_image, Image.Image):
|
|
72
|
+
depth_array = np.array(load_image(depth_image))
|
|
73
|
+
else:
|
|
74
|
+
depth_array = np.asarray(depth_image)
|
|
75
|
+
|
|
76
|
+
if isinstance(camera_intrinsics, str):
|
|
77
|
+
intrinsics = np.load(camera_intrinsics)
|
|
78
|
+
else:
|
|
79
|
+
intrinsics = np.asarray(camera_intrinsics)
|
|
80
|
+
|
|
81
|
+
payload: Dict[str, Any] = {
|
|
82
|
+
"depth_image": array_to_npy_bytes(depth_array.astype(np.float32)),
|
|
83
|
+
"camera_intrinsics": array_to_npy_bytes(intrinsics.astype(np.float64)),
|
|
84
|
+
"aux_args": aux_args or {},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if seg_image is not None:
|
|
88
|
+
if isinstance(seg_image, (str, Image.Image)):
|
|
89
|
+
seg_array = np.array(load_image(seg_image))
|
|
90
|
+
else:
|
|
91
|
+
seg_array = np.asarray(seg_image)
|
|
92
|
+
payload["seg_image"] = array_to_npy_bytes(seg_array.astype(np.uint8))
|
|
93
|
+
|
|
94
|
+
return payload
|
|
95
|
+
|
|
96
|
+
def postprocess(self, response_data: Dict[str, Any], **_: Any) -> Dict[str, Any]:
|
|
97
|
+
"""Decode grasps and confidence from response."""
|
|
98
|
+
raw_grasps = response_data["output"]
|
|
99
|
+
# Keep the (N, 4, 4) shape invariant when no grasps were found —
|
|
100
|
+
# np.array([]) → (0,) which breaks any caller indexing grasps[:, :3, 3].
|
|
101
|
+
if not raw_grasps:
|
|
102
|
+
grasps = np.zeros((0, 4, 4), dtype=np.float32)
|
|
103
|
+
else:
|
|
104
|
+
grasps = np.array(raw_grasps)
|
|
105
|
+
conf = np.array(response_data["confidence"])
|
|
106
|
+
return {
|
|
107
|
+
"grasps": grasps,
|
|
108
|
+
"confidence": conf,
|
|
109
|
+
"latency_ms": response_data.get("latency_ms"),
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
def run(
|
|
113
|
+
self,
|
|
114
|
+
*,
|
|
115
|
+
depth_image: Union[str, Image.Image, np.ndarray, None] = None,
|
|
116
|
+
camera_intrinsics: Union[str, np.ndarray, None] = None,
|
|
117
|
+
seg_image: Union[str, Image.Image, np.ndarray, None] = None,
|
|
118
|
+
aux_args: Dict[str, Any] | None = None,
|
|
119
|
+
timeout: float | None = None,
|
|
120
|
+
) -> Dict[str, Any]:
|
|
121
|
+
"""Generate grasps using ContactGraspNet.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
depth_image: Depth image (required).
|
|
125
|
+
camera_intrinsics: 3×3 intrinsics matrix (required).
|
|
126
|
+
seg_image: Optional segmentation mask (uint8 H×W).
|
|
127
|
+
aux_args: Optional dict. Keys: ``top_k`` (int, default 64),
|
|
128
|
+
``z_range`` ([float, float], default [0.2, 1.8]),
|
|
129
|
+
``local_regions`` (bool, default False),
|
|
130
|
+
``filter_grasps`` (bool, default False; requires ``seg_image``),
|
|
131
|
+
``forward_passes`` (int, default 1).
|
|
132
|
+
timeout: Optional HTTP timeout.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dict with:
|
|
136
|
+
- ``grasps``: (N, 4, 4) array of grasp poses in camera frame
|
|
137
|
+
- ``confidence``: (N,) array of confidence scores in [0, 1]
|
|
138
|
+
- ``latency_ms``: server-reported latency
|
|
139
|
+
|
|
140
|
+
Examples:
|
|
141
|
+
>>> from grid_cortex_client import CortexClient, ModelType
|
|
142
|
+
>>> import numpy as np
|
|
143
|
+
>>> client = CortexClient()
|
|
144
|
+
>>> depth = np.load("depth.npy")
|
|
145
|
+
>>> K = np.array([[615,0,320],[0,615,240],[0,0,1]], dtype=np.float64)
|
|
146
|
+
>>> out = client.run(ModelType.CONTACT_GRASPNET, depth_image=depth, camera_intrinsics=K, aux_args={"top_k": 32})
|
|
147
|
+
>>> print(out["grasps"].shape) # (<=32, 4, 4)
|
|
148
|
+
"""
|
|
149
|
+
return super().run(
|
|
150
|
+
depth_image=depth_image,
|
|
151
|
+
camera_intrinsics=camera_intrinsics,
|
|
152
|
+
seg_image=seg_image,
|
|
153
|
+
aux_args=aux_args,
|
|
154
|
+
timeout=timeout,
|
|
155
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Metric3D wrapper.
|
|
2
|
+
|
|
3
|
+
Client-side helper for monocular metric depth estimation and surface normal
|
|
4
|
+
prediction powered by Metric3D ViT-Large.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from io import BytesIO
|
|
10
|
+
from typing import Any, Dict, Optional, Sequence, Union
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from PIL import Image
|
|
14
|
+
|
|
15
|
+
from ..preprocessing import load_image, image_to_bytes
|
|
16
|
+
from .base_model import BaseModel
|
|
17
|
+
|
|
18
|
+
VALID_OUTPUTS = ("depth", "normals")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Metric3D(BaseModel):
|
|
22
|
+
"""Monocular metric depth + surface normals (Metric3D ViT-Large).
|
|
23
|
+
|
|
24
|
+
Preferred usage (through :class:`CortexClient`)
|
|
25
|
+
------------------------------------------------
|
|
26
|
+
```pycon
|
|
27
|
+
>>> result = CortexClient().run("metric3d", image_input=img)
|
|
28
|
+
>>> result["depth"].shape # (H, W) float32 metres
|
|
29
|
+
>>> result["normals"].shape # (H, W, 3) float32 unit normals
|
|
30
|
+
|
|
31
|
+
>>> # Request a single map to cut response size (~75% smaller for depth-only)
|
|
32
|
+
>>> result = CortexClient().run("metric3d", image_input=img, outputs=["depth"])
|
|
33
|
+
>>> "normals" in result
|
|
34
|
+
False
|
|
35
|
+
```
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
name: str = "metric3d"
|
|
39
|
+
model_id: str = "metric3d"
|
|
40
|
+
|
|
41
|
+
def __init__(self, *, transport=None) -> None:
|
|
42
|
+
super().__init__(transport=transport)
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
# BaseModel interface
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def preprocess(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
image_input: Union[str, Image.Image, np.ndarray],
|
|
52
|
+
outputs: Optional[Union[str, Sequence[str]]] = None,
|
|
53
|
+
) -> Dict[str, Any]:
|
|
54
|
+
"""Return msgpack-ready payload with raw image bytes under key ``image_input``.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
image_input: RGB image as file path, URL, PIL Image, or numpy array.
|
|
58
|
+
outputs: Optional selection of maps to return — ``"depth"``,
|
|
59
|
+
``"normals"``, or a non-empty subset of both. ``None`` (default)
|
|
60
|
+
returns both.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: If ``outputs`` is empty or contains an unknown name.
|
|
64
|
+
"""
|
|
65
|
+
pil_img = load_image(image_input)
|
|
66
|
+
payload: Dict[str, Any] = {"image_input": image_to_bytes(pil_img, encoding_format="JPEG")}
|
|
67
|
+
|
|
68
|
+
if outputs is not None:
|
|
69
|
+
selected = [outputs] if isinstance(outputs, str) else [str(o) for o in outputs]
|
|
70
|
+
if not selected or any(o not in VALID_OUTPUTS for o in selected):
|
|
71
|
+
raise ValueError(f"'outputs' must be a non-empty subset of {VALID_OUTPUTS}, got {outputs!r}")
|
|
72
|
+
payload["outputs"] = selected
|
|
73
|
+
|
|
74
|
+
return payload
|
|
75
|
+
|
|
76
|
+
def postprocess(
|
|
77
|
+
self,
|
|
78
|
+
response_data: Dict[str, Any],
|
|
79
|
+
**_: Any,
|
|
80
|
+
) -> Dict[str, np.ndarray]:
|
|
81
|
+
"""Decode the maps present in the server response.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dict with the returned subset of ``"depth"`` (H, W) float32 and
|
|
85
|
+
``"normals"`` (H, W, 3) float32.
|
|
86
|
+
"""
|
|
87
|
+
if isinstance(response_data, list):
|
|
88
|
+
response_data = response_data[0]
|
|
89
|
+
|
|
90
|
+
result: Dict[str, np.ndarray] = {}
|
|
91
|
+
for key in VALID_OUTPUTS:
|
|
92
|
+
buf = response_data.get(key)
|
|
93
|
+
if buf is None:
|
|
94
|
+
continue
|
|
95
|
+
if not isinstance(buf, (bytes, bytearray, memoryview)):
|
|
96
|
+
raise ValueError(f"'{key}' field is not bytes")
|
|
97
|
+
result[key] = np.load(BytesIO(buf), allow_pickle=False).astype(np.float32)
|
|
98
|
+
|
|
99
|
+
if not result:
|
|
100
|
+
raise ValueError(f"response contains none of {VALID_OUTPUTS}")
|
|
101
|
+
return result
|
|
102
|
+
|
|
103
|
+
def run(
|
|
104
|
+
self,
|
|
105
|
+
image_input: Union[str, Image.Image, np.ndarray],
|
|
106
|
+
timeout: float | None = None,
|
|
107
|
+
*,
|
|
108
|
+
outputs: Optional[Union[str, Sequence[str]]] = None,
|
|
109
|
+
) -> Dict[str, np.ndarray]:
|
|
110
|
+
"""Estimate metric depth and/or surface normals from a single RGB image.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
image_input: RGB image as file path, URL, PIL Image, or numpy array.
|
|
114
|
+
timeout: Optional timeout in seconds for the HTTP request.
|
|
115
|
+
outputs: Keyword-only. Optional selection of maps to return —
|
|
116
|
+
``"depth"``, ``"normals"``, or a non-empty subset of both.
|
|
117
|
+
``None`` (default) returns both. Requesting only what you need
|
|
118
|
+
shrinks the response payload (normals are 3x the size of depth).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Dict with the requested subset of:
|
|
122
|
+
- ``"depth"``: (H, W) float32 array, metric depth in metres.
|
|
123
|
+
- ``"normals"``: (H, W, 3) float32 array, unit surface normals.
|
|
124
|
+
"""
|
|
125
|
+
return super().run(
|
|
126
|
+
image_input=image_input,
|
|
127
|
+
outputs=outputs,
|
|
128
|
+
timeout=timeout,
|
|
129
|
+
)
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""Test ContactGraspNet model (6-DoF grasp generation, PyTorch port)."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from grid_cortex_client import AsyncCortexClient, CortexClient, CortexHubClient, ModelType
|
|
10
|
+
from grid_cortex_client.client import CortexAPIError
|
|
11
|
+
|
|
12
|
+
from .conftest import BaseModelTestHTTP, is_gpu_mem_close, run_concurrent_requests
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestContactGraspNetHTTP(BaseModelTestHTTP):
|
|
16
|
+
"""HTTP-based tests for ContactGraspNet.
|
|
17
|
+
|
|
18
|
+
Reuses the depth/seg/intrinsics fixtures from the graspgen suite — both
|
|
19
|
+
grasping models take the same input shape, so a single eval set covers
|
|
20
|
+
both. ContactGraspNet has no point_cloud direct-input path (the model
|
|
21
|
+
extracts the cloud itself from depth + K with z-range filtering).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def batch_size(self) -> int:
|
|
26
|
+
return 1
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def gpu_mem(self) -> float:
|
|
30
|
+
return 3200.0
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def max_input_size(self) -> tuple:
|
|
34
|
+
return (720, 1280)
|
|
35
|
+
|
|
36
|
+
@pytest.fixture(scope="class")
|
|
37
|
+
def depth_image(self, depth_image_np: np.ndarray) -> np.ndarray:
|
|
38
|
+
return depth_image_np
|
|
39
|
+
|
|
40
|
+
@pytest.fixture(scope="class")
|
|
41
|
+
def seg_image(self, seg_image_np: np.ndarray) -> np.ndarray:
|
|
42
|
+
return seg_image_np
|
|
43
|
+
|
|
44
|
+
@pytest.fixture(scope="class")
|
|
45
|
+
def camera_intrinsics(self, camera_intrinsics_np: np.ndarray) -> np.ndarray:
|
|
46
|
+
return camera_intrinsics_np
|
|
47
|
+
|
|
48
|
+
@pytest.fixture(scope="class")
|
|
49
|
+
def aux_args(self) -> dict:
|
|
50
|
+
return {
|
|
51
|
+
"top_k": 32,
|
|
52
|
+
"z_range": [0.2, 1.8],
|
|
53
|
+
"local_regions": False,
|
|
54
|
+
"filter_grasps": False,
|
|
55
|
+
"forward_passes": 1,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def test_basic_inference(
|
|
59
|
+
self,
|
|
60
|
+
client: CortexClient,
|
|
61
|
+
depth_image: np.ndarray,
|
|
62
|
+
camera_intrinsics: np.ndarray,
|
|
63
|
+
aux_args: dict,
|
|
64
|
+
):
|
|
65
|
+
result = client.run(
|
|
66
|
+
ModelType.CONTACT_GRASPNET,
|
|
67
|
+
depth_image=depth_image,
|
|
68
|
+
camera_intrinsics=camera_intrinsics,
|
|
69
|
+
aux_args=aux_args,
|
|
70
|
+
)
|
|
71
|
+
assert result is not None
|
|
72
|
+
assert "grasps" in result
|
|
73
|
+
assert "confidence" in result
|
|
74
|
+
assert isinstance(result["grasps"], np.ndarray)
|
|
75
|
+
assert isinstance(result["confidence"], np.ndarray)
|
|
76
|
+
|
|
77
|
+
def test_with_segmentation(
|
|
78
|
+
self,
|
|
79
|
+
client: CortexClient,
|
|
80
|
+
depth_image: np.ndarray,
|
|
81
|
+
seg_image: np.ndarray,
|
|
82
|
+
camera_intrinsics: np.ndarray,
|
|
83
|
+
):
|
|
84
|
+
aux = {"top_k": 16, "filter_grasps": True, "local_regions": True}
|
|
85
|
+
result = client.run(
|
|
86
|
+
ModelType.CONTACT_GRASPNET,
|
|
87
|
+
depth_image=depth_image,
|
|
88
|
+
seg_image=seg_image,
|
|
89
|
+
camera_intrinsics=camera_intrinsics,
|
|
90
|
+
aux_args=aux,
|
|
91
|
+
)
|
|
92
|
+
assert result is not None
|
|
93
|
+
assert "grasps" in result
|
|
94
|
+
|
|
95
|
+
def test_raw_response_with_gpu_stats(
|
|
96
|
+
self,
|
|
97
|
+
client: CortexClient,
|
|
98
|
+
depth_image: np.ndarray,
|
|
99
|
+
camera_intrinsics: np.ndarray,
|
|
100
|
+
aux_args: dict,
|
|
101
|
+
):
|
|
102
|
+
raw = client.run(
|
|
103
|
+
ModelType.CONTACT_GRASPNET,
|
|
104
|
+
depth_image=depth_image,
|
|
105
|
+
camera_intrinsics=camera_intrinsics,
|
|
106
|
+
aux_args=aux_args,
|
|
107
|
+
raw_response=True,
|
|
108
|
+
)
|
|
109
|
+
assert "output" in raw
|
|
110
|
+
assert "confidence" in raw
|
|
111
|
+
assert "latency_ms" in raw
|
|
112
|
+
assert "gpu_stats" in raw
|
|
113
|
+
assert "mem_usage_mb" in raw["gpu_stats"]
|
|
114
|
+
|
|
115
|
+
def test_grasp_output_structure(
|
|
116
|
+
self,
|
|
117
|
+
client: CortexClient,
|
|
118
|
+
depth_image: np.ndarray,
|
|
119
|
+
camera_intrinsics: np.ndarray,
|
|
120
|
+
aux_args: dict,
|
|
121
|
+
):
|
|
122
|
+
result = client.run(
|
|
123
|
+
ModelType.CONTACT_GRASPNET,
|
|
124
|
+
depth_image=depth_image,
|
|
125
|
+
camera_intrinsics=camera_intrinsics,
|
|
126
|
+
aux_args=aux_args,
|
|
127
|
+
)
|
|
128
|
+
grasps = result["grasps"]
|
|
129
|
+
confidence = result["confidence"]
|
|
130
|
+
if len(grasps) > 0:
|
|
131
|
+
assert grasps.ndim == 3
|
|
132
|
+
assert grasps.shape[1:] == (4, 4)
|
|
133
|
+
assert confidence.ndim == 1
|
|
134
|
+
assert len(grasps) == len(confidence)
|
|
135
|
+
assert len(grasps) <= aux_args["top_k"]
|
|
136
|
+
|
|
137
|
+
def test_maximum_payload_size(
|
|
138
|
+
self,
|
|
139
|
+
client: CortexClient,
|
|
140
|
+
camera_intrinsics: np.ndarray,
|
|
141
|
+
aux_args: dict,
|
|
142
|
+
):
|
|
143
|
+
large_depth = np.random.rand(*self.max_input_size).astype(np.float32) * 2.0
|
|
144
|
+
result = client.run(
|
|
145
|
+
ModelType.CONTACT_GRASPNET,
|
|
146
|
+
depth_image=large_depth,
|
|
147
|
+
camera_intrinsics=camera_intrinsics,
|
|
148
|
+
aux_args=aux_args,
|
|
149
|
+
)
|
|
150
|
+
assert result is not None
|
|
151
|
+
|
|
152
|
+
def test_exceeds_maximum_payload_size(
|
|
153
|
+
self,
|
|
154
|
+
client: CortexClient,
|
|
155
|
+
camera_intrinsics: np.ndarray,
|
|
156
|
+
aux_args: dict,
|
|
157
|
+
):
|
|
158
|
+
# Handler guard at depth.size > 4_000_000 -> 413.
|
|
159
|
+
huge_depth = np.random.rand(2001, 2001).astype(np.float32) * 2.0
|
|
160
|
+
with pytest.raises(CortexAPIError) as exc_info:
|
|
161
|
+
client.run(
|
|
162
|
+
ModelType.CONTACT_GRASPNET,
|
|
163
|
+
depth_image=huge_depth,
|
|
164
|
+
camera_intrinsics=camera_intrinsics,
|
|
165
|
+
aux_args=aux_args,
|
|
166
|
+
)
|
|
167
|
+
assert exc_info.value.status_code == 413
|
|
168
|
+
|
|
169
|
+
def test_inference_latency(
|
|
170
|
+
self,
|
|
171
|
+
client: CortexClient,
|
|
172
|
+
depth_image: np.ndarray,
|
|
173
|
+
camera_intrinsics: np.ndarray,
|
|
174
|
+
aux_args: dict,
|
|
175
|
+
):
|
|
176
|
+
start = time.time()
|
|
177
|
+
result = client.run(
|
|
178
|
+
ModelType.CONTACT_GRASPNET,
|
|
179
|
+
depth_image=depth_image,
|
|
180
|
+
camera_intrinsics=camera_intrinsics,
|
|
181
|
+
aux_args=aux_args,
|
|
182
|
+
)
|
|
183
|
+
elapsed = time.time() - start
|
|
184
|
+
assert result is not None
|
|
185
|
+
assert elapsed < 50.0, f"Inference took {elapsed:.2f}s, expected < 50s"
|
|
186
|
+
|
|
187
|
+
def test_missing_camera_intrinsics(
|
|
188
|
+
self,
|
|
189
|
+
client: CortexClient,
|
|
190
|
+
depth_image: np.ndarray,
|
|
191
|
+
aux_args: dict,
|
|
192
|
+
):
|
|
193
|
+
with pytest.raises(ValueError):
|
|
194
|
+
client.run(
|
|
195
|
+
ModelType.CONTACT_GRASPNET,
|
|
196
|
+
depth_image=depth_image,
|
|
197
|
+
aux_args=aux_args,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def test_missing_depth_image(
|
|
201
|
+
self,
|
|
202
|
+
client: CortexClient,
|
|
203
|
+
camera_intrinsics: np.ndarray,
|
|
204
|
+
aux_args: dict,
|
|
205
|
+
):
|
|
206
|
+
with pytest.raises(ValueError):
|
|
207
|
+
client.run(
|
|
208
|
+
ModelType.CONTACT_GRASPNET,
|
|
209
|
+
camera_intrinsics=camera_intrinsics,
|
|
210
|
+
aux_args=aux_args,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
@pytest.mark.asyncio
|
|
214
|
+
async def test_concurrent_requests(
|
|
215
|
+
self,
|
|
216
|
+
async_client: AsyncCortexClient,
|
|
217
|
+
depth_image: np.ndarray,
|
|
218
|
+
camera_intrinsics: np.ndarray,
|
|
219
|
+
aux_args: dict,
|
|
220
|
+
):
|
|
221
|
+
async def make_request():
|
|
222
|
+
return await async_client.run(
|
|
223
|
+
ModelType.CONTACT_GRASPNET,
|
|
224
|
+
depth_image=depth_image,
|
|
225
|
+
camera_intrinsics=camera_intrinsics,
|
|
226
|
+
aux_args=aux_args,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Match graspgen's pragmatic cap-of-1 — keeps the test from timing out
|
|
230
|
+
# at full input size while still exercising the async path.
|
|
231
|
+
num_requests = min(self.batch_size, 1)
|
|
232
|
+
results = await run_concurrent_requests(make_request, num_requests=num_requests)
|
|
233
|
+
assert len(results) == num_requests
|
|
234
|
+
assert all(not isinstance(r, Exception) for r in results)
|
|
235
|
+
assert all("grasps" in r for r in results)
|
|
236
|
+
|
|
237
|
+
@pytest.mark.asyncio
|
|
238
|
+
async def test_gpu_memory_usage(
|
|
239
|
+
self,
|
|
240
|
+
async_client: AsyncCortexClient,
|
|
241
|
+
depth_image: np.ndarray,
|
|
242
|
+
camera_intrinsics: np.ndarray,
|
|
243
|
+
aux_args: dict,
|
|
244
|
+
):
|
|
245
|
+
async def make_request():
|
|
246
|
+
return await async_client.run(
|
|
247
|
+
ModelType.CONTACT_GRASPNET,
|
|
248
|
+
depth_image=depth_image,
|
|
249
|
+
camera_intrinsics=camera_intrinsics,
|
|
250
|
+
aux_args=aux_args,
|
|
251
|
+
raw_response=True,
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
num_requests = min(self.batch_size, 2)
|
|
255
|
+
results = await run_concurrent_requests(make_request, num_requests=num_requests)
|
|
256
|
+
assert len(results) == num_requests
|
|
257
|
+
assert all(not isinstance(r, Exception) for r in results)
|
|
258
|
+
for result in results:
|
|
259
|
+
print(f"Specified GPU mem: {self.gpu_mem} MB, Current GPU mem: {result['gpu_stats']['mem_usage_mb']} MB")
|
|
260
|
+
assert all(is_gpu_mem_close(self.gpu_mem, result["gpu_stats"]["mem_usage_mb"]) for result in results), (
|
|
261
|
+
f"GPU memory usage too high: {[r['gpu_stats']['mem_usage_mb'] for r in results]} MB"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
@pytest.mark.asyncio
|
|
265
|
+
async def test_cortex_hub(
|
|
266
|
+
self,
|
|
267
|
+
depth_image: np.ndarray,
|
|
268
|
+
camera_intrinsics: np.ndarray,
|
|
269
|
+
aux_args: dict,
|
|
270
|
+
):
|
|
271
|
+
api_key = os.getenv("GRID_CORTEX_API_KEY") or os.getenv("CORTEX_API_KEY")
|
|
272
|
+
if not api_key:
|
|
273
|
+
pytest.skip("GRID_CORTEX_API_KEY or CORTEX_API_KEY not set")
|
|
274
|
+
|
|
275
|
+
async with CortexHubClient(api_key=api_key) as hub:
|
|
276
|
+
result = await hub.run(
|
|
277
|
+
ModelType.CONTACT_GRASPNET,
|
|
278
|
+
depth_image=depth_image,
|
|
279
|
+
camera_intrinsics=camera_intrinsics,
|
|
280
|
+
aux_args=aux_args,
|
|
281
|
+
)
|
|
282
|
+
assert result is not None
|
|
@@ -55,6 +55,24 @@ class TestMetric3DHTTP(BaseModelTestHTTP):
|
|
|
55
55
|
with pytest.raises((TypeError, CortexAPIError)):
|
|
56
56
|
client.run(ModelType.METRIC3D, image_input=[rgb_image_np, rgb_image_np])
|
|
57
57
|
|
|
58
|
+
def test_output_selection(self, client: CortexClient, rgb_image_np: np.ndarray):
|
|
59
|
+
"""outputs= returns only the requested maps; invalid names are rejected client-side."""
|
|
60
|
+
depth_only = client.run(ModelType.METRIC3D, image_input=rgb_image_np, outputs=["depth"])
|
|
61
|
+
assert set(depth_only) == {"depth"}
|
|
62
|
+
assert depth_only["depth"].shape == rgb_image_np.shape[:2]
|
|
63
|
+
|
|
64
|
+
normals_only = client.run(ModelType.METRIC3D, image_input=rgb_image_np, outputs="normals")
|
|
65
|
+
assert set(normals_only) == {"normals"}
|
|
66
|
+
assert normals_only["normals"].shape == (*rgb_image_np.shape[:2], 3)
|
|
67
|
+
|
|
68
|
+
both = client.run(ModelType.METRIC3D, image_input=rgb_image_np, outputs=["depth", "normals"])
|
|
69
|
+
assert set(both) == {"depth", "normals"}
|
|
70
|
+
|
|
71
|
+
with pytest.raises(ValueError):
|
|
72
|
+
client.run(ModelType.METRIC3D, image_input=rgb_image_np, outputs=["depth", "rgb"])
|
|
73
|
+
with pytest.raises(ValueError):
|
|
74
|
+
client.run(ModelType.METRIC3D, image_input=rgb_image_np, outputs=[])
|
|
75
|
+
|
|
58
76
|
def test_maximum_payload_size(self, client: CortexClient):
|
|
59
77
|
"""Test that inference works with maximum allowed payload size."""
|
|
60
78
|
large_image = np.random.randint(0, 255, (*self.max_input_size, 3), dtype=np.uint8)
|
|
@@ -122,7 +140,9 @@ class TestMetric3DHTTP(BaseModelTestHTTP):
|
|
|
122
140
|
|
|
123
141
|
async with CortexHubClient(api_key=api_key) as hub:
|
|
124
142
|
result = await hub.run(ModelType.METRIC3D, image_input=rgb_image_np)
|
|
143
|
+
depth_only = await hub.run(ModelType.METRIC3D, image_input=rgb_image_np, outputs=["depth"])
|
|
125
144
|
assert result is not None
|
|
126
145
|
assert isinstance(result, dict)
|
|
127
146
|
assert "depth" in result
|
|
128
147
|
assert "normals" in result
|
|
148
|
+
assert set(depth_only) == {"depth"}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"""Metric3D wrapper.
|
|
2
|
-
|
|
3
|
-
Client-side helper for monocular metric depth estimation and surface normal
|
|
4
|
-
prediction powered by Metric3D ViT-Large.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from io import BytesIO
|
|
10
|
-
from typing import Any, Dict, Union
|
|
11
|
-
|
|
12
|
-
import numpy as np
|
|
13
|
-
from PIL import Image
|
|
14
|
-
|
|
15
|
-
from ..preprocessing import load_image, image_to_bytes
|
|
16
|
-
from .base_model import BaseModel
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class Metric3D(BaseModel):
|
|
20
|
-
"""Monocular metric depth + surface normals (Metric3D ViT-Large).
|
|
21
|
-
|
|
22
|
-
Preferred usage (through :class:`CortexClient`)
|
|
23
|
-
------------------------------------------------
|
|
24
|
-
```pycon
|
|
25
|
-
>>> result = CortexClient().run("metric3d", image_input=img)
|
|
26
|
-
>>> result["depth"].shape # (H, W) float32 metres
|
|
27
|
-
>>> result["normals"].shape # (H, W, 3) float32 unit normals
|
|
28
|
-
```
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
name: str = "metric3d"
|
|
32
|
-
model_id: str = "metric3d"
|
|
33
|
-
|
|
34
|
-
def __init__(self, *, transport=None) -> None:
|
|
35
|
-
super().__init__(transport=transport)
|
|
36
|
-
|
|
37
|
-
# ------------------------------------------------------------------
|
|
38
|
-
# BaseModel interface
|
|
39
|
-
# ------------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
def preprocess(
|
|
42
|
-
self,
|
|
43
|
-
*,
|
|
44
|
-
image_input: Union[str, Image.Image, np.ndarray],
|
|
45
|
-
) -> Dict[str, Any]:
|
|
46
|
-
"""Return msgpack-ready payload with raw image bytes under key ``image_input``."""
|
|
47
|
-
pil_img = load_image(image_input)
|
|
48
|
-
return {"image_input": image_to_bytes(pil_img, encoding_format="JPEG")}
|
|
49
|
-
|
|
50
|
-
def postprocess(
|
|
51
|
-
self,
|
|
52
|
-
response_data: Dict[str, Any],
|
|
53
|
-
**_: Any,
|
|
54
|
-
) -> Dict[str, np.ndarray]:
|
|
55
|
-
"""Decode server response into depth and normals arrays.
|
|
56
|
-
|
|
57
|
-
Returns:
|
|
58
|
-
Dict with ``"depth"`` (H, W) float32 and ``"normals"`` (H, W, 3) float32.
|
|
59
|
-
"""
|
|
60
|
-
if isinstance(response_data, list):
|
|
61
|
-
response_data = response_data[0]
|
|
62
|
-
|
|
63
|
-
depth_bytes = response_data.get("depth")
|
|
64
|
-
normals_bytes = response_data.get("normals")
|
|
65
|
-
|
|
66
|
-
if not isinstance(depth_bytes, (bytes, bytearray, memoryview)):
|
|
67
|
-
raise ValueError("'depth' field missing or not bytes")
|
|
68
|
-
if not isinstance(normals_bytes, (bytes, bytearray, memoryview)):
|
|
69
|
-
raise ValueError("'normals' field missing or not bytes")
|
|
70
|
-
|
|
71
|
-
depth = np.load(BytesIO(depth_bytes), allow_pickle=False).astype(np.float32)
|
|
72
|
-
normals = np.load(BytesIO(normals_bytes), allow_pickle=False).astype(np.float32)
|
|
73
|
-
|
|
74
|
-
return {"depth": depth, "normals": normals}
|
|
75
|
-
|
|
76
|
-
def run(
|
|
77
|
-
self,
|
|
78
|
-
image_input: Union[str, Image.Image, np.ndarray],
|
|
79
|
-
timeout: float | None = None,
|
|
80
|
-
) -> Dict[str, np.ndarray]:
|
|
81
|
-
"""Estimate metric depth and surface normals from a single RGB image.
|
|
82
|
-
|
|
83
|
-
Args:
|
|
84
|
-
image_input: RGB image as file path, URL, PIL Image, or numpy array.
|
|
85
|
-
timeout: Optional timeout in seconds for the HTTP request.
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
Dict with keys:
|
|
89
|
-
- ``"depth"``: (H, W) float32 array, metric depth in metres.
|
|
90
|
-
- ``"normals"``: (H, W, 3) float32 array, unit surface normals.
|
|
91
|
-
"""
|
|
92
|
-
return super().run(
|
|
93
|
-
image_input=image_input,
|
|
94
|
-
timeout=timeout,
|
|
95
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/cortex_client.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/cortex_hub_client.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/__init__.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/base_model.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/da3metric.py
RENAMED
|
File without changes
|
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/finetune_pi05.py
RENAMED
|
File without changes
|
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/graspgen.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/graspgenx.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/gsam2.py
RENAMED
|
File without changes
|
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/moondream.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/oneformer.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/owlv2.py
RENAMED
|
File without changes
|
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/qwen_vl.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/models/zoedepth.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/preprocessing.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/tools/__init__.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/tools/generate_enum.py
RENAMED
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/src/grid_cortex_client/tools/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{grid_cortex_client-0.4.0 → grid_cortex_client-0.5.0}/tests/test_ur5e_pyroki_collision_batch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|