Upload depth_pro recipe (v1)
Browse files- LICENSE +1 -0
- README.md +76 -0
- __init__.py +10 -0
- app.py +127 -0
- demo.py +60 -0
- manifest.yaml +36 -0
- model.py +146 -0
- requirements.txt +1 -0
- test.py +44 -0
LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
The license of the original trained model can be found at https://huggingface.co/apple/DepthPro/blob/main/LICENSE.
|
README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: pytorch
|
| 3 |
+
license: other
|
| 4 |
+
tags:
|
| 5 |
+
- qai-hub-models
|
| 6 |
+
- qualcomm
|
| 7 |
+
- android
|
| 8 |
+
pipeline_tag: depth-estimation
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# DepthPro: Sharp monocular metric depth in less than a second, on-device
|
| 12 |
+
|
| 13 |
+
Apple DepthPro is a zero-shot monocular metric depth estimator that emits high-resolution, sharp depth maps from a single 1536x1536 RGB image. The architecture is a multi-scale Vision Transformer built on Dinov2 encoders with DPT-style fusion; alongside the depth map the model predicts a per-image horizontal field of view, which downstream calibration converts into a focal length in pixels. This recipe wraps Apple's HuggingFace checkpoint (`apple/DepthPro-hf`, ~952M parameters) at the model's native 1536x1536 input resolution.
|
| 14 |
+
|
| 15 |
+
This is based on the implementation of DepthPro found [here](https://github.com/apple/ml-depth-pro).
|
| 16 |
+
This is a standalone recipe compatible with the [Qualcomm® AI Hub Models](https://github.com/quic/ai-hub-models) CLI — it can be compiled and evaluated on real Snapdragon devices via [Qualcomm® AI Hub Workbench](https://workbench.aihub.qualcomm.com).
|
| 17 |
+
|
| 18 |
+
Qualcomm AI Hub Models uses [Qualcomm AI Hub Workbench](https://workbench.aihub.qualcomm.com) to compile, profile, and evaluate this model. [Sign up](https://myaccount.qualcomm.com/signup) to run these models on a hosted Qualcomm® device.
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
## Setup
|
| 22 |
+
### 1. Install the package
|
| 23 |
+
Install the base package, fetch this recipe from Hugging Face, then use the
|
| 24 |
+
`qai-hub-models` CLI to install the recipe's dependencies:
|
| 25 |
+
```bash
|
| 26 |
+
# NOTE: 3.10 <= PYTHON_VERSION < 3.14 is supported.
|
| 27 |
+
pip install qai-hub-models
|
| 28 |
+
qai-hub-models register ashwmurt/depth_pro
|
| 29 |
+
qai-hub-models install depth_pro
|
| 30 |
+
```
|
| 31 |
+
`register` downloads the recipe and names it `depth_pro`, which is how every
|
| 32 |
+
command below refers to it.
|
| 33 |
+
|
| 34 |
+
### 2. Configure Qualcomm® AI Hub Workbench
|
| 35 |
+
Sign-in to [Qualcomm® AI Hub Workbench](https://workbench.aihub.qualcomm.com/) with your
|
| 36 |
+
Qualcomm® ID. Once signed in navigate to `Account -> Settings -> API Token`.
|
| 37 |
+
|
| 38 |
+
With this API token, you can configure your client to run models on the cloud
|
| 39 |
+
hosted devices.
|
| 40 |
+
```bash
|
| 41 |
+
qai-hub configure --api_token API_TOKEN
|
| 42 |
+
```
|
| 43 |
+
Navigate to [docs](https://workbench.aihub.qualcomm.com/docs/) for more information.
|
| 44 |
+
|
| 45 |
+
## Run CLI Demo
|
| 46 |
+
Run the following simple CLI demo to verify the model is working end to end:
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
qai-hub-models demo depth_pro
|
| 50 |
+
```
|
| 51 |
+
More details on the CLI tool can be found with the `--help` option. See
|
| 52 |
+
[demo.py](demo.py) for sample usage of the model including pre/post processing
|
| 53 |
+
scripts.
|
| 54 |
+
|
| 55 |
+
By default, the demo will run locally in PyTorch. Pass `--eval-mode on-device` to run the model on a cloud-hosted target device.
|
| 56 |
+
|
| 57 |
+
## Export for on-device deployment
|
| 58 |
+
To run the model on Qualcomm® devices, you must export the model for use with an edge runtime such as
|
| 59 |
+
TensorFlow Lite, ONNX Runtime, or Qualcomm AI Engine Direct.
|
| 60 |
+
Use the following command to export the model:
|
| 61 |
+
```bash
|
| 62 |
+
qai-hub-models export depth_pro
|
| 63 |
+
```
|
| 64 |
+
Additional options are documented with the `--help` option.
|
| 65 |
+
|
| 66 |
+
## License
|
| 67 |
+
* The license for the original implementation of DepthPro can be found
|
| 68 |
+
[here](https://huggingface.co/apple/DepthPro/blob/main/LICENSE).
|
| 69 |
+
|
| 70 |
+
## References
|
| 71 |
+
* [Depth Pro: Sharp Monocular Metric Depth in Less Than a Second](https://arxiv.org/abs/2410.02073)
|
| 72 |
+
* [Source Model Implementation](https://github.com/apple/ml-depth-pro)
|
| 73 |
+
|
| 74 |
+
## Community
|
| 75 |
+
* Join [our AI Hub Slack community](https://aihub.qualcomm.com/community/slack) to collaborate, post questions and learn more about on-device AI.
|
| 76 |
+
* For questions or feedback please [reach out to us](mailto:ai-hub-support@qti.qualcomm.com).
|
__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ---------------------------------------------------------------------
|
| 2 |
+
# Copyright (c) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries.
|
| 3 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
# ---------------------------------------------------------------------
|
| 5 |
+
|
| 6 |
+
from .app import DepthProApp as App
|
| 7 |
+
from .model import MODEL_ID
|
| 8 |
+
from .model import DepthPro as Model
|
| 9 |
+
|
| 10 |
+
__all__ = ["MODEL_ID", "App", "Model"]
|
app.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ---------------------------------------------------------------------
|
| 2 |
+
# Copyright (c) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries.
|
| 3 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
# ---------------------------------------------------------------------
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from collections.abc import Callable
|
| 9 |
+
from dataclasses import dataclass
|
| 10 |
+
from typing import Any, cast
|
| 11 |
+
|
| 12 |
+
import matplotlib.pyplot as plt
|
| 13 |
+
import numpy as np
|
| 14 |
+
import numpy.typing as npt
|
| 15 |
+
import torch
|
| 16 |
+
from PIL import Image
|
| 17 |
+
from torchvision import transforms
|
| 18 |
+
|
| 19 |
+
from qai_hub_models.utils.image_processing import pil_resize_pad, undo_resize_pad
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@dataclass
|
| 23 |
+
class DepthProPrediction:
|
| 24 |
+
"""Structured output of :class:`DepthProApp`.
|
| 25 |
+
|
| 26 |
+
``depth`` is metric depth in scene units (aligned via ``focal_length_px``);
|
| 27 |
+
``heatmap`` is a plasma-colored visualization of inverse depth for display;
|
| 28 |
+
``field_of_view`` is horizontal FoV in degrees; ``focal_length_px`` is
|
| 29 |
+
the pixel focal length derived from FoV and the original image width.
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
depth: npt.NDArray[np.float32]
|
| 33 |
+
heatmap: Image.Image
|
| 34 |
+
field_of_view: float
|
| 35 |
+
focal_length_px: float
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class DepthProApp:
|
| 39 |
+
"""End-to-end app for Apple DepthPro depth estimation.
|
| 40 |
+
|
| 41 |
+
Wraps a callable returning ``(predicted_depth, field_of_view)`` — either
|
| 42 |
+
the torch model or an on-device runner. Preprocessing resizes with
|
| 43 |
+
aspect-preserving padding to the network's 1536x1536 input; post-
|
| 44 |
+
processing mirrors HuggingFace's
|
| 45 |
+
``DepthProImageProcessorFast.post_process_depth_estimation`` (metric
|
| 46 |
+
scaling by ``width / focal_length_px``, then inversion of the canonical
|
| 47 |
+
inverse depth).
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
def __init__(
|
| 51 |
+
self,
|
| 52 |
+
model: Callable[
|
| 53 |
+
[torch.Tensor], tuple[torch.Tensor, torch.Tensor]
|
| 54 |
+
],
|
| 55 |
+
input_height: int | None = None,
|
| 56 |
+
input_width: int | None = None,
|
| 57 |
+
) -> None:
|
| 58 |
+
self.model = model
|
| 59 |
+
if input_height is None or input_width is None:
|
| 60 |
+
get_input_spec = getattr(model, "get_input_spec", None)
|
| 61 |
+
if get_input_spec is None:
|
| 62 |
+
raise TypeError(
|
| 63 |
+
"DepthProApp needs input_height and input_width when the "
|
| 64 |
+
"provided model is not a BaseModel (has no get_input_spec)."
|
| 65 |
+
)
|
| 66 |
+
_, _, h, w = get_input_spec()["image"][0]
|
| 67 |
+
input_height = input_height if input_height is not None else h
|
| 68 |
+
input_width = input_width if input_width is not None else w
|
| 69 |
+
self.input_height = input_height
|
| 70 |
+
self.input_width = input_width
|
| 71 |
+
|
| 72 |
+
def predict(self, *args: Any, **kwargs: Any) -> DepthProPrediction:
|
| 73 |
+
return self.estimate_depth(*args, **kwargs)
|
| 74 |
+
|
| 75 |
+
def estimate_depth(self, image: Image.Image) -> DepthProPrediction:
|
| 76 |
+
"""Estimate depth, FoV, and focal length for a single image.
|
| 77 |
+
|
| 78 |
+
Parameters
|
| 79 |
+
----------
|
| 80 |
+
image
|
| 81 |
+
PIL image in any resolution / aspect ratio.
|
| 82 |
+
"""
|
| 83 |
+
resized_image, scale, padding = pil_resize_pad(
|
| 84 |
+
image, (self.input_height, self.input_width)
|
| 85 |
+
)
|
| 86 |
+
image_tensor = transforms.ToTensor()(resized_image).unsqueeze(0)
|
| 87 |
+
predicted_depth, field_of_view = self.model(image_tensor)
|
| 88 |
+
|
| 89 |
+
# Horizontal FoV -> focal length in pixels of the *original* image.
|
| 90 |
+
# Matches HF's post_process_depth_estimation.
|
| 91 |
+
orig_width = float(image.size[0])
|
| 92 |
+
fov_deg = field_of_view.detach().float().view(-1)
|
| 93 |
+
focal_length_px = 0.5 * orig_width / torch.tan(
|
| 94 |
+
0.5 * torch.deg2rad(fov_deg)
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
# Metric scaling of canonical inverse depth (again, from HF).
|
| 98 |
+
depth_scaled = predicted_depth * (
|
| 99 |
+
orig_width / focal_length_px
|
| 100 |
+
).view(-1, 1, 1)
|
| 101 |
+
|
| 102 |
+
# (B, 1, H, W) shape is what undo_resize_pad expects.
|
| 103 |
+
depth_map = undo_resize_pad(
|
| 104 |
+
depth_scaled.unsqueeze(1), image.size, scale, padding
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
# Canonical inverse depth -> metric depth. Clamp mirrors HF.
|
| 108 |
+
depth_map = 1.0 / torch.clamp(depth_map, min=1e-4, max=1e4)
|
| 109 |
+
|
| 110 |
+
depth_np = cast(
|
| 111 |
+
npt.NDArray[np.float32],
|
| 112 |
+
depth_map.squeeze().detach().cpu().numpy().astype(np.float32),
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
# Visualize inverse depth so closer objects appear brighter, matching
|
| 116 |
+
# the shared depth-estimation demo convention.
|
| 117 |
+
inv = 1.0 / np.maximum(depth_np, 1e-6)
|
| 118 |
+
inv_norm = inv / max(inv.max(), 1e-6)
|
| 119 |
+
heatmap = plt.cm.get_cmap("plasma")(inv_norm)[..., :3]
|
| 120 |
+
heatmap_image = Image.fromarray((heatmap * 255).astype(np.uint8))
|
| 121 |
+
|
| 122 |
+
return DepthProPrediction(
|
| 123 |
+
depth=depth_np,
|
| 124 |
+
heatmap=heatmap_image,
|
| 125 |
+
field_of_view=float(fov_deg.squeeze().item()),
|
| 126 |
+
focal_length_px=float(focal_length_px.squeeze().item()),
|
| 127 |
+
)
|
demo.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ---------------------------------------------------------------------
|
| 2 |
+
# Copyright (c) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries.
|
| 3 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
# ---------------------------------------------------------------------
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from qai_hub_models.utils.args import (
|
| 9 |
+
demo_model_from_cli_args,
|
| 10 |
+
get_model_cli_parser,
|
| 11 |
+
get_on_device_demo_parser,
|
| 12 |
+
validate_on_device_demo_args,
|
| 13 |
+
)
|
| 14 |
+
from qai_hub_models.utils.asset_loaders import CachedWebModelAsset, load_image
|
| 15 |
+
from qai_hub_models.utils.display import display_or_save_image
|
| 16 |
+
|
| 17 |
+
from .app import DepthProApp
|
| 18 |
+
from .model import MODEL_ID, DepthPro
|
| 19 |
+
|
| 20 |
+
# Reuse the midas depth-estimation fixture — any indoor/outdoor natural image
|
| 21 |
+
# works; keeping this out-of-tree avoids uploading a fresh asset just for
|
| 22 |
+
# the initial recipe.
|
| 23 |
+
INPUT_IMAGE_ADDRESS = CachedWebModelAsset.from_asset_store(
|
| 24 |
+
"midas", 3, "test_input_image.jpg"
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def main(is_test: bool = False) -> None:
|
| 29 |
+
parser = get_model_cli_parser(DepthPro)
|
| 30 |
+
parser = get_on_device_demo_parser(parser, add_output_dir=True)
|
| 31 |
+
parser.add_argument(
|
| 32 |
+
"--image",
|
| 33 |
+
type=str,
|
| 34 |
+
default=INPUT_IMAGE_ADDRESS,
|
| 35 |
+
help="image file path or URL",
|
| 36 |
+
)
|
| 37 |
+
args = parser.parse_args([] if is_test else None)
|
| 38 |
+
model = demo_model_from_cli_args(DepthPro, MODEL_ID, args)
|
| 39 |
+
validate_on_device_demo_args(args, MODEL_ID)
|
| 40 |
+
|
| 41 |
+
(_, _, height, width) = model.get_input_spec()["image"][0]
|
| 42 |
+
image = load_image(args.image)
|
| 43 |
+
print("Model Loaded")
|
| 44 |
+
|
| 45 |
+
app = DepthProApp(model, height, width) # type: ignore[arg-type]
|
| 46 |
+
prediction = app.estimate_depth(image)
|
| 47 |
+
|
| 48 |
+
print(
|
| 49 |
+
f"Predicted field of view: {prediction.field_of_view:.2f} deg "
|
| 50 |
+
f"(focal length: {prediction.focal_length_px:.1f} px)"
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
if not is_test:
|
| 54 |
+
display_or_save_image(
|
| 55 |
+
prediction.heatmap, args.output_dir, "out_heatmap.png", "heatmap"
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
main()
|
manifest.yaml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: DepthPro
|
| 2 |
+
id: depth_pro
|
| 3 |
+
headline: Sharp monocular metric depth in less than a second, on-device.
|
| 4 |
+
description: Apple DepthPro is a zero-shot monocular metric depth estimator that
|
| 5 |
+
emits high-resolution, sharp depth maps from a single 1536x1536 RGB image.
|
| 6 |
+
The architecture is a multi-scale Vision Transformer built on Dinov2 encoders
|
| 7 |
+
with DPT-style fusion; alongside the depth map the model predicts a per-image
|
| 8 |
+
horizontal field of view, which downstream calibration converts into a focal
|
| 9 |
+
length in pixels. This recipe wraps Apple's HuggingFace checkpoint
|
| 10 |
+
(`apple/DepthPro-hf`, ~952M parameters) at the model's native 1536x1536 input
|
| 11 |
+
resolution.
|
| 12 |
+
domain: Computer Vision
|
| 13 |
+
use_case: Depth Estimation
|
| 14 |
+
applicable_scenarios:
|
| 15 |
+
- Anomaly Detection
|
| 16 |
+
- Inventory Management
|
| 17 |
+
form_factors:
|
| 18 |
+
- Phone
|
| 19 |
+
- Tablet
|
| 20 |
+
- IoT
|
| 21 |
+
- XR
|
| 22 |
+
has_static_banner: false
|
| 23 |
+
has_animated_banner: false
|
| 24 |
+
has_on_target_demo: true
|
| 25 |
+
license_type: other-non-commercial
|
| 26 |
+
license: https://huggingface.co/apple/DepthPro/blob/main/LICENSE
|
| 27 |
+
source_repo: https://github.com/apple/ml-depth-pro
|
| 28 |
+
research_paper: https://arxiv.org/abs/2410.02073
|
| 29 |
+
research_paper_title: "Depth Pro: Sharp Monocular Metric Depth in Less Than a Second"
|
| 30 |
+
related_models:
|
| 31 |
+
- depth_anything_v2
|
| 32 |
+
- midas
|
| 33 |
+
templates:
|
| 34 |
+
- depth_estimation
|
| 35 |
+
supported_precisions:
|
| 36 |
+
- float
|
model.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ---------------------------------------------------------------------
|
| 2 |
+
# Copyright (c) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries.
|
| 3 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
# ---------------------------------------------------------------------
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from transformers import DepthProForDepthEstimation
|
| 10 |
+
from typing_extensions import Self
|
| 11 |
+
|
| 12 |
+
from qai_hub_models.datasets.nyuv2 import NYUV2Dataset
|
| 13 |
+
from qai_hub_models.models._shared.depth_estimation.depth_evaluator import (
|
| 14 |
+
DepthEvaluator,
|
| 15 |
+
)
|
| 16 |
+
from qai_hub_models.utils.base_dataset import BaseDataset
|
| 17 |
+
from qai_hub_models.utils.base_evaluator import BaseEvaluator
|
| 18 |
+
from qai_hub_models.utils.base_model import BaseModel
|
| 19 |
+
from qai_hub_models.utils.input_spec import (
|
| 20 |
+
ColorFormat,
|
| 21 |
+
ImageMetadata,
|
| 22 |
+
InputSpec,
|
| 23 |
+
IoType,
|
| 24 |
+
OutputSpec,
|
| 25 |
+
TensorSpec,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
MODEL_ID = "depth_pro"
|
| 29 |
+
MODEL_ASSET_VERSION = 1
|
| 30 |
+
DEFAULT_WEIGHTS = "apple/DepthPro-hf"
|
| 31 |
+
DEFAULT_INPUT_SIZE = 1536
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class DepthProDepthEvaluator(DepthEvaluator):
|
| 35 |
+
"""Adapts the shared depth δ1 evaluator to DepthPro's (depth, fov) tuple.
|
| 36 |
+
|
| 37 |
+
DepthPro's canonical inverse-depth head is what the shared evaluator
|
| 38 |
+
already scale/shift-aligns against NYUv2 ground truth, so the FoV output
|
| 39 |
+
is ignored here — focal-length calibration is only needed for absolute
|
| 40 |
+
metric alignment, which the δ1 metric is invariant to.
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
def add_batch(
|
| 44 |
+
self,
|
| 45 |
+
output: torch.Tensor | tuple[torch.Tensor, ...] | list[torch.Tensor],
|
| 46 |
+
gt: torch.Tensor,
|
| 47 |
+
) -> None:
|
| 48 |
+
if isinstance(output, (tuple, list)):
|
| 49 |
+
output = output[0]
|
| 50 |
+
if output.dim() == 3:
|
| 51 |
+
# depth arrives as (B, H, W); base evaluator expects (B, 1, H, W)
|
| 52 |
+
output = output.unsqueeze(1)
|
| 53 |
+
super().add_batch(output, gt)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class DepthPro(BaseModel):
|
| 57 |
+
"""Apple DepthPro monocular metric depth estimator, end-to-end.
|
| 58 |
+
|
| 59 |
+
Exposes two on-device outputs: canonical inverse depth at the network's
|
| 60 |
+
input resolution and a scalar horizontal field of view (degrees) per
|
| 61 |
+
image. Off-device post-processing (see ``DepthProApp``) converts these
|
| 62 |
+
into metric depth and a focal length in pixels, matching HuggingFace's
|
| 63 |
+
``DepthProImageProcessorFast.post_process_depth_estimation``.
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def __init__(self, model: torch.nn.Module) -> None:
|
| 67 |
+
super().__init__()
|
| 68 |
+
self.model = model.eval()
|
| 69 |
+
|
| 70 |
+
@classmethod
|
| 71 |
+
def from_pretrained(cls, ckpt: str = DEFAULT_WEIGHTS) -> Self:
|
| 72 |
+
net = DepthProForDepthEstimation.from_pretrained(ckpt)
|
| 73 |
+
return cls(net)
|
| 74 |
+
|
| 75 |
+
def forward(
|
| 76 |
+
self, image: torch.Tensor
|
| 77 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 78 |
+
"""Run DepthPro on `image`.
|
| 79 |
+
|
| 80 |
+
Parameters
|
| 81 |
+
----------
|
| 82 |
+
image
|
| 83 |
+
Shape ``[B, 3, 1536, 1536]`` RGB in ``[0, 1]``.
|
| 84 |
+
|
| 85 |
+
Returns
|
| 86 |
+
-------
|
| 87 |
+
predicted_depth
|
| 88 |
+
Shape ``[B, 1536, 1536]``. Canonical inverse depth (the raw head
|
| 89 |
+
output before FoV-based metric scaling and inversion).
|
| 90 |
+
field_of_view
|
| 91 |
+
Shape ``[B]``, horizontal field of view in degrees.
|
| 92 |
+
"""
|
| 93 |
+
# HF's DepthProImageProcessorFast rescales to [0, 1] then normalizes
|
| 94 |
+
# with mean=std=0.5, i.e. (image - 0.5) / 0.5 = 2 * image - 1.
|
| 95 |
+
pixel_values = image * 2.0 - 1.0
|
| 96 |
+
predicted_depth, field_of_view = self.model(
|
| 97 |
+
pixel_values, return_dict=False
|
| 98 |
+
)
|
| 99 |
+
return predicted_depth, field_of_view
|
| 100 |
+
|
| 101 |
+
def get_input_spec(
|
| 102 |
+
self,
|
| 103 |
+
batch_size: int = 1,
|
| 104 |
+
height: int = DEFAULT_INPUT_SIZE,
|
| 105 |
+
width: int = DEFAULT_INPUT_SIZE,
|
| 106 |
+
) -> InputSpec:
|
| 107 |
+
return {
|
| 108 |
+
"image": TensorSpec(
|
| 109 |
+
shape=(batch_size, 3, height, width),
|
| 110 |
+
dtype="float32",
|
| 111 |
+
io_type=IoType.IMAGE,
|
| 112 |
+
value_range=(0.0, 1.0),
|
| 113 |
+
image_metadata=ImageMetadata(color_format=ColorFormat.RGB),
|
| 114 |
+
apply_runtime_channel_reordering=True,
|
| 115 |
+
),
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
def get_output_spec(self) -> OutputSpec:
|
| 119 |
+
return {
|
| 120 |
+
"predicted_depth": TensorSpec(
|
| 121 |
+
io_type=IoType.TENSOR,
|
| 122 |
+
description=(
|
| 123 |
+
"Canonical inverse depth at the network's input "
|
| 124 |
+
"resolution. Invert and rescale by "
|
| 125 |
+
"width / focal_length_px for metric depth."
|
| 126 |
+
),
|
| 127 |
+
apply_runtime_channel_reordering=True,
|
| 128 |
+
),
|
| 129 |
+
"field_of_view": TensorSpec(
|
| 130 |
+
io_type=IoType.TENSOR,
|
| 131 |
+
description=(
|
| 132 |
+
"Horizontal field of view in degrees, one scalar per "
|
| 133 |
+
"image; used off-device to derive focal length."
|
| 134 |
+
),
|
| 135 |
+
),
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
def get_evaluator(self) -> BaseEvaluator:
|
| 139 |
+
return DepthProDepthEvaluator()
|
| 140 |
+
|
| 141 |
+
@classmethod
|
| 142 |
+
def get_eval_dataset_classes(cls) -> list[type[BaseDataset]]:
|
| 143 |
+
return [NYUV2Dataset]
|
| 144 |
+
|
| 145 |
+
def get_calibration_dataset_cls(self) -> type[BaseDataset]:
|
| 146 |
+
return NYUV2Dataset
|
requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
transformers==4.56.2
|
test.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ---------------------------------------------------------------------
|
| 2 |
+
# Copyright (c) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries.
|
| 3 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
# ---------------------------------------------------------------------
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from qai_hub_models.utils.asset_loaders import load_image
|
| 11 |
+
|
| 12 |
+
from .app import DepthProApp
|
| 13 |
+
from .demo import INPUT_IMAGE_ADDRESS
|
| 14 |
+
from .demo import main as demo_main
|
| 15 |
+
from .model import DepthPro
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_task() -> None:
|
| 19 |
+
"""Run torch DepthPro end-to-end on the sample fixture.
|
| 20 |
+
|
| 21 |
+
Sanity-checks that the pipeline resolves the HF weights, produces a
|
| 22 |
+
depth map at the network's native 1536x1536 unpadded to the original
|
| 23 |
+
input resolution, and yields a plausible field of view (roughly the
|
| 24 |
+
range Apple demos on natural imagery, 30-100 degrees).
|
| 25 |
+
"""
|
| 26 |
+
model = DepthPro.from_pretrained()
|
| 27 |
+
(_, _, height, width) = model.get_input_spec()["image"][0]
|
| 28 |
+
app = DepthProApp(model, height, width)
|
| 29 |
+
image = load_image(INPUT_IMAGE_ADDRESS)
|
| 30 |
+
prediction = app.estimate_depth(image)
|
| 31 |
+
|
| 32 |
+
assert prediction.depth.ndim == 2
|
| 33 |
+
assert prediction.depth.shape == (image.size[1], image.size[0])
|
| 34 |
+
assert np.all(np.isfinite(prediction.depth))
|
| 35 |
+
assert prediction.depth.min() > 0
|
| 36 |
+
|
| 37 |
+
assert 10.0 < prediction.field_of_view < 170.0
|
| 38 |
+
assert prediction.focal_length_px > 0
|
| 39 |
+
|
| 40 |
+
assert prediction.heatmap.size == image.size
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def test_demo() -> None:
|
| 44 |
+
demo_main(is_test=True)
|