depth2normal 2.0.0__tar.gz → 2.0.1__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.
- {depth2normal-2.0.0 → depth2normal-2.0.1}/PKG-INFO +9 -7
- {depth2normal-2.0.0 → depth2normal-2.0.1}/README.md +6 -5
- {depth2normal-2.0.0 → depth2normal-2.0.1}/pyproject.toml +3 -2
- {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/__init__.py +1 -1
- {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_cli.py +4 -1
- {depth2normal-2.0.0 → depth2normal-2.0.1}/uv.lock +1 -1
- depth2normal-2.0.1/web/_headers +18 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/web/app.js +18 -8
- depth2normal-2.0.1/web/assets/og.png +0 -0
- depth2normal-2.0.1/web/index.html +215 -0
- depth2normal-2.0.1/web/style.css +601 -0
- depth2normal-2.0.0/web/index.html +0 -101
- depth2normal-2.0.0/web/style.css +0 -253
- {depth2normal-2.0.0 → depth2normal-2.0.1}/.github/workflows/ci.yml +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/.github/workflows/release.yml +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/.gitignore +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/LICENSE +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/CREDITS.md +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/depth.png +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/hero.webp +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/normal.png +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/pipeline.webp +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/__main__.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/cli.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/converter.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/estimate.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/filters.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/onnx_export.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/py.typed +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/__init__.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_converter.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_estimate.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_filters.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_onnx_export.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/tools/benchmark.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/tools/build_web_models.py +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/gaussian-0.5.onnx +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/gaussian-1.onnx +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/gaussian-2.onnx +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/gaussian-3.onnx +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/scharr.onnx +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/sobel.onnx +0 -0
- {depth2normal-2.0.0 → depth2normal-2.0.1}/web/sample-depth.png +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: depth2normal
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: Convert depth maps to normal maps, from a photo if you have no depth map
|
|
5
|
-
Project-URL: Homepage, https://
|
|
5
|
+
Project-URL: Homepage, https://depth2normal.cobanov.dev
|
|
6
6
|
Project-URL: Repository, https://github.com/cobanov/depth2normal
|
|
7
7
|
Project-URL: Issues, https://github.com/cobanov/depth2normal/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/cobanov/depth2normal/releases
|
|
8
9
|
Author: Mert Cobanov
|
|
9
10
|
License-Expression: MIT
|
|
10
11
|
License-File: LICENSE
|
|
@@ -37,7 +38,7 @@ Requires-Dist: onnx; extra == 'onnx'
|
|
|
37
38
|
Description-Content-Type: text/markdown
|
|
38
39
|
|
|
39
40
|
<p align="center">
|
|
40
|
-
<img src="assets/hero.webp" alt="A Mars photograph next to the normal map depth2normal produced from it" width="700">
|
|
41
|
+
<img src="https://raw.githubusercontent.com/cobanov/depth2normal/main/assets/hero.webp" alt="A Mars photograph next to the normal map depth2normal produced from it" width="700">
|
|
41
42
|
</p>
|
|
42
43
|
|
|
43
44
|
<p align="center">
|
|
@@ -48,9 +49,9 @@ Description-Content-Type: text/markdown
|
|
|
48
49
|
<p align="center">
|
|
49
50
|
<a href="https://pypi.org/project/depth2normal/"><img alt="pypi" src="https://img.shields.io/pypi/v/depth2normal?color=8c8cff&labelColor=1a1a1a"></a>
|
|
50
51
|
<a href="https://github.com/cobanov/depth2normal/actions/workflows/ci.yml"><img alt="ci" src="https://img.shields.io/github/actions/workflow/status/cobanov/depth2normal/ci.yml?branch=main&color=8c8cff&labelColor=1a1a1a"></a>
|
|
51
|
-
<img alt="tests" src="https://img.shields.io/badge/tests-
|
|
52
|
+
<img alt="tests" src="https://img.shields.io/badge/tests-87-8c8cff?labelColor=1a1a1a">
|
|
52
53
|
<img alt="python" src="https://img.shields.io/badge/python-3.10%2B-8c8cff?labelColor=1a1a1a">
|
|
53
|
-
<a href="LICENSE"><img alt="licence" src="https://img.shields.io/badge/licence-MIT-8c8cff?labelColor=1a1a1a"></a>
|
|
54
|
+
<a href="https://github.com/cobanov/depth2normal/blob/main/LICENSE"><img alt="licence" src="https://img.shields.io/badge/licence-MIT-8c8cff?labelColor=1a1a1a"></a>
|
|
54
55
|
</p>
|
|
55
56
|
|
|
56
57
|
---
|
|
@@ -123,7 +124,7 @@ depth2normal.export_onnx("depth2normal.onnx") # needs [onnx]
|
|
|
123
124
|
## From a photo
|
|
124
125
|
|
|
125
126
|
<p align="center">
|
|
126
|
-
<img src="assets/pipeline.webp" alt="A photograph, the depth map estimated from it, and the resulting normal map" width="700">
|
|
127
|
+
<img src="https://raw.githubusercontent.com/cobanov/depth2normal/main/assets/pipeline.webp" alt="A photograph, the depth map estimated from it, and the resulting normal map" width="700">
|
|
127
128
|
</p>
|
|
128
129
|
|
|
129
130
|
`--estimate` runs [Depth Anything V2 Small](https://huggingface.co/onnx-community/depth-anything-v2-small)
|
|
@@ -263,9 +264,10 @@ uv run pytest
|
|
|
263
264
|
uv run ruff check .
|
|
264
265
|
uv run ruff format --check .
|
|
265
266
|
uv run python tools/build_web_models.py
|
|
267
|
+
wrangler pages deploy web --project-name depth2normal # the demo
|
|
266
268
|
```
|
|
267
269
|
|
|
268
270
|
## Licence
|
|
269
271
|
|
|
270
272
|
MIT. The depth model is Apache-2.0 and is downloaded, not vendored. Image
|
|
271
|
-
credits are in [assets/CREDITS.md](assets/CREDITS.md).
|
|
273
|
+
credits are in [assets/CREDITS.md](https://github.com/cobanov/depth2normal/blob/main/assets/CREDITS.md).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="assets/hero.webp" alt="A Mars photograph next to the normal map depth2normal produced from it" width="700">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/cobanov/depth2normal/main/assets/hero.webp" alt="A Mars photograph next to the normal map depth2normal produced from it" width="700">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
<p align="center">
|
|
11
11
|
<a href="https://pypi.org/project/depth2normal/"><img alt="pypi" src="https://img.shields.io/pypi/v/depth2normal?color=8c8cff&labelColor=1a1a1a"></a>
|
|
12
12
|
<a href="https://github.com/cobanov/depth2normal/actions/workflows/ci.yml"><img alt="ci" src="https://img.shields.io/github/actions/workflow/status/cobanov/depth2normal/ci.yml?branch=main&color=8c8cff&labelColor=1a1a1a"></a>
|
|
13
|
-
<img alt="tests" src="https://img.shields.io/badge/tests-
|
|
13
|
+
<img alt="tests" src="https://img.shields.io/badge/tests-87-8c8cff?labelColor=1a1a1a">
|
|
14
14
|
<img alt="python" src="https://img.shields.io/badge/python-3.10%2B-8c8cff?labelColor=1a1a1a">
|
|
15
|
-
<a href="LICENSE"><img alt="licence" src="https://img.shields.io/badge/licence-MIT-8c8cff?labelColor=1a1a1a"></a>
|
|
15
|
+
<a href="https://github.com/cobanov/depth2normal/blob/main/LICENSE"><img alt="licence" src="https://img.shields.io/badge/licence-MIT-8c8cff?labelColor=1a1a1a"></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
---
|
|
@@ -85,7 +85,7 @@ depth2normal.export_onnx("depth2normal.onnx") # needs [onnx]
|
|
|
85
85
|
## From a photo
|
|
86
86
|
|
|
87
87
|
<p align="center">
|
|
88
|
-
<img src="assets/pipeline.webp" alt="A photograph, the depth map estimated from it, and the resulting normal map" width="700">
|
|
88
|
+
<img src="https://raw.githubusercontent.com/cobanov/depth2normal/main/assets/pipeline.webp" alt="A photograph, the depth map estimated from it, and the resulting normal map" width="700">
|
|
89
89
|
</p>
|
|
90
90
|
|
|
91
91
|
`--estimate` runs [Depth Anything V2 Small](https://huggingface.co/onnx-community/depth-anything-v2-small)
|
|
@@ -225,9 +225,10 @@ uv run pytest
|
|
|
225
225
|
uv run ruff check .
|
|
226
226
|
uv run ruff format --check .
|
|
227
227
|
uv run python tools/build_web_models.py
|
|
228
|
+
wrangler pages deploy web --project-name depth2normal # the demo
|
|
228
229
|
```
|
|
229
230
|
|
|
230
231
|
## Licence
|
|
231
232
|
|
|
232
233
|
MIT. The depth model is Apache-2.0 and is downloaded, not vendored. Image
|
|
233
|
-
credits are in [assets/CREDITS.md](assets/CREDITS.md).
|
|
234
|
+
credits are in [assets/CREDITS.md](https://github.com/cobanov/depth2normal/blob/main/assets/CREDITS.md).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "depth2normal"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.1"
|
|
8
8
|
description = "Convert depth maps to normal maps, from a photo if you have no depth map"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -53,9 +53,10 @@ all = ["onnx", "onnxruntime>=1.20; python_version >= '3.11'"]
|
|
|
53
53
|
depth2normal = "depth2normal.cli:cli"
|
|
54
54
|
|
|
55
55
|
[project.urls]
|
|
56
|
-
Homepage = "https://
|
|
56
|
+
Homepage = "https://depth2normal.cobanov.dev"
|
|
57
57
|
Repository = "https://github.com/cobanov/depth2normal"
|
|
58
58
|
Issues = "https://github.com/cobanov/depth2normal/issues"
|
|
59
|
+
Changelog = "https://github.com/cobanov/depth2normal/releases"
|
|
59
60
|
|
|
60
61
|
[tool.ruff]
|
|
61
62
|
target-version = "py310"
|
|
@@ -59,6 +59,9 @@ def test_export_writes_a_model(tmp_path):
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
def test_version_flag():
|
|
62
|
+
"""The CLI reports the installed version, whatever it is."""
|
|
63
|
+
from importlib.metadata import version
|
|
64
|
+
|
|
62
65
|
result = CliRunner().invoke(cli, ["--version"])
|
|
63
66
|
assert result.exit_code == 0
|
|
64
|
-
assert "
|
|
67
|
+
assert version("depth2normal") in result.output
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Cloudflare Pages serves static assets with `max-age=14400` and ignores any
|
|
2
|
+
# Cache-Control set here, which was measured rather than assumed: the CSP below
|
|
3
|
+
# arrives on every response, the cache header does not. So the lever for
|
|
4
|
+
# style.css and app.js is the version query on their links in index.html, which
|
|
5
|
+
# is bumped whenever either file changes. index.html itself is served with
|
|
6
|
+
# max-age=0, so a bumped query is picked up on the next visit.
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
X-Content-Type-Options: nosniff
|
|
10
|
+
Referrer-Policy: no-referrer
|
|
11
|
+
X-Frame-Options: SAMEORIGIN
|
|
12
|
+
Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=(), usb=()
|
|
13
|
+
# Everything the page runs is either its own or from one of three hosts:
|
|
14
|
+
# jsdelivr for ONNX Runtime and its WebAssembly, Google Fonts for the two
|
|
15
|
+
# typefaces, and Hugging Face for the depth model, fetched only when the
|
|
16
|
+
# visitor asks for depth estimation. Nothing is ever sent anywhere: there is
|
|
17
|
+
# no form action and no endpoint to post an image to.
|
|
18
|
+
Content-Security-Policy: default-src 'self'; script-src 'self' 'wasm-unsafe-eval' https://cdn.jsdelivr.net; worker-src 'self' blob:; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; img-src 'self' blob: data:; connect-src 'self' https://cdn.jsdelivr.net https://huggingface.co https://*.hf.co; form-action 'none'; frame-ancestors 'self'; base-uri 'none'; object-src 'none'
|
|
@@ -22,7 +22,9 @@ const ui = {
|
|
|
22
22
|
sample: el("sample"),
|
|
23
23
|
source: el("source"),
|
|
24
24
|
sourceLabel: el("source-label"),
|
|
25
|
+
sourceMeta: el("source-meta"),
|
|
25
26
|
output: el("output"),
|
|
27
|
+
outputMeta: el("output-meta"),
|
|
26
28
|
controls: el("controls"),
|
|
27
29
|
method: el("method"),
|
|
28
30
|
strength: el("strength"),
|
|
@@ -39,13 +41,14 @@ const conversionSessions = new Map();
|
|
|
39
41
|
let depthSession = null;
|
|
40
42
|
let state = { depth: null, photo: null };
|
|
41
43
|
|
|
42
|
-
const status = (text) => {
|
|
44
|
+
const status = (text, busy = false) => {
|
|
43
45
|
ui.status.textContent = text;
|
|
46
|
+
ui.status.dataset.busy = String(busy);
|
|
44
47
|
};
|
|
45
48
|
|
|
46
49
|
async function conversionSession(name) {
|
|
47
50
|
if (!conversionSessions.has(name)) {
|
|
48
|
-
status(`Loading ${name}.onnx
|
|
51
|
+
status(`Loading ${name}.onnx`, true);
|
|
49
52
|
conversionSessions.set(
|
|
50
53
|
name,
|
|
51
54
|
await ort.InferenceSession.create(`models/${name}.onnx`),
|
|
@@ -166,13 +169,15 @@ async function render() {
|
|
|
166
169
|
image.data.set([rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2], 255], i * 4);
|
|
167
170
|
}
|
|
168
171
|
paint(ui.output, image);
|
|
169
|
-
|
|
172
|
+
ui.sourceMeta.textContent = `${width} \u00d7 ${height}`;
|
|
173
|
+
ui.outputMeta.textContent = `${elapsed} ms`;
|
|
174
|
+
status("");
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
async function loadDepthSession() {
|
|
173
178
|
if (depthSession) return depthSession;
|
|
174
179
|
|
|
175
|
-
status("Downloading the depth model (27 MB, once)");
|
|
180
|
+
status("Downloading the depth model (27 MB, once)", true);
|
|
176
181
|
const response = await fetch(DEPTH_MODEL_URL);
|
|
177
182
|
if (!response.ok) throw new Error(`model download failed: ${response.status}`);
|
|
178
183
|
|
|
@@ -185,7 +190,10 @@ async function loadDepthSession() {
|
|
|
185
190
|
if (done) break;
|
|
186
191
|
chunks.push(value);
|
|
187
192
|
received += value.length;
|
|
188
|
-
status(
|
|
193
|
+
status(
|
|
194
|
+
`Downloading the depth model: ${Math.round((100 * received) / total)}%`,
|
|
195
|
+
true,
|
|
196
|
+
);
|
|
189
197
|
}
|
|
190
198
|
|
|
191
199
|
const bytes = new Uint8Array(received);
|
|
@@ -194,7 +202,7 @@ async function loadDepthSession() {
|
|
|
194
202
|
bytes.set(chunk, at);
|
|
195
203
|
at += chunk.length;
|
|
196
204
|
}
|
|
197
|
-
status("Starting the depth model");
|
|
205
|
+
status("Starting the depth model", true);
|
|
198
206
|
depthSession = await ort.InferenceSession.create(bytes);
|
|
199
207
|
return depthSession;
|
|
200
208
|
}
|
|
@@ -231,7 +239,7 @@ async function estimate() {
|
|
|
231
239
|
}
|
|
232
240
|
}
|
|
233
241
|
|
|
234
|
-
status("Estimating depth");
|
|
242
|
+
status("Estimating depth", true);
|
|
235
243
|
const started = performance.now();
|
|
236
244
|
const results = await session.run({
|
|
237
245
|
pixel_values: new ort.Tensor("float32", input, [1, 3, height, width]),
|
|
@@ -259,7 +267,7 @@ async function estimate() {
|
|
|
259
267
|
target.drawImage(source, 0, 0, scaled.width, scaled.height);
|
|
260
268
|
|
|
261
269
|
state.depth = toDepth(pixelsOf(scaled));
|
|
262
|
-
ui.sourceLabel.textContent = "Depth
|
|
270
|
+
ui.sourceLabel.textContent = "Depth, estimated";
|
|
263
271
|
await render();
|
|
264
272
|
status(`Depth estimated in ${elapsed} ms`);
|
|
265
273
|
} catch (error) {
|
|
@@ -334,5 +342,7 @@ ui.reset.addEventListener("click", () => {
|
|
|
334
342
|
ui.stage.classList.add("empty");
|
|
335
343
|
ui.controls.hidden = true;
|
|
336
344
|
ui.file.value = "";
|
|
345
|
+
ui.sourceMeta.textContent = "";
|
|
346
|
+
ui.outputMeta.textContent = "";
|
|
337
347
|
status("");
|
|
338
348
|
});
|
|
Binary file
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
|
+
<title>depth2normal: a depth map goes in, a normal map comes out</title>
|
|
7
|
+
<meta
|
|
8
|
+
name="description"
|
|
9
|
+
content="Turn a depth map into a normal map in your browser, or estimate the depth of a photograph first. Runs on a 1.7 KB ONNX graph in your own tab; nothing is uploaded."
|
|
10
|
+
/>
|
|
11
|
+
<meta name="theme-color" content="#0c0c0e" />
|
|
12
|
+
<meta name="robots" content="index, follow, max-image-preview:large" />
|
|
13
|
+
<link rel="canonical" href="https://depth2normal.cobanov.dev/" />
|
|
14
|
+
|
|
15
|
+
<meta property="og:type" content="website" />
|
|
16
|
+
<meta property="og:site_name" content="depth2normal" />
|
|
17
|
+
<meta property="og:title" content="depth2normal: a depth map goes in, a normal map comes out" />
|
|
18
|
+
<meta
|
|
19
|
+
property="og:description"
|
|
20
|
+
content="Drop a depth map, get a normal map. No depth map? It estimates one from a photograph. Everything runs in your browser, nothing is uploaded."
|
|
21
|
+
/>
|
|
22
|
+
<meta property="og:url" content="https://depth2normal.cobanov.dev/" />
|
|
23
|
+
<meta property="og:locale" content="en_US" />
|
|
24
|
+
<meta property="og:image" content="https://depth2normal.cobanov.dev/assets/og.png" />
|
|
25
|
+
<meta property="og:image:type" content="image/png" />
|
|
26
|
+
<meta property="og:image:width" content="1200" />
|
|
27
|
+
<meta property="og:image:height" content="630" />
|
|
28
|
+
<meta property="og:image:alt" content="A photograph of Mars beside the normal map depth2normal produced from it" />
|
|
29
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
30
|
+
<meta name="twitter:site" content="@mertcobanov" />
|
|
31
|
+
<meta name="twitter:creator" content="@mertcobanov" />
|
|
32
|
+
<meta name="twitter:title" content="depth2normal: a depth map goes in, a normal map comes out" />
|
|
33
|
+
<meta
|
|
34
|
+
name="twitter:description"
|
|
35
|
+
content="Drop a depth map, get a normal map. No depth map? It estimates one from a photograph. Everything runs in your browser."
|
|
36
|
+
/>
|
|
37
|
+
<meta name="twitter:image" content="https://depth2normal.cobanov.dev/assets/og.png" />
|
|
38
|
+
|
|
39
|
+
<!-- The favicon is the page's own subject: a normal map of a sphere. -->
|
|
40
|
+
<link
|
|
41
|
+
rel="icon"
|
|
42
|
+
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3CradialGradient id='n' cx='30%25' cy='28%25'%3E%3Cstop offset='0' stop-color='%23d7ffbe'/%3E%3Cstop offset='.46' stop-color='%238c8cff'/%3E%3Cstop offset='.78' stop-color='%235b5bd6'/%3E%3Cstop offset='1' stop-color='%233a3aa8'/%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='8' fill='%230c0c0e'/%3E%3Ccircle cx='16' cy='16' r='10' fill='url(%23n)'/%3E%3C/svg%3E"
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
46
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
47
|
+
<link
|
|
48
|
+
href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
49
|
+
rel="stylesheet"
|
|
50
|
+
/>
|
|
51
|
+
<!-- Bump ?v= when either file changes: Pages caches assets for four
|
|
52
|
+
hours and ignores the Cache-Control in _headers. -->
|
|
53
|
+
<link rel="stylesheet" href="style.css?v=2" />
|
|
54
|
+
|
|
55
|
+
<script type="application/ld+json">
|
|
56
|
+
{
|
|
57
|
+
"@context": "https://schema.org",
|
|
58
|
+
"@type": "SoftwareApplication",
|
|
59
|
+
"name": "depth2normal",
|
|
60
|
+
"url": "https://depth2normal.cobanov.dev/",
|
|
61
|
+
"applicationCategory": "MultimediaApplication",
|
|
62
|
+
"operatingSystem": "Any browser",
|
|
63
|
+
"description": "Convert a depth map to a normal map in the browser, or estimate depth from a photograph first. Nothing is uploaded.",
|
|
64
|
+
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
|
|
65
|
+
"license": "https://opensource.org/licenses/MIT"
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
</head>
|
|
69
|
+
|
|
70
|
+
<body>
|
|
71
|
+
<a class="skip-link" href="#main">Skip to the tool</a>
|
|
72
|
+
|
|
73
|
+
<header class="masthead">
|
|
74
|
+
<div class="shell masthead-inner">
|
|
75
|
+
<a class="wordmark" href="/">
|
|
76
|
+
<span class="wordmark-mark" aria-hidden="true"></span>
|
|
77
|
+
depth2normal
|
|
78
|
+
</a>
|
|
79
|
+
<nav>
|
|
80
|
+
<a href="https://github.com/cobanov/depth2normal">GitHub</a>
|
|
81
|
+
<a href="https://pypi.org/project/depth2normal/">PyPI</a>
|
|
82
|
+
<code class="install">pip install depth2normal</code>
|
|
83
|
+
</nav>
|
|
84
|
+
</div>
|
|
85
|
+
</header>
|
|
86
|
+
|
|
87
|
+
<main id="main">
|
|
88
|
+
<section class="hero shell">
|
|
89
|
+
<h1>A depth map goes in, a normal map comes out.</h1>
|
|
90
|
+
<p class="lead">
|
|
91
|
+
The graph doing the work is <strong>1.7 KB of ONNX</strong> and it runs
|
|
92
|
+
in this tab, so your image never leaves the browser. No depth map?
|
|
93
|
+
Drop a photograph and it will estimate one first.
|
|
94
|
+
</p>
|
|
95
|
+
</section>
|
|
96
|
+
|
|
97
|
+
<section class="stage shell empty" id="stage">
|
|
98
|
+
<div class="dropzone" id="dropzone">
|
|
99
|
+
<h2>Drop a depth map here</h2>
|
|
100
|
+
<p>
|
|
101
|
+
or <button type="button" id="pick" class="link-button">choose a file</button>,
|
|
102
|
+
or <button type="button" id="sample" class="link-button">use the sample</button>
|
|
103
|
+
</p>
|
|
104
|
+
<p>A photograph works too: its depth can be estimated in the tab.</p>
|
|
105
|
+
<input id="file" type="file" accept="image/*" hidden />
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<figure class="panel">
|
|
109
|
+
<figcaption>
|
|
110
|
+
<span id="source-label">Depth</span>
|
|
111
|
+
<span class="value mono" id="source-meta"></span>
|
|
112
|
+
</figcaption>
|
|
113
|
+
<canvas id="source"></canvas>
|
|
114
|
+
</figure>
|
|
115
|
+
|
|
116
|
+
<figure class="panel">
|
|
117
|
+
<figcaption>
|
|
118
|
+
<span>Normal</span>
|
|
119
|
+
<span class="value mono" id="output-meta"></span>
|
|
120
|
+
</figcaption>
|
|
121
|
+
<canvas id="output"></canvas>
|
|
122
|
+
</figure>
|
|
123
|
+
</section>
|
|
124
|
+
|
|
125
|
+
<section class="controls shell" id="controls" hidden>
|
|
126
|
+
<div class="field">
|
|
127
|
+
<span class="label">Method</span>
|
|
128
|
+
<select id="method">
|
|
129
|
+
<option value="gaussian-0.5">gaussian, sigma 0.5</option>
|
|
130
|
+
<option value="gaussian-1">gaussian, sigma 1</option>
|
|
131
|
+
<option value="gaussian-2" selected>gaussian, sigma 2</option>
|
|
132
|
+
<option value="gaussian-3">gaussian, sigma 3</option>
|
|
133
|
+
<option value="sobel">sobel</option>
|
|
134
|
+
<option value="scharr">scharr</option>
|
|
135
|
+
</select>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div class="field">
|
|
139
|
+
<label class="label" for="strength">
|
|
140
|
+
Strength <output id="strength-value">1.0</output>
|
|
141
|
+
</label>
|
|
142
|
+
<input id="strength" type="range" min="0.1" max="10" step="0.1" value="1" />
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div class="field">
|
|
146
|
+
<label class="label" for="range">Depth range</label>
|
|
147
|
+
<select id="range">
|
|
148
|
+
<option value="auto">auto</option>
|
|
149
|
+
<option value="minmax">minmax</option>
|
|
150
|
+
<option value="raw">raw</option>
|
|
151
|
+
</select>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div class="field">
|
|
155
|
+
<span class="label">Direction</span>
|
|
156
|
+
<label class="toggle">
|
|
157
|
+
<input id="invert" type="checkbox" />
|
|
158
|
+
Invert, if bright means far
|
|
159
|
+
</label>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="actions">
|
|
163
|
+
<button type="button" id="download" class="primary">Download PNG</button>
|
|
164
|
+
<button type="button" id="estimate">Estimate depth from photo</button>
|
|
165
|
+
<button type="button" id="reset" class="quiet">Start over</button>
|
|
166
|
+
</div>
|
|
167
|
+
</section>
|
|
168
|
+
|
|
169
|
+
<p class="status shell" id="status" role="status"></p>
|
|
170
|
+
|
|
171
|
+
<section class="notes shell">
|
|
172
|
+
<article class="note">
|
|
173
|
+
<h2>What it does</h2>
|
|
174
|
+
<p>
|
|
175
|
+
Two gradients, one vector per pixel, normalised to unit length. The
|
|
176
|
+
depth is rescaled to a fixed range first, so an 8-bit and a 16-bit
|
|
177
|
+
copy of one map give the same normals.
|
|
178
|
+
</p>
|
|
179
|
+
</article>
|
|
180
|
+
<article class="note">
|
|
181
|
+
<h2>Rings in the output</h2>
|
|
182
|
+
<p>
|
|
183
|
+
Those are the source depth map's 8-bit steps, amplified by the
|
|
184
|
+
derivative. Raise sigma until they go, or feed a 16-bit depth map,
|
|
185
|
+
where the steps are 256 times smaller.
|
|
186
|
+
</p>
|
|
187
|
+
</article>
|
|
188
|
+
<article class="note">
|
|
189
|
+
<h2>Same thing offline</h2>
|
|
190
|
+
<p>
|
|
191
|
+
<code>pip install depth2normal</code> gives you the CLI, the ONNX
|
|
192
|
+
export and camera intrinsics for metric depth. The models on this
|
|
193
|
+
page come from <code>depth2normal export</code>.
|
|
194
|
+
</p>
|
|
195
|
+
</article>
|
|
196
|
+
</section>
|
|
197
|
+
</main>
|
|
198
|
+
|
|
199
|
+
<footer>
|
|
200
|
+
<div class="shell">
|
|
201
|
+
<p>
|
|
202
|
+
Depth estimation, when you ask for it, fetches Depth Anything V2 Small
|
|
203
|
+
(27 MB, Apache-2.0) and runs it here as well. Nothing is uploaded
|
|
204
|
+
either way.
|
|
205
|
+
</p>
|
|
206
|
+
<p>
|
|
207
|
+
<a href="https://github.com/cobanov/depth2normal">MIT</a> ·
|
|
208
|
+
<a href="https://cobanov.dev">cobanov.dev</a>
|
|
209
|
+
</p>
|
|
210
|
+
</div>
|
|
211
|
+
</footer>
|
|
212
|
+
|
|
213
|
+
<script type="module" src="app.js?v=2"></script>
|
|
214
|
+
</body>
|
|
215
|
+
</html>
|