Spaces:
Running
Running
Download processor/web/static/js/depth-renderer.js from Stouching-Robotics/DataFlowing: direct link, hf CLI and curl.
- Browser
- Download file 10 kB
-
https://huggingface.co/spaces/Stouching-Robotics/DataFlowing/resolve/main/processor/web/static/js/depth-renderer.js
- Command line
-
hf download hf://spaces/Stouching-Robotics/DataFlowing/processor/web/static/js/depth-renderer.js
-
curl -L -o depth-renderer.js https://huggingface.co/spaces/Stouching-Robotics/DataFlowing/resolve/main/processor/web/static/js/depth-renderer.js
10 kB
| /* Frontend-only canonical depth renderer. | |
| * | |
| * The server sends uint16 little-endian depth codes. This module performs | |
| * only the display conversion: code -> q01/q99-clamped 8-bit index -> Turbo | |
| * LUT -> RGBA (Google Turbo = Rerun's default depth colormap, blue→red ramp). | |
| * No colorized depth frame is uploaded, persisted, or returned by the API. | |
| */ | |
| (function () { | |
| const DEPTH_QMAX = 4095; | |
| let lut = null; | |
| let lutPromise = null; | |
| const VERTEX_SHADER = `#version 300 es | |
| in vec2 a_position; | |
| out vec2 v_uv; | |
| void main() { | |
| v_uv = a_position * 0.5 + 0.5; | |
| gl_Position = vec4(a_position, 0.0, 1.0); | |
| } | |
| `; | |
| const FRAGMENT_SHADER = `#version 300 es | |
| precision highp float; | |
| precision highp usampler2D; | |
| in vec2 v_uv; | |
| uniform usampler2D u_depth; | |
| uniform sampler2D u_lut; | |
| uniform float u_q01; | |
| uniform float u_q99; | |
| out vec4 out_color; | |
| void main() { | |
| // Typed-array texture rows start at the top for this presentation | |
| // path; flip only the texture lookup, never the stored samples. | |
| uint code = texture(u_depth, vec2(v_uv.x, 1.0 - v_uv.y)).r; | |
| // Rerun-style adaptive range: clamp to [q01, q99] then map to LUT. | |
| float span = max(u_q99 - u_q01, 1.0); | |
| float clamped = clamp(float(code), u_q01, u_q99); | |
| uint c8 = uint((clamped - u_q01) * 255.0 / span + 0.5); | |
| out_color = texelFetch(u_lut, ivec2(int(c8), 0), 0); | |
| } | |
| `; | |
| function compileShader(gl, type, source) { | |
| const shader = gl.createShader(type); | |
| gl.shaderSource(shader, source); | |
| gl.compileShader(shader); | |
| if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
| gl.deleteShader(shader); | |
| return null; | |
| } | |
| return shader; | |
| } | |
| function createGpuState(canvas) { | |
| const gl = canvas.getContext('webgl2', { | |
| alpha: false, antialias: false, depth: false, | |
| preserveDrawingBuffer: false, | |
| }); | |
| if (!gl) return null; | |
| const vertex = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER); | |
| const fragment = compileShader(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER); | |
| if (!vertex || !fragment) return null; | |
| const program = gl.createProgram(); | |
| gl.attachShader(program, vertex); | |
| gl.attachShader(program, fragment); | |
| gl.linkProgram(program); | |
| if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return null; | |
| const buffer = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ | |
| -1, -1, 1, -1, -1, 1, 1, 1, | |
| ]), gl.STATIC_DRAW); | |
| const depthTexture = gl.createTexture(); | |
| const lutTexture = gl.createTexture(); | |
| const position = gl.getAttribLocation(program, 'a_position'); | |
| const state = { gl, program, buffer, depthTexture, lutTexture, | |
| position, depthWidth: 0, depthHeight: 0, depthAllocated: false, | |
| q01Loc: gl.getUniformLocation(program, 'u_q01'), | |
| q99Loc: gl.getUniformLocation(program, 'u_q99') }; | |
| gl.useProgram(program); | |
| gl.uniform1i(gl.getUniformLocation(program, 'u_depth'), 0); | |
| gl.uniform1i(gl.getUniformLocation(program, 'u_lut'), 1); | |
| gl.bindTexture(gl.TEXTURE_2D, lutTexture); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| // OpenCV gives BGR; upload the equivalent RGBA display order. | |
| const lutPixels = new Uint8Array(256 * 4); | |
| lut.forEach((bgr, i) => { | |
| lutPixels[i * 4] = bgr[2]; | |
| lutPixels[i * 4 + 1] = bgr[1]; | |
| lutPixels[i * 4 + 2] = bgr[0]; | |
| lutPixels[i * 4 + 3] = 255; | |
| }); | |
| gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 256, 1, 0, | |
| gl.RGBA, gl.UNSIGNED_BYTE, lutPixels); | |
| return state; | |
| } | |
| function renderGpu(canvas, codes, width, height) { | |
| let state = canvas._depthWebglState; | |
| if (!state) { | |
| state = createGpuState(canvas); | |
| if (!state) return false; | |
| canvas._depthWebglState = state; | |
| } | |
| const { gl } = state; | |
| if (canvas.width !== width || canvas.height !== height) { | |
| canvas.width = width; | |
| canvas.height = height; | |
| } | |
| gl.viewport(0, 0, width, height); | |
| gl.useProgram(state.program); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, state.buffer); | |
| gl.enableVertexAttribArray(state.position); | |
| gl.vertexAttribPointer(state.position, 2, gl.FLOAT, false, 0, 0); | |
| gl.activeTexture(gl.TEXTURE0); | |
| gl.bindTexture(gl.TEXTURE_2D, state.depthTexture); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| if (state.depthWidth !== width || state.depthHeight !== height) { | |
| // Allocate immutable storage once. Repeated texImage2D calls can | |
| // force the browser to retire/recreate the texture and stall the | |
| // compositor. The frame path below only updates the existing | |
| // storage with texSubImage2D. | |
| if (state.depthAllocated) { | |
| // Immutable storage cannot be resized. This is normally only | |
| // reached if a source changes resolution after mount. | |
| gl.deleteTexture(state.depthTexture); | |
| state.depthTexture = gl.createTexture(); | |
| gl.bindTexture(gl.TEXTURE_2D, state.depthTexture); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| } | |
| if (typeof gl.texStorage2D === 'function') { | |
| gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R16UI, width, height); | |
| state.depthAllocated = true; | |
| } else { | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.R16UI, width, height, 0, | |
| gl.RED_INTEGER, gl.UNSIGNED_SHORT, null); | |
| state.depthAllocated = false; | |
| } | |
| state.depthWidth = width; | |
| state.depthHeight = height; | |
| } | |
| gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, | |
| gl.RED_INTEGER, gl.UNSIGNED_SHORT, codes); | |
| const range = _rangeFor(canvas); | |
| gl.uniform1f(state.q01Loc, range.q01); | |
| gl.uniform1f(state.q99Loc, range.q99); | |
| gl.activeTexture(gl.TEXTURE1); | |
| gl.bindTexture(gl.TEXTURE_2D, state.lutTexture); | |
| gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
| return true; | |
| } | |
| function _rangeFor(canvas) { | |
| const range = canvas._depthRange; | |
| if (range && Number.isFinite(range.q01) && Number.isFinite(range.q99) | |
| && range.q99 > range.q01) { | |
| return range; | |
| } | |
| return { q01: 0, q99: DEPTH_QMAX }; | |
| } | |
| function loadJetLut() { | |
| if (lut) return Promise.resolve(lut); | |
| if (lutPromise) return lutPromise; | |
| lutPromise = fetch('/api/v1/video/depth-color-lut') | |
| .then(response => { | |
| if (!response.ok) throw new Error('Depth color LUT request failed'); | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| if (!data || data.name !== 'google_turbo' || | |
| data.order !== 'bgr' || !Array.isArray(data.values) || | |
| data.values.length !== 256) { | |
| throw new Error('Invalid Turbo depth LUT'); | |
| } | |
| lut = data.values; | |
| return lut; | |
| }); | |
| return lutPromise; | |
| } | |
| function render(canvas, codes, width, height) { | |
| if (!canvas || !codes || !lut || codes.length !== width * height) { | |
| return false; | |
| } | |
| try { | |
| if (renderGpu(canvas, codes, width, height)) return true; | |
| } catch (_) { | |
| canvas._depthWebglState = null; | |
| } | |
| if (canvas.width !== width || canvas.height !== height) { | |
| canvas.width = width; | |
| canvas.height = height; | |
| } | |
| const ctx = canvas.getContext('2d'); | |
| if (!ctx) return false; | |
| let image = canvas._depthImageData; | |
| if (!image || image.width !== width || image.height !== height) { | |
| image = canvas._depthImageData = ctx.createImageData(width, height); | |
| } | |
| const range = _rangeFor(canvas); | |
| const span = Math.max(1, range.q99 - range.q01); | |
| // CPU fallback: per-pixel adaptive mapping (q01/q99) through Turbo LUT. | |
| const pixels = image.data; | |
| for (let src = 0, dst = 0; src < codes.length; src++, dst += 4) { | |
| const code = Math.max(0, Math.min(DEPTH_QMAX, codes[src])); | |
| const clamped = Math.max(range.q01, Math.min(range.q99, code)); | |
| const c8 = Math.round((clamped - range.q01) * 255 / span); | |
| const bgr = lut[Math.max(0, Math.min(255, c8))]; | |
| pixels[dst] = bgr[2]; | |
| pixels[dst + 1] = bgr[1]; | |
| pixels[dst + 2] = bgr[0]; | |
| pixels[dst + 3] = 255; | |
| } | |
| ctx.putImageData(image, 0, 0); | |
| return true; | |
| } | |
| window.DepthRenderer = { DEPTH_QMAX, loadJetLut, render }; | |
| })(); | |