Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 64dae5b

Browse files
committed
feat: implement heatmap and dense pose render modes
- Heatmap: Gaussian radial blobs per keypoint with per-person hue, faint skeleton overlay at 25% opacity - Dense: body region segmentation with colored filled polygons for head, torso, arms, legs — thick strokes + joint circles - Keypoints: now also renders bounding box and confidence - Previously both heatmap and dense were stubs falling back to skeleton Co-Authored-By: claude-flow <[email protected]>
1 parent 8e487c5 commit 64dae5b

1 file changed

Lines changed: 113 additions & 12 deletions

File tree

ui/utils/pose-renderer.js

Lines changed: 113 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -183,31 +183,132 @@ export class PoseRenderer {
183183
}
184184
}
185185

186-
// Keypoints only mode
186+
// Keypoints only mode — large colored dots with labels, no skeleton lines
187187
renderKeypointsMode(poseData, metadata) {
188188
const persons = poseData.persons || [];
189-
189+
190190
persons.forEach((person, index) => {
191191
if (person.confidence >= this.config.confidenceThreshold && person.keypoints) {
192192
this.renderKeypoints(person.keypoints, person.confidence, true);
193+
194+
// Render bounding box
195+
if (this.config.showBoundingBox && person.bbox) {
196+
this.renderBoundingBox(person.bbox, person.confidence, index);
197+
}
198+
if (this.config.showConfidence) {
199+
this.renderConfidenceScore(person, index);
200+
}
193201
}
194202
});
203+
204+
if (this.config.showZones && poseData.zone_summary) {
205+
this.renderZones(poseData.zone_summary);
206+
}
195207
}
196208

197-
// Heatmap rendering mode
209+
// Heatmap rendering mode — Gaussian blobs around each keypoint
198210
renderHeatmapMode(poseData, metadata) {
199-
// This would render a heatmap visualization
200-
// For now, fall back to skeleton mode
201-
this.logger.debug('Heatmap mode not fully implemented, using skeleton mode');
202-
this.renderSkeletonMode(poseData, metadata);
211+
const persons = poseData.persons || [];
212+
213+
persons.forEach((person, personIdx) => {
214+
if (person.confidence < this.config.confidenceThreshold || !person.keypoints) return;
215+
216+
const hue = (personIdx * 60) % 360; // different hue per person
217+
218+
person.keypoints.forEach((kp) => {
219+
if (kp.confidence <= this.config.keypointConfidenceThreshold) return;
220+
221+
const cx = this.scaleX(kp.x);
222+
const cy = this.scaleY(kp.y);
223+
const radius = 30 + kp.confidence * 20;
224+
225+
const grad = this.ctx.createRadialGradient(cx, cy, 0, cx, cy, radius);
226+
grad.addColorStop(0, `hsla(${hue}, 100%, 55%, ${kp.confidence * 0.7})`);
227+
grad.addColorStop(0.5, `hsla(${hue}, 100%, 45%, ${kp.confidence * 0.3})`);
228+
grad.addColorStop(1, `hsla(${hue}, 100%, 40%, 0)`);
229+
230+
this.ctx.fillStyle = grad;
231+
this.ctx.fillRect(cx - radius, cy - radius, radius * 2, radius * 2);
232+
});
233+
234+
// Light skeleton overlay so joints are connected
235+
if (person.keypoints) {
236+
this.ctx.globalAlpha = 0.25;
237+
this.renderSkeleton(person.keypoints, person.confidence);
238+
this.ctx.globalAlpha = 1.0;
239+
}
240+
241+
if (this.config.showConfidence) {
242+
this.renderConfidenceScore(person, personIdx);
243+
}
244+
});
245+
246+
if (this.config.showZones && poseData.zone_summary) {
247+
this.renderZones(poseData.zone_summary);
248+
}
203249
}
204250

205-
// Dense pose rendering mode
251+
// Dense pose rendering mode — body region segmentation with filled polygons
206252
renderDenseMode(poseData, metadata) {
207-
// This would render dense pose segmentation
208-
// For now, fall back to skeleton mode
209-
this.logger.debug('Dense mode not fully implemented, using skeleton mode');
210-
this.renderSkeletonMode(poseData, metadata);
253+
const persons = poseData.persons || [];
254+
255+
// Body part groups: [start_kp, end_kp, color]
256+
const bodyParts = [
257+
{ name: 'head', kps: [0, 1, 2, 3, 4], color: 'rgba(255, 100, 100, 0.4)' },
258+
{ name: 'torso', kps: [5, 6, 12, 11], color: 'rgba(100, 200, 255, 0.4)' },
259+
{ name: 'left_arm', kps: [5, 7, 9], color: 'rgba(100, 255, 150, 0.4)' },
260+
{ name: 'right_arm', kps: [6, 8, 10], color: 'rgba(255, 200, 100, 0.4)' },
261+
{ name: 'left_leg', kps: [11, 13, 15], color: 'rgba(200, 100, 255, 0.4)' },
262+
{ name: 'right_leg', kps: [12, 14, 16], color: 'rgba(255, 255, 100, 0.4)' },
263+
];
264+
265+
persons.forEach((person, personIdx) => {
266+
if (person.confidence < this.config.confidenceThreshold || !person.keypoints) return;
267+
268+
const kps = person.keypoints;
269+
270+
bodyParts.forEach((part) => {
271+
// Collect valid keypoints for this body part
272+
const points = part.kps
273+
.filter(i => kps[i] && kps[i].confidence > this.config.keypointConfidenceThreshold)
274+
.map(i => ({ x: this.scaleX(kps[i].x), y: this.scaleY(kps[i].y) }));
275+
276+
if (points.length < 2) return;
277+
278+
// Draw filled region with padding around joints
279+
this.ctx.fillStyle = part.color;
280+
this.ctx.strokeStyle = part.color.replace('0.4', '0.7');
281+
this.ctx.lineWidth = 8;
282+
this.ctx.lineJoin = 'round';
283+
this.ctx.lineCap = 'round';
284+
285+
// Draw thick path as a "region"
286+
this.ctx.beginPath();
287+
this.ctx.moveTo(points[0].x, points[0].y);
288+
for (let i = 1; i < points.length; i++) {
289+
this.ctx.lineTo(points[i].x, points[i].y);
290+
}
291+
this.ctx.stroke();
292+
293+
// Draw circles at each joint to widen the region
294+
points.forEach(p => {
295+
this.ctx.beginPath();
296+
this.ctx.arc(p.x, p.y, 10, 0, Math.PI * 2);
297+
this.ctx.fill();
298+
});
299+
});
300+
301+
// Subtle keypoint dots on top
302+
this.renderKeypoints(kps, person.confidence, false);
303+
304+
if (this.config.showConfidence) {
305+
this.renderConfidenceScore(person, personIdx);
306+
}
307+
});
308+
309+
if (this.config.showZones && poseData.zone_summary) {
310+
this.renderZones(poseData.zone_summary);
311+
}
211312
}
212313

213314
// Render skeleton connections

0 commit comments

Comments
 (0)