- 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
13 KiB
JavaScript
384 lines
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');
|
|
});
|
|
});
|
|
} |