Skip to content

Commit 40489df

Browse files
Merge pull request #32 from OlafenwaMoses/main
release - Annotation tasks, Stereo depth image input fixation to allowed dimension, logic fixes
2 parents b918933 + f8e2d59 commit 40489df

39 files changed

Lines changed: 2712 additions & 194 deletions

docs/annotation/object_mask_annotation_3d.md

Lines changed: 408 additions & 0 deletions
Large diffs are not rendered by default.

docs/concepts/camera_intrinsics.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ K = | 0.0 525.0 239.5 |
159159
| 0.0 0.0 1.0 |
160160
```
161161

162-
For a different camera or resolution, always supply calibrated values — wrong intrinsics produce correct topology but geometrically distorted metric scale.
162+
For a different camera or resolution, always supply calibrated values — intrinsics that do not match your camera produce correct topology but geometrically distorted metric scale.
163163

164164
See the full field reference and per-entry-point usage examples in the Advanced Config pages:
165165

docs/features/depth_estimation.md

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Set `VIZION3D_MODEL_CACHE` in your environment to change the default cache direc
114114
| `return_depth_image` | `bool` | No | `True` | If `True`, the result includes a 16-bit grayscale Open3D Image. Depth Anything V2 outputs inverse relative depth (higher = closer), so higher uint16 values = closer pixels. |
115115
| `return_raw_depth` | `bool` | No | `True` | If `True`, the result includes the raw depth as a float32 numpy array `(H, W)` — unmodified model output, relative values (not metric). |
116116
| `return_point_cloud` | `bool` | No | `False` | If `True`, the result includes an Open3D PointCloud unprojected from the RGB-D image. |
117-
| `advanced_config` | `DepthEstimationAdvanceConfig` | No | PrimeSense defaults | Camera intrinsics and depth range settings. See [Advanced config](#10-advanced-config-camera-intrinsics-depth-range) below. Not sure what intrinsics are? See [Camera Intrinsics Matrix](../concepts/camera_intrinsics.md). |
117+
| `advanced_config` | `DepthEstimationAdvanceConfig` | No | PrimeSense defaults | Camera intrinsics and depth range settings. See [Advanced config](#8-advanced-config-camera-intrinsics-depth-range) below. Not sure what intrinsics are? See [Camera Intrinsics Matrix](../concepts/camera_intrinsics.md). |
118118

119119
---
120120

@@ -261,36 +261,7 @@ o3d.io.write_point_cloud("scene.ply", pcd)
261261

262262
---
263263

264-
## 6. Custom model backend
265-
266-
Use a local `.pth` checkpoint or a remote URL to a `.pth` file.
267-
268-
```python
269-
from vizion3d.lifting import DepthEstimation, DepthEstimationCommand
270-
271-
# Local checkpoint
272-
cmd = DepthEstimationCommand(
273-
image_input="scene.png",
274-
model_backend="/models/depth_anything_v2_vitl.pth",
275-
)
276-
result = DepthEstimation().run(cmd)
277-
print(f"Backend: {result.backend_used}")
278-
279-
# Remote checkpoint URL (downloaded and cached on first use)
280-
cmd = DepthEstimationCommand(
281-
image_input="scene.png",
282-
model_backend=(
283-
"https://github.com/OlafenwaMoses/vizion3D/releases/download/"
284-
"essentials-v1/depth_anything_v2_vitb.pth"
285-
),
286-
)
287-
result = DepthEstimation().run(cmd)
288-
print(f"Backend: {result.backend_used}")
289-
```
290-
291-
---
292-
293-
## 7. REST API
264+
## 6. REST API
294265

295266
Start the server with all REST features enabled:
296267

@@ -341,7 +312,7 @@ The response is a JSON-serialised `DepthEstimationResult`. Binary fields (`depth
341312

342313
---
343314

344-
## 8. gRPC API
315+
## 7. gRPC API
345316

346317
Start the server:
347318

@@ -380,7 +351,7 @@ print(f"Backend : {response.backend_used}")
380351

381352
---
382353

383-
## 9. Advanced config: camera intrinsics & depth range
354+
## 8. Advanced config: camera intrinsics & depth range
384355

385356
`DepthEstimationAdvanceConfig` lets you supply the actual camera intrinsics and depth range for your sensor, replacing the built-in PrimeSense defaults. This is required for accurate metric 3D geometry when your camera is not a 640×480 PrimeSense sensor.
386357

