mirror of
https://github.com/ruvnet/RuView.git
synced 2026-04-26 13:10:40 +00:00
- Implemented the WiFi DensePose model in PyTorch, including CSI phase processing, modality translation, and DensePose prediction heads. - Added a comprehensive training utility for the model, including loss functions and training steps. - Created a CSV file to document hardware specifications, architecture details, training parameters, performance metrics, and advantages of the model.
384 lines
No EOL
13 KiB
JavaScript
384 lines
No EOL
13 KiB
JavaScript
// WiFi DensePose Application JavaScript
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize tabs
|
|
initTabs();
|
|
|
|
// Initialize hardware visualization
|
|
initHardware();
|
|
|
|
// Initialize demo simulation
|
|
initDemo();
|
|
|
|
// Initialize architecture interaction
|
|
initArchitecture();
|
|
});
|
|
|
|
// Tab switching functionality
|
|
function initTabs() {
|
|
const tabs = document.querySelectorAll('.nav-tab');
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
|
tabs.forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
// Get the tab id
|
|
const tabId = tab.getAttribute('data-tab');
|
|
|
|
// Remove active class from all tabs and contents
|
|
tabs.forEach(t => t.classList.remove('active'));
|
|
tabContents.forEach(c => c.classList.remove('active'));
|
|
|
|
// Add active class to current tab and content
|
|
tab.classList.add('active');
|
|
document.getElementById(tabId).classList.add('active');
|
|
});
|
|
});
|
|
}
|
|
|
|
// Hardware panel functionality
|
|
function initHardware() {
|
|
// Antenna interaction
|
|
const antennas = document.querySelectorAll('.antenna');
|
|
|
|
antennas.forEach(antenna => {
|
|
antenna.addEventListener('click', () => {
|
|
antenna.classList.toggle('active');
|
|
updateCSIDisplay();
|
|
});
|
|
});
|
|
|
|
// Start CSI simulation
|
|
updateCSIDisplay();
|
|
setInterval(updateCSIDisplay, 1000);
|
|
}
|
|
|
|
// Update CSI display with random values
|
|
function updateCSIDisplay() {
|
|
const activeAntennas = document.querySelectorAll('.antenna.active');
|
|
const isActive = activeAntennas.length > 0;
|
|
|
|
// Only update if at least one antenna is active
|
|
if (isActive) {
|
|
const amplitudeFill = document.querySelector('.csi-fill.amplitude');
|
|
const phaseFill = document.querySelector('.csi-fill.phase');
|
|
const amplitudeValue = document.querySelector('.csi-row:first-child .csi-value');
|
|
const phaseValue = document.querySelector('.csi-row:last-child .csi-value');
|
|
|
|
// Generate random values
|
|
const amplitude = (Math.random() * 0.4 + 0.5).toFixed(2); // Between 0.5 and 0.9
|
|
const phase = (Math.random() * 1.5 + 0.5).toFixed(1); // Between 0.5 and 2.0
|
|
|
|
// Update the display
|
|
amplitudeFill.style.width = `${amplitude * 100}%`;
|
|
phaseFill.style.width = `${phase * 50}%`;
|
|
amplitudeValue.textContent = amplitude;
|
|
phaseValue.textContent = `${phase}π`;
|
|
}
|
|
}
|
|
|
|
// Demo functionality
|
|
function initDemo() {
|
|
const startButton = document.getElementById('startDemo');
|
|
const stopButton = document.getElementById('stopDemo');
|
|
const demoStatus = document.getElementById('demoStatus');
|
|
const signalCanvas = document.getElementById('signalCanvas');
|
|
const poseCanvas = document.getElementById('poseCanvas');
|
|
const signalStrength = document.getElementById('signalStrength');
|
|
const latency = document.getElementById('latency');
|
|
const personCount = document.getElementById('personCount');
|
|
const confidence = document.getElementById('confidence');
|
|
const keypoints = document.getElementById('keypoints');
|
|
|
|
let demoRunning = false;
|
|
let animationFrameId = null;
|
|
let signalCtx = signalCanvas.getContext('2d');
|
|
let poseCtx = poseCanvas.getContext('2d');
|
|
|
|
// Initialize canvas contexts
|
|
signalCtx.fillStyle = 'rgba(0, 0, 0, 0.2)';
|
|
signalCtx.fillRect(0, 0, signalCanvas.width, signalCanvas.height);
|
|
|
|
poseCtx.fillStyle = 'rgba(0, 0, 0, 0.2)';
|
|
poseCtx.fillRect(0, 0, poseCanvas.width, poseCanvas.height);
|
|
|
|
// Start demo button
|
|
startButton.addEventListener('click', () => {
|
|
if (!demoRunning) {
|
|
demoRunning = true;
|
|
startButton.disabled = true;
|
|
stopButton.disabled = false;
|
|
demoStatus.textContent = 'Running';
|
|
demoStatus.className = 'status status--success';
|
|
|
|
// Start the animations
|
|
startSignalAnimation();
|
|
startPoseAnimation();
|
|
|
|
// Update metrics with random values
|
|
updateDemoMetrics();
|
|
}
|
|
});
|
|
|
|
// Stop demo button
|
|
stopButton.addEventListener('click', () => {
|
|
if (demoRunning) {
|
|
demoRunning = false;
|
|
startButton.disabled = false;
|
|
stopButton.disabled = true;
|
|
demoStatus.textContent = 'Stopped';
|
|
demoStatus.className = 'status status--info';
|
|
|
|
// Stop the animations
|
|
if (animationFrameId) {
|
|
cancelAnimationFrame(animationFrameId);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Signal animation
|
|
function startSignalAnimation() {
|
|
let time = 0;
|
|
const fps = 30;
|
|
const interval = 1000 / fps;
|
|
let then = Date.now();
|
|
|
|
function animate() {
|
|
if (!demoRunning) return;
|
|
|
|
const now = Date.now();
|
|
const elapsed = now - then;
|
|
|
|
if (elapsed > interval) {
|
|
then = now - (elapsed % interval);
|
|
|
|
// Clear canvas
|
|
signalCtx.clearRect(0, 0, signalCanvas.width, signalCanvas.height);
|
|
signalCtx.fillStyle = 'rgba(0, 0, 0, 0.2)';
|
|
signalCtx.fillRect(0, 0, signalCanvas.width, signalCanvas.height);
|
|
|
|
// Draw amplitude signal
|
|
signalCtx.beginPath();
|
|
signalCtx.strokeStyle = '#1FB8CD';
|
|
signalCtx.lineWidth = 2;
|
|
|
|
for (let x = 0; x < signalCanvas.width; x++) {
|
|
const y = signalCanvas.height / 2 +
|
|
Math.sin(x * 0.05 + time) * 30 +
|
|
Math.sin(x * 0.02 + time * 1.5) * 15;
|
|
|
|
if (x === 0) {
|
|
signalCtx.moveTo(x, y);
|
|
} else {
|
|
signalCtx.lineTo(x, y);
|
|
}
|
|
}
|
|
|
|
signalCtx.stroke();
|
|
|
|
// Draw phase signal
|
|
signalCtx.beginPath();
|
|
signalCtx.strokeStyle = '#FFC185';
|
|
signalCtx.lineWidth = 2;
|
|
|
|
for (let x = 0; x < signalCanvas.width; x++) {
|
|
const y = signalCanvas.height / 2 +
|
|
Math.cos(x * 0.03 + time * 0.8) * 20 +
|
|
Math.cos(x * 0.01 + time * 0.5) * 25;
|
|
|
|
if (x === 0) {
|
|
signalCtx.moveTo(x, y);
|
|
} else {
|
|
signalCtx.lineTo(x, y);
|
|
}
|
|
}
|
|
|
|
signalCtx.stroke();
|
|
|
|
time += 0.05;
|
|
}
|
|
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
}
|
|
|
|
animate();
|
|
}
|
|
|
|
// Human pose animation
|
|
function startPoseAnimation() {
|
|
// Create a human wireframe model with keypoints
|
|
const keyPoints = [
|
|
{ x: 200, y: 70 }, // Head
|
|
{ x: 200, y: 100 }, // Neck
|
|
{ x: 200, y: 150 }, // Torso
|
|
{ x: 160, y: 100 }, // Left shoulder
|
|
{ x: 120, y: 130 }, // Left elbow
|
|
{ x: 100, y: 160 }, // Left hand
|
|
{ x: 240, y: 100 }, // Right shoulder
|
|
{ x: 280, y: 130 }, // Right elbow
|
|
{ x: 300, y: 160 }, // Right hand
|
|
{ x: 180, y: 200 }, // Left hip
|
|
{ x: 170, y: 250 }, // Left knee
|
|
{ x: 160, y: 290 }, // Left foot
|
|
{ x: 220, y: 200 }, // Right hip
|
|
{ x: 230, y: 250 }, // Right knee
|
|
{ x: 240, y: 290 }, // Right foot
|
|
];
|
|
|
|
// Connections between points
|
|
const connections = [
|
|
[0, 1], // Head to neck
|
|
[1, 2], // Neck to torso
|
|
[1, 3], // Neck to left shoulder
|
|
[3, 4], // Left shoulder to left elbow
|
|
[4, 5], // Left elbow to left hand
|
|
[1, 6], // Neck to right shoulder
|
|
[6, 7], // Right shoulder to right elbow
|
|
[7, 8], // Right elbow to right hand
|
|
[2, 9], // Torso to left hip
|
|
[9, 10], // Left hip to left knee
|
|
[10, 11], // Left knee to left foot
|
|
[2, 12], // Torso to right hip
|
|
[12, 13], // Right hip to right knee
|
|
[13, 14], // Right knee to right foot
|
|
[9, 12] // Left hip to right hip
|
|
];
|
|
|
|
let time = 0;
|
|
const fps = 30;
|
|
const interval = 1000 / fps;
|
|
let then = Date.now();
|
|
|
|
function animate() {
|
|
if (!demoRunning) return;
|
|
|
|
const now = Date.now();
|
|
const elapsed = now - then;
|
|
|
|
if (elapsed > interval) {
|
|
then = now - (elapsed % interval);
|
|
|
|
// Clear canvas
|
|
poseCtx.clearRect(0, 0, poseCanvas.width, poseCanvas.height);
|
|
poseCtx.fillStyle = 'rgba(0, 0, 0, 0.2)';
|
|
poseCtx.fillRect(0, 0, poseCanvas.width, poseCanvas.height);
|
|
|
|
// Animate keypoints with subtle movement
|
|
const animatedPoints = keyPoints.map((point, index) => {
|
|
// Add subtle movement based on position
|
|
const xOffset = Math.sin(time + index * 0.2) * 2;
|
|
const yOffset = Math.cos(time + index * 0.2) * 2;
|
|
|
|
return {
|
|
x: point.x + xOffset,
|
|
y: point.y + yOffset
|
|
};
|
|
});
|
|
|
|
// Draw connections (skeleton)
|
|
poseCtx.strokeStyle = '#1FB8CD';
|
|
poseCtx.lineWidth = 3;
|
|
|
|
connections.forEach(([i, j]) => {
|
|
poseCtx.beginPath();
|
|
poseCtx.moveTo(animatedPoints[i].x, animatedPoints[i].y);
|
|
poseCtx.lineTo(animatedPoints[j].x, animatedPoints[j].y);
|
|
poseCtx.stroke();
|
|
});
|
|
|
|
// Draw keypoints
|
|
poseCtx.fillStyle = '#FFC185';
|
|
|
|
animatedPoints.forEach(point => {
|
|
poseCtx.beginPath();
|
|
poseCtx.arc(point.x, point.y, 5, 0, Math.PI * 2);
|
|
poseCtx.fill();
|
|
});
|
|
|
|
// Draw body segments (simplified DensePose representation)
|
|
drawBodySegments(poseCtx, animatedPoints);
|
|
|
|
time += 0.05;
|
|
}
|
|
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
}
|
|
|
|
animate();
|
|
}
|
|
|
|
// Draw body segments for DensePose visualization
|
|
function drawBodySegments(ctx, points) {
|
|
// Define simplified body segments
|
|
const segments = [
|
|
[0, 1, 6, 3], // Head and shoulders
|
|
[1, 2, 12, 9], // Torso
|
|
[3, 4, 5, 3], // Left arm
|
|
[6, 7, 8, 6], // Right arm
|
|
[9, 10, 11, 9], // Left leg
|
|
[12, 13, 14, 12] // Right leg
|
|
];
|
|
|
|
ctx.globalAlpha = 0.2;
|
|
|
|
segments.forEach((segment, index) => {
|
|
const gradient = ctx.createLinearGradient(
|
|
points[segment[0]].x, points[segment[0]].y,
|
|
points[segment[2]].x, points[segment[2]].y
|
|
);
|
|
|
|
gradient.addColorStop(0, '#1FB8CD');
|
|
gradient.addColorStop(1, '#FFC185');
|
|
|
|
ctx.fillStyle = gradient;
|
|
ctx.beginPath();
|
|
ctx.moveTo(points[segment[0]].x, points[segment[0]].y);
|
|
|
|
// Connect the points in the segment
|
|
for (let i = 1; i < segment.length; i++) {
|
|
ctx.lineTo(points[segment[i]].x, points[segment[i]].y);
|
|
}
|
|
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
});
|
|
|
|
ctx.globalAlpha = 1.0;
|
|
}
|
|
|
|
// Update demo metrics
|
|
function updateDemoMetrics() {
|
|
if (!demoRunning) return;
|
|
|
|
// Update with random values
|
|
const strength = Math.floor(Math.random() * 10) - 50;
|
|
const lat = Math.floor(Math.random() * 8) + 8;
|
|
const persons = Math.floor(Math.random() * 2) + 1;
|
|
const conf = (Math.random() * 10 + 80).toFixed(1);
|
|
|
|
signalStrength.textContent = `${strength} dBm`;
|
|
latency.textContent = `${lat} ms`;
|
|
personCount.textContent = persons;
|
|
confidence.textContent = `${conf}%`;
|
|
|
|
// Schedule next update
|
|
setTimeout(updateDemoMetrics, 2000);
|
|
}
|
|
}
|
|
|
|
// Architecture interaction
|
|
function initArchitecture() {
|
|
const stepCards = document.querySelectorAll('.step-card');
|
|
|
|
stepCards.forEach(card => {
|
|
card.addEventListener('click', () => {
|
|
// Get step number
|
|
const step = card.getAttribute('data-step');
|
|
|
|
// Remove active class from all steps
|
|
stepCards.forEach(s => s.classList.remove('highlight'));
|
|
|
|
// Add active class to current step
|
|
card.classList.add('highlight');
|
|
});
|
|
});
|
|
} |