Skip to content

Commit b2e3f27

Browse files
committed
Enhance viewer: skeleton overlay, weather, buildings, better camera
Add COCO skeleton rendering with yellow keypoint spheres and white bone lines, info panel sections for weather/buildings/CSI rate/confidence, overhead camera at (0,2,-4), and denser point size with sizeAttenuation. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent e39a35e commit b2e3f27

File tree

1 file changed

+165
-28
lines changed
  • rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src

1 file changed

+165
-28
lines changed

rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/stream.rs

Lines changed: 165 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,10 @@ async fn index() -> Html<String> {
192192
<style>
193193
body { margin: 0; background: #0a0a0a; color: #e8a634; font-family: monospace; }
194194
canvas { display: block; }
195-
#info { position: absolute; top: 10px; left: 10px; padding: 12px; background: rgba(0,0,0,0.85); border: 1px solid #e8a634; border-radius: 6px; min-width: 200px; }
195+
#info { position: absolute; top: 10px; left: 10px; padding: 12px; background: rgba(0,0,0,0.85); border: 1px solid #e8a634; border-radius: 6px; min-width: 240px; font-size: 13px; line-height: 1.5; }
196196
.live { color: #4f4; } .demo { color: #f44; }
197+
.section { margin-top: 6px; padding-top: 6px; border-top: 1px solid #333; }
198+
.label { color: #888; }
197199
</style>
198200
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
199201
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
@@ -204,38 +206,171 @@ async fn index() -> Html<String> {
204206
<div id="stats">Loading...</div>
205207
</div>
206208
<script>
207-
const scene = new THREE.Scene();
209+
var scene = new THREE.Scene();
208210
scene.background = new THREE.Color(0x0a0a0a);
209-
const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 100);
210-
camera.position.set(0, 0, -2);
211-
camera.lookAt(0, 0, 3);
211+
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 100);
212+
camera.position.set(0, 2, -4);
213+
camera.lookAt(0, 0, 2);
212214
213-
const renderer = new THREE.WebGLRenderer({ antialias: true });
215+
var renderer = new THREE.WebGLRenderer({ antialias: true });
214216
renderer.setSize(window.innerWidth, window.innerHeight);
215217
document.body.appendChild(renderer.domElement);
216218
217-
const controls = new THREE.OrbitControls(camera, renderer.domElement);
219+
var controls = new THREE.OrbitControls(camera, renderer.domElement);
218220
controls.enableDamping = true;
219-
controls.target.set(0, 0, 3);
221+
controls.target.set(0, 0, 2);
220222
221-
let pointsMesh = null;
222-
let lastFrame = -1;
223+
var pointsMesh = null;
224+
var lastFrame = -1;
225+
var skeletonGroup = null;
226+
var prevTimestamp = 0;
227+
var frameRateVal = 0;
228+
229+
// COCO skeleton connections: pairs of keypoint indices
230+
// 0=nose 1=leftEye 2=rightEye 3=leftEar 4=rightEar
231+
// 5=leftShoulder 6=rightShoulder 7=leftElbow 8=rightElbow
232+
// 9=leftWrist 10=rightWrist 11=leftHip 12=rightHip
233+
// 13=leftKnee 14=rightKnee 15=leftAnkle 16=rightAnkle
234+
var COCO_BONES = [
235+
[0,1],[0,2],[1,3],[2,4],
236+
[5,6],[5,7],[7,9],[6,8],[8,10],
237+
[5,11],[6,12],[11,12],
238+
[11,13],[13,15],[12,14],[14,16]
239+
];
240+
241+
function clearSkeleton() {
242+
if (skeletonGroup) {
243+
scene.remove(skeletonGroup);
244+
skeletonGroup.traverse(function(obj) {
245+
if (obj.geometry) obj.geometry.dispose();
246+
if (obj.material) obj.material.dispose();
247+
});
248+
skeletonGroup = null;
249+
}
250+
}
251+
252+
function drawSkeleton(keypoints) {
253+
clearSkeleton();
254+
if (!keypoints || keypoints.length < 17) return;
255+
skeletonGroup = new THREE.Group();
256+
257+
// Map keypoints from [0,1] to scene coords
258+
// x: [-2, 2], y: [2, -2] (flip y), z: fixed at 2
259+
var sphereGeo = new THREE.SphereGeometry(0.04, 8, 8);
260+
var sphereMat = new THREE.MeshBasicMaterial({ color: 0xffff00 });
261+
var positions3D = [];
262+
var i, kp, sx, sy;
263+
for (i = 0; i < 17; i++) {
264+
kp = keypoints[i];
265+
if (!kp) { positions3D.push(null); continue; }
266+
sx = (kp[0] - 0.5) * 4;
267+
sy = (0.5 - kp[1]) * 4;
268+
positions3D.push([sx, sy, 2]);
269+
var sphere = new THREE.Mesh(sphereGeo, sphereMat);
270+
sphere.position.set(sx, sy, 2);
271+
skeletonGroup.add(sphere);
272+
}
273+
274+
// Draw bones as white lines
275+
var lineMat = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2 });
276+
var b, a, bIdx;
277+
for (b = 0; b < COCO_BONES.length; b++) {
278+
a = COCO_BONES[b][0];
279+
bIdx = COCO_BONES[b][1];
280+
if (!positions3D[a] || !positions3D[bIdx]) continue;
281+
var lineGeo = new THREE.BufferGeometry();
282+
var verts = new Float32Array([
283+
positions3D[a][0], positions3D[a][1], positions3D[a][2],
284+
positions3D[bIdx][0], positions3D[bIdx][1], positions3D[bIdx][2]
285+
]);
286+
lineGeo.setAttribute("position", new THREE.BufferAttribute(verts, 3));
287+
var line = new THREE.Line(lineGeo, lineMat);
288+
skeletonGroup.add(line);
289+
}
290+
291+
scene.add(skeletonGroup);
292+
}
223293
224294
async function fetchCloud() {
225295
try {
226-
const resp = await fetch('/api/splats');
227-
const data = await resp.json();
296+
var resp = await fetch("/api/splats");
297+
var data = await resp.json();
228298
if (data.splats && data.frame !== lastFrame) {
299+
// Compute CSI frame rate
300+
var now = Date.now();
301+
if (prevTimestamp > 0) {
302+
var dt = (now - prevTimestamp) / 1000.0;
303+
if (dt > 0) frameRateVal = (1.0 / dt).toFixed(1);
304+
}
305+
prevTimestamp = now;
229306
lastFrame = data.frame;
230307
updateSplats(data.splats);
231-
const mode = data.live ? '<span class="live">● LIVE</span>' : '<span class="demo">● DEMO</span>';
232-
let csiInfo = '';
233-
if (data.csi) {
234-
const m = (data.csi.motion * 100).toFixed(0);
235-
csiInfo = `<br>CSI: ${data.csi.frames} frames, motion ${m}%<br>Distance: ${data.csi.distance_m.toFixed(1)}m`;
308+
309+
// Draw skeleton if available
310+
var pipe = data.pipeline;
311+
if (pipe && pipe.skeleton && pipe.skeleton.keypoints) {
312+
drawSkeleton(pipe.skeleton.keypoints);
313+
} else {
314+
clearSkeleton();
236315
}
237-
document.getElementById('stats').innerHTML =
238-
`${mode} Camera + CSI<br>Splats: ${data.count}<br>Frame: ${data.frame}${csiInfo}`;
316+
317+
// Build info panel
318+
var mode = data.live
319+
? '<span class="live">&#9679; LIVE</span>'
320+
: '<span class="demo">&#9679; DEMO</span>';
321+
var html = mode + " Camera + CSI<br>"
322+
+ "Splats: " + data.count + "<br>"
323+
+ "Frame: " + data.frame;
324+
325+
// CSI frame rate
326+
html += '<div class="section">'
327+
+ '<span class="label">CSI Rate:</span> '
328+
+ frameRateVal + " fps</div>";
329+
330+
// Skeleton confidence
331+
if (pipe && pipe.skeleton && pipe.skeleton.confidence !== undefined) {
332+
var conf = (pipe.skeleton.confidence * 100).toFixed(0);
333+
html += '<div class="section">'
334+
+ '<span class="label">Skeleton:</span> '
335+
+ conf + "% confidence</div>";
336+
}
337+
338+
// Weather data
339+
if (pipe && pipe.weather) {
340+
var w = pipe.weather;
341+
html += '<div class="section">'
342+
+ '<span class="label">Weather:</span> ';
343+
if (w.temperature !== undefined) {
344+
html += w.temperature + "&deg;C";
345+
}
346+
if (w.conditions) {
347+
html += " " + w.conditions;
348+
}
349+
html += "</div>";
350+
}
351+
352+
// Building count from geo
353+
if (pipe && pipe.geo && pipe.geo.building_count !== undefined) {
354+
html += '<div class="section">'
355+
+ '<span class="label">Buildings:</span> '
356+
+ pipe.geo.building_count + "</div>";
357+
}
358+
359+
// Vitals
360+
if (pipe && pipe.vitals) {
361+
var v = pipe.vitals;
362+
html += '<div class="section">'
363+
+ '<span class="label">Vitals:</span> ';
364+
if (v.breathing_rate !== undefined) {
365+
html += "BR " + v.breathing_rate + "/min";
366+
}
367+
if (v.motion_score !== undefined) {
368+
html += " Motion " + (v.motion_score * 100).toFixed(0) + "%";
369+
}
370+
html += "</div>";
371+
}
372+
373+
document.getElementById("stats").innerHTML = html;
239374
}
240375
} catch(e) {}
241376
}
@@ -244,21 +379,23 @@ async fn index() -> Html<String> {
244379
245380
function updateSplats(splats) {
246381
if (pointsMesh) scene.remove(pointsMesh);
247-
const geometry = new THREE.BufferGeometry();
248-
const positions = new Float32Array(splats.length * 3);
249-
const colors = new Float32Array(splats.length * 3);
250-
splats.forEach((s, i) => {
382+
var geometry = new THREE.BufferGeometry();
383+
var positions = new Float32Array(splats.length * 3);
384+
var colors = new Float32Array(splats.length * 3);
385+
var i, s;
386+
for (i = 0; i < splats.length; i++) {
387+
s = splats[i];
251388
positions[i*3] = s.center[0];
252389
positions[i*3+1] = -s.center[1];
253390
positions[i*3+2] = s.center[2];
254391
colors[i*3] = s.color[0];
255392
colors[i*3+1] = s.color[1];
256393
colors[i*3+2] = s.color[2];
257-
});
258-
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
259-
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
394+
}
395+
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
396+
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
260397
pointsMesh = new THREE.Points(geometry, new THREE.PointsMaterial({
261-
size: 0.025, vertexColors: true, sizeAttenuation: true,
398+
size: 0.02, vertexColors: true, sizeAttenuation: true
262399
}));
263400
scene.add(pointsMesh);
264401
}
@@ -269,7 +406,7 @@ async fn index() -> Html<String> {
269406
renderer.render(scene, camera);
270407
}
271408
animate();
272-
window.addEventListener('resize', () => {
409+
window.addEventListener("resize", function() {
273410
camera.aspect = window.innerWidth / window.innerHeight;
274411
camera.updateProjectionMatrix();
275412
renderer.setSize(window.innerWidth, window.innerHeight);

0 commit comments

Comments
 (0)