@@ -410,8 +381,6 @@ The same config is available in the REST and gRPC entry points. See [Advanced Co
410381

411382
---
412383

413-
---
414-
415384
## Known limitations
416385

417386
- **Relative depth only** — the default monocular backend produces relative (inverse) depth, not metric depth. Point cloud distances are internally consistent but not calibrated to real-world scale without a known reference distance.

docs/features/depth_estimation_advanced_config.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ X = (u - cx) * d / fx
1616
Y = (v - cy) * d / fy
1717
```
1818

19-
All four intrinsic parameters — `fx`, `fy`, `cx`, `cy` — appear in this formula. Getting them wrong produces a point cloud that is geometrically distorted: correct topology but wrong angles, skewed shapes, or objects that appear compressed or stretched.
19+
All four intrinsic parameters — `fx`, `fy`, `cx`, `cy` — appear in this formula. Values that do not match your camera produce a point cloud that is geometrically distorted: correct topology but skewed angles, compressed shapes, or stretched geometry.
2020

2121
---
2222

@@ -44,7 +44,7 @@ fx = (image_width / 2) / tan(FoV_h / 2)
4444

4545
The vertical focal length in pixels. For cameras with square pixels, `fy ≈ fx`. Cameras with non-square sensors may have `fy ≠ fx`.
4646

47-
**Effect on the point cloud:** Controls vertical spread analogously to `fx`. Incorrect `fy` produces vertically compressed or stretched geometry.
47+
**Effect on the point cloud:** Controls vertical spread analogously to `fx`. A `fy` that does not match your sensor produces vertically compressed or stretched geometry.
4848

4949
**How to find it:** `K[1][1]` from the calibration matrix, or:
5050

@@ -60,7 +60,7 @@ fy = (image_height / 2) / tan(FoV_v / 2)
6060

6161
The horizontal image coordinate of the optical axis — ideally the exact centre of the sensor. For a 640-wide image the ideal value is `319.5`; for a 1920-wide image it is typically near `959.5`.
6262

63-
**Effect on the point cloud:** Shifts the entire point cloud left or right. A wrong `cx` makes the scene appear to be viewed from an off-centre vantage point, introducing a lateral tilt.
63+
**Effect on the point cloud:** Shifts the entire point cloud left or right. A `cx` that does not match your sensor makes the scene appear viewed from an off-centre vantage point, introducing a lateral tilt.
6464

6565
---
6666

@@ -70,7 +70,7 @@ The horizontal image coordinate of the optical axis — ideally the exact centre
7070

7171
The vertical image coordinate of the optical axis. For a 480-tall image the ideal value is `239.5`.
7272

73-
**Effect on the point cloud:** Shifts the entire point cloud up or down. Like `cx`, an incorrect value introduces a tilt — vertical in this case.
73+
**Effect on the point cloud:** Shifts the entire point cloud up or down. Like `cx`, a value that does not match your sensor introduces a tilt — vertical in this case.
7474

7575
---
7676

docs/features/depth_estimation_vs_stereo_depth.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,19 @@ Provided the camera calibration is accurate, the output is **real metric depth i
2828
|---|---|---|
2929
| **Input** | Rectified left + right image pair | Single RGB image |
3030
| **Depth type** | Metric (real metres) | Relative (inverse depth, arbitrary scale) |
31-
| **Coordinate system** | Camera space | Camera space |
32-
| **Units** | Metres (real) | Metres (fictitious — mapped to `[0, depth_trunc]`) |
31+
| **Coordinate system** | Camera space (X right, Y down, Z forward) | Camera space (X right, Y down, Z forward) |
32+
| **Z ordering** | Near objects have smaller Z | Near objects have larger Z — relative depth only, not physical ordering |
33+
| **Units** | Metres (real) | Metres (relative, mapped to `[0, depth_trunc]`) |
3334
| **Object at 2.4 m reads as 2.4 m** | Yes — if calibration is correct | No — depends on scene content |
34-
| **Scale factor to world** | 1.0 (accurate) | Unknown, scene-dependent |
35-
| **`point_cloud_scale` field** | 1.0 (accurate) | 1.0 (misleading — not real metres) |
35+
| **Scale factor to world** | 1.0 (real) | Unknown, scene-dependent |
36+
| **`point_cloud_scale` field** | 1.0 (real metres) | 1.0 (relative, not real metres) |
3637
| **Shape / topology correct** | Yes | Yes, if correct intrinsics supplied via `DepthEstimationAdvanceConfig` |
3738
| **Camera calibration needed** | Yes — `focal_length`, `baseline`, `cx`, `cy` | Optional — only affects point cloud geometry |
39+
| **Compatible with annotation task** || ✅ back-projection is self-consistent |
40+
| **Compatible with other 3D tools** | ✅ registration, reconstruction, metric tools | ⚠️ Z ordering is relative — not directly interoperable with metric clouds |
41+
| **Output format** | Open3D `PointCloud` | Open3D `PointCloud` |
42+
| **PLY export** |||
43+
| **MPS inference** | float32 | float32 |
3844
| **Input requirements** | Stereo rig, rectified images | Any single photo |
3945
| **Depth completeness** | Gaps in occluded / textureless regions | Dense — every pixel has a prediction |
4046
| **Runtime** | Moderate (transformer-based matching) | Moderate (ViT-based encoder-decoder) |
@@ -78,7 +84,7 @@ result = DepthEstimation().run(
7884
points = np.asarray(result.point_cloud.points) # shape (N, 3)
7985
# point_cloud_scale == 1.0, but distances are NOT real metres —
8086
# the depth model output is relative and mapped to depth_trunc.
81-
print(f"point_cloud_scale: {result.point_cloud_scale}") # 1.0 (misleading)
87+
print(f"point_cloud_scale: {result.point_cloud_scale}") # 1.0 (relative, not real metres)
8288
```
8389

8490
### Stereo point cloud (Stereo Depth)
@@ -113,10 +119,10 @@ print(f"point_cloud_scale: {result.point_cloud_scale}") # 1.0 (accurate)
113119

114120
| Output field | Depth Estimation | Stereo Depth |
115121
|---|---|---|
116-
| `depth_map` | Relative depth (fictitious metres) | Metric depth (real metres) |
122+
| `depth_map` | Relative depth (not real metres) | Metric depth (real metres) |
117123
| `disparity_map` | Not present | Pixel disparity (always returned) |
118124
| `min_depth` / `max_depth` | Relative range | Real range in metres |
119-
| `point_cloud_scale` | 1.0 (misleading) | 1.0 (accurate) |
125+
| `point_cloud_scale` | 1.0 (relative, not real metres) | 1.0 (real metres) |
120126
| `backend_used` | Local path to Depth Anything V2 `.pth` | Local path to S2M2 `.pth` |
121127

122128
---
@@ -155,10 +161,10 @@ cfg = StereoDepthAdvancedConfig(
155161
cy=360.0, # principal point y
156162
baseline=100.0, # stereo baseline in millimetres
157163
doffs=0.0, # disparity offset (Middlebury-style calibration)
158-
z_far=10.0, # max depth in metres
164+
z_far=50.0, # max depth in metres
159165
conf_threshold=0.1, # min confidence score for point inclusion
160166
occ_threshold=0.5, # min occlusion score for point inclusion
161-
scale_factor=1.0, # input downscale for speed/quality tradeoff
167+
# input is auto-resized to fit 960×540 before inference
162168
)
163169
```
164170

docs/features/stereo_depth.md

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -118,23 +118,10 @@ curl -L \
118118

119119
| Value | What happens |
120120
|---|---|
121-
| *(default)* | Downloads the vizion3D release checkpoint (`stereo-depth-s2m2-L.pth`, the L variant) to `~/.cache/vizion3d/models/` on first use, then loads it |
122-
| An HTTPS URL ending in `.pth` or `.pt` | Downloaded to the cache directory on first use, then loaded as an S2M2 checkpoint |
123-
| A local `.pth` or `.pt` file path | Loaded directly — no download |
121+
| *(default)* | Downloads the vizion3D release checkpoint (`stereo-depth-s2m2-L.pth`) to `~/.cache/vizion3d/models/` on first use, then loads it |
124122

125123
Models are kept in memory after the first inference. Set `VIZION3D_MODEL_CACHE` to override the cache directory.
126124

127-
### S2M2 variants
128-
129-
The S2M2 architecture comes in four size variants. The correct one is detected automatically from the checkpoint filename:
130-
131-
| Variant | Channels | Transformers | Speed | Quality |
132-
|---|---|---|---|---|
133-
| S (`-S.pth`) | 128 | 1 | Fastest | Good |
134-
| M (`-M.pth`) | 192 | 2 | Fast | Better |
135-
| L (`-L.pth`) | 256 | 3 | Balanced | Best (default) |
136-
| XL (`-XL.pth`) | 384 | 3 | Slowest | Best |
137-
138125
---
139126

140127
## Command parameters
@@ -306,22 +293,11 @@ o3d.io.write_point_cloud("scene.ply", result.point_cloud)
306293

307294
---
308295

309-
## 7. Speed vs quality: scale factor
296+
## 7. Automatic input scaling
310297

311-
Use `scale_factor < 1.0` to downsample input before inference for faster results:
298+
The handler automatically resizes both images to fit within **960 × 540** before inference, preserving the aspect ratio. This matches the resolution the model was trained near; running at higher resolutions collapses the internal correlation matrix to near-zero disparity and produces an empty point cloud.
312299

313-
```python
314-
from vizion3d.stereo import StereoDepth, StereoDepthAdvancedConfig, StereoDepthCommand
315-
316-
cmd = StereoDepthCommand(
317-
left_image="left.png",
318-
right_image="right.png",
319-
advanced_config=StereoDepthAdvancedConfig(
320-
scale_factor=0.5, # half-resolution → ~3–4× faster
321-
),
322-
)
323-
result = StereoDepth().run(cmd)
324-
```
300+
The resize is transparent — disparity and point cloud are reprojected back to the original image dimensions before the result is returned, so all depth values and 3D coordinates are in the original pixel coordinate space. No adjustment to your intrinsics (`focal_length`, `cx`, `cy`) is needed regardless of the input resolution.
325301

326302
---
327303

@@ -435,10 +411,10 @@ print(f"Backend : {response.backend_used}")
435411
| `cy` | `float` | `360.0` | Principal point y (pixel row of optical axis). |
436412
| `baseline` | `float` | `100.0` | Stereo baseline in **millimetres**. |
437413
| `doffs` | `float` | `0.0` | Disparity offset (non-zero for Middlebury-style calibration). |
438-
| `z_far` | `float` | `10.0` | Max depth in metres for point cloud. |
414+
| `z_far` | `float` | `50.0` | Max depth in metres for point cloud. |
439415
| `conf_threshold` | `float` | `0.1` | Min per-pixel confidence score for point cloud inclusion. |
440416
| `occ_threshold` | `float` | `0.5` | Min occlusion score for point cloud inclusion. |
441-
| `scale_factor` | `float` | `1.0` | Input downscale factor (`0.5` = half-res, ~3–4× faster). |
417+
| *(input scaling)* | | automatic | Images are automatically resized to fit within 960×540 before inference, preserving aspect ratio. Disparity and point cloud are reprojected back to the original resolution — metric depth and intrinsics are unaffected. |
442418

443419
### How to obtain camera intrinsics
444420

@@ -492,8 +468,55 @@ cfg = StereoDepthAdvancedConfig(
492468

493469
---
494470

471+
## 3D annotation from a stereo cloud
472+
473+
A stereo point cloud is in camera space (Z = metric depth, origin at the left camera), making it directly compatible with [Object Mask Annotation 3D](../annotation/object_mask_annotation_3d.md). Pass the same intrinsics you used for stereo depth. Do not pass `image_input` — the annotation task synthesises the segmentation image from the point cloud's stored colours, which avoids having to pick between the left and right frames.
474+
475+
```python
476+
import open3d as o3d
477+
from vizion3d.stereo import StereoDepth, StereoDepthCommand, StereoDepthAdvancedConfig
478+
from vizion3d.annotation import ObjectMaskAnnotation3D, ObjectMaskAnnotation3DCommand
479+
from vizion3d.annotation.models import ObjectMaskAnnotation3DConfig
480+
481+
stereo_result = StereoDepth().run(
482+
StereoDepthCommand(
483+
left_image="left.png",
484+
right_image="right.png",
485+
return_point_cloud=True,
486+
advanced_config=StereoDepthAdvancedConfig(
487+
focal_length=1733.74,
488+
cx=792.27,
489+
cy=541.89,
490+
baseline=536.62,
491+
),
492+
)
493+
)
494+
495+
annotation_result = ObjectMaskAnnotation3D().run(
496+
ObjectMaskAnnotation3DCommand(
497+
point_cloud=stereo_result.point_cloud,
498+
return_annotated_cloud=True,
499+
advanced_config=ObjectMaskAnnotation3DConfig(
500+
fx=1733.74,
501+
fy=1733.74,
502+
cx=792.27,
503+
cy=541.89,
504+
),
505+
)
506+
)
507+
508+
for ann in annotation_result.annotations:
509+
print(f"{ann.label:20s} conf={ann.confidence:.2f} 3D points={len(ann.point_indices)}")
510+
511+
o3d.io.write_point_cloud("annotated.ply", annotation_result.annotated_cloud)
512+
```
513+
514+
See [Object Mask Annotation 3D — Stereo integration](../annotation/object_mask_annotation_3d.md#5-stereo-point-cloud-integration) for the full walkthrough.
515+
516+
---
517+
495518
## Known limitations
496519

497-
- **Rectified pairs required** — images must be stereo-rectified so corresponding points lie on the same horizontal scanline. Un-rectified pairs will produce incorrect results.
498-
- **Metric scale depends on calibration** — an incorrect `baseline` or `focal_length` scales all depth values uniformly. Always use calibrated values for real applications.
520+
- **Rectified pairs required** — images must be stereo-rectified so corresponding points lie on the same horizontal scanline. Un-rectified pairs will not produce reliable results.
521+
- **Metric scale depends on calibration** — an inaccurate `baseline` or `focal_length` scales all depth values uniformly. Always use calibrated values for real applications.
499522
- **Python 3.12 required for Open3D**`return_depth_image` and `return_point_cloud` require Open3D, which currently only supports Python 3.12 in this project.

docs/index.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,37 @@ Each task lives in its own module under `vizion3d/<category>/` and exposes exact
195195
| Monocular depth estimation | Stable | [Depth Estimation](features/depth_estimation.md) |
196196
| Stereo depth estimation | Stable | [Stereo Depth](features/stereo_depth.md) |
197197

198+
### Annotation
199+
200+
| Task | Status | Docs |
201+
|---|---|---|
202+
| Object mask annotation 3D | Stable | [Object Mask Annotation 3D](annotation/object_mask_annotation_3d.md) |
203+
204+
---
205+
206+
## Quick start — object mask annotation 3D
207+
208+
Detect and instance-segment objects in a scene, then get the exact 3D point cloud subset for each detected object.
209+
210+
```python
211+
import open3d as o3d
212+
from vizion3d.annotation import ObjectMaskAnnotation3D, ObjectMaskAnnotation3DCommand
213+
214+
pcd = o3d.io.read_point_cloud("scene.ply")
215+
216+
result = ObjectMaskAnnotation3D().run(
217+
ObjectMaskAnnotation3DCommand(
218+
point_cloud=pcd,
219+
image_input="scene.jpg", # optional — omit to synthesise from the cloud
220+
return_annotated_cloud=True,
221+
)
222+
)
223+
224+
for ann in result.annotations:
225+
print(f"{ann.label:20s} conf={ann.confidence:.2f} 3D points={len(ann.point_indices)}")
226+
227+
o3d.io.write_point_cloud("annotated.ply", result.annotated_cloud)
228+
```
229+
230+
See [Object Mask Annotation 3D](annotation/object_mask_annotation_3d.md) for the full reference.
231+

mkdocs.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ plugins:
2525
nav:
2626
- Home: index.md
2727
- Hardware Acceleration: hardware_acceleration.md
28-
- Features:
29-
- Depth Estimation:
30-
- Overview: features/depth_estimation.md
31-
- Advanced Config: features/depth_estimation_advanced_config.md
32-
- Stereo Depth:
33-
- Overview: features/stereo_depth.md
34-
- Depth Estimation vs Stereo Depth: features/depth_estimation_vs_stereo_depth.md
28+
- Tasks:
29+
- Lifting:
30+
- Depth Estimation:
31+
- Overview: features/depth_estimation.md
32+
- Advanced Config: features/depth_estimation_advanced_config.md
33+
- Stereo Depth:
34+
- Overview: features/stereo_depth.md
35+
- Depth Estimation vs Stereo Depth: features/depth_estimation_vs_stereo_depth.md
36+
- Annotation:
37+
- Object Mask Annotation 3D: annotation/object_mask_annotation_3d.md
3538
- Concepts:
3639
- Camera Intrinsics Matrix: concepts/camera_intrinsics.md
3740
- API Reference:

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dependencies = [
3838
"pillow>=12.2.0",
3939
"open3d>=0.18.0",
4040
"orjson>=3.9.0",
41+
"ultralytics>=8.3.0",
4142
]
4243

4344
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)