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.
Files changed (43) hide show
  1. {depth2normal-2.0.0 → depth2normal-2.0.1}/PKG-INFO +9 -7
  2. {depth2normal-2.0.0 → depth2normal-2.0.1}/README.md +6 -5
  3. {depth2normal-2.0.0 → depth2normal-2.0.1}/pyproject.toml +3 -2
  4. {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/__init__.py +1 -1
  5. {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_cli.py +4 -1
  6. {depth2normal-2.0.0 → depth2normal-2.0.1}/uv.lock +1 -1
  7. depth2normal-2.0.1/web/_headers +18 -0
  8. {depth2normal-2.0.0 → depth2normal-2.0.1}/web/app.js +18 -8
  9. depth2normal-2.0.1/web/assets/og.png +0 -0
  10. depth2normal-2.0.1/web/index.html +215 -0
  11. depth2normal-2.0.1/web/style.css +601 -0
  12. depth2normal-2.0.0/web/index.html +0 -101
  13. depth2normal-2.0.0/web/style.css +0 -253
  14. {depth2normal-2.0.0 → depth2normal-2.0.1}/.github/workflows/ci.yml +0 -0
  15. {depth2normal-2.0.0 → depth2normal-2.0.1}/.github/workflows/release.yml +0 -0
  16. {depth2normal-2.0.0 → depth2normal-2.0.1}/.gitignore +0 -0
  17. {depth2normal-2.0.0 → depth2normal-2.0.1}/LICENSE +0 -0
  18. {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/CREDITS.md +0 -0
  19. {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/depth.png +0 -0
  20. {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/hero.webp +0 -0
  21. {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/normal.png +0 -0
  22. {depth2normal-2.0.0 → depth2normal-2.0.1}/assets/pipeline.webp +0 -0
  23. {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/__main__.py +0 -0
  24. {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/cli.py +0 -0
  25. {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/converter.py +0 -0
  26. {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/estimate.py +0 -0
  27. {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/filters.py +0 -0
  28. {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/onnx_export.py +0 -0
  29. {depth2normal-2.0.0 → depth2normal-2.0.1}/src/depth2normal/py.typed +0 -0
  30. {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/__init__.py +0 -0
  31. {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_converter.py +0 -0
  32. {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_estimate.py +0 -0
  33. {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_filters.py +0 -0
  34. {depth2normal-2.0.0 → depth2normal-2.0.1}/tests/test_onnx_export.py +0 -0
  35. {depth2normal-2.0.0 → depth2normal-2.0.1}/tools/benchmark.py +0 -0
  36. {depth2normal-2.0.0 → depth2normal-2.0.1}/tools/build_web_models.py +0 -0
  37. {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/gaussian-0.5.onnx +0 -0
  38. {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/gaussian-1.onnx +0 -0
  39. {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/gaussian-2.onnx +0 -0
  40. {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/gaussian-3.onnx +0 -0
  41. {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/scharr.onnx +0 -0
  42. {depth2normal-2.0.0 → depth2normal-2.0.1}/web/models/sobel.onnx +0 -0
  43. {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.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://github.com/cobanov/depth2normal
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-71-8c8cff?labelColor=1a1a1a">
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-71-8c8cff?labelColor=1a1a1a">
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.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://github.com/cobanov/depth2normal"
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"
@@ -11,7 +11,7 @@ from depth2normal.converter import (
11
11
  from depth2normal.estimate import estimate_depth
12
12
  from depth2normal.onnx_export import build_model, export_onnx
13
13
 
14
- __version__ = "2.0.0"
14
+ __version__ = "2.0.1"
15
15
  __all__ = [
16
16
  "METHODS",
17
17
  "RANGES",
@@ -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 "2.0.0" in result.output
67
+ assert version("depth2normal") in result.output
@@ -31,7 +31,7 @@ wheels = [
31
31
 
32
32
  [[package]]
33
33
  name = "depth2normal"
34
- version = "2.0.0"
34
+ version = "2.0.1"
35
35
  source = { editable = "." }
36
36
  dependencies = [
37
37
  { name = "click" },
@@ -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
- status(`${width} x ${height}, converted in ${elapsed} ms`);
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(`Downloading the depth model: ${Math.round((100 * received) / total)}%`);
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 (estimated)";
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>