|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Perceptron Passive Radar</title> |
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
font-family: monospace; |
|
} |
|
|
|
body { |
|
background: #001; |
|
color: #0f8; |
|
min-height: 100vh; |
|
overflow: hidden; |
|
} |
|
|
|
.main-container { |
|
display: grid; |
|
grid-template-columns: 1fr 400px; |
|
gap: 10px; |
|
padding: 10px; |
|
height: 100vh; |
|
} |
|
|
|
.radar-container { |
|
position: relative; |
|
border: 2px solid #0f8; |
|
border-radius: 50%; |
|
background: radial-gradient(circle, rgba(0,255,136,0.05) 0%, rgba(0,0,0,0.9) 100%); |
|
overflow: hidden; |
|
} |
|
|
|
.sweep-line { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
width: 50%; |
|
height: 2px; |
|
background: linear-gradient(90deg, #0f8, transparent); |
|
transform-origin: left; |
|
animation: radar-sweep 4s linear infinite; |
|
} |
|
|
|
.grid-overlay { |
|
position: absolute; |
|
width: 100%; |
|
height: 100%; |
|
background: |
|
linear-gradient(#0f81 1px, transparent 1px), |
|
linear-gradient(90deg, #0f81 1px, transparent 1px); |
|
background-size: 50px 50px; |
|
} |
|
|
|
@keyframes radar-sweep { |
|
from { transform: rotate(0deg); } |
|
to { transform: rotate(360deg); } |
|
} |
|
|
|
.control-panel { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 10px; |
|
} |
|
|
|
.panel { |
|
background: rgba(0,255,136,0.1); |
|
border: 1px solid #0f8; |
|
padding: 10px; |
|
} |
|
|
|
.perceptron-layer { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
height: 200px; |
|
margin: 10px 0; |
|
position: relative; |
|
} |
|
|
|
.layer { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 4px; |
|
} |
|
|
|
.neuron { |
|
width: 12px; |
|
height: 12px; |
|
background: #0f8; |
|
border-radius: 50%; |
|
position: relative; |
|
opacity: 0.3; |
|
transition: all 0.2s; |
|
} |
|
|
|
.neuron.active { |
|
opacity: 1; |
|
box-shadow: 0 0 10px #0f8; |
|
} |
|
|
|
.connections { |
|
position: absolute; |
|
width: 100%; |
|
height: 100%; |
|
pointer-events: none; |
|
} |
|
|
|
.spectrum { |
|
height: 120px; |
|
display: flex; |
|
align-items: flex-end; |
|
gap: 2px; |
|
} |
|
|
|
.bar { |
|
flex: 1; |
|
background: #0f8; |
|
transition: height 0.1s; |
|
} |
|
|
|
.signal { |
|
position: absolute; |
|
width: 8px; |
|
height: 8px; |
|
background: #0f8; |
|
border-radius: 50%; |
|
transform: translate(-50%, -50%); |
|
} |
|
|
|
.signal-echo { |
|
position: absolute; |
|
border: 1px solid #0f8; |
|
border-radius: 50%; |
|
transform: translate(-50%, -50%); |
|
animation: echo 2s ease-out forwards; |
|
} |
|
|
|
@keyframes echo { |
|
0% { |
|
width: 8px; |
|
height: 8px; |
|
opacity: 1; |
|
} |
|
100% { |
|
width: 60px; |
|
height: 60px; |
|
opacity: 0; |
|
} |
|
} |
|
|
|
button { |
|
background: transparent; |
|
border: 1px solid #0f8; |
|
color: #0f8; |
|
padding: 8px; |
|
cursor: pointer; |
|
transition: 0.3s; |
|
} |
|
|
|
button:hover { |
|
background: #0f8; |
|
color: #001; |
|
} |
|
|
|
.stats { |
|
font-size: 12px; |
|
line-height: 1.5; |
|
} |
|
|
|
canvas { |
|
margin-top: 10px; |
|
border: 1px solid #0f8; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="main-container"> |
|
<div class="radar-container"> |
|
<div class="grid-overlay"></div> |
|
<div class="sweep-line"></div> |
|
<div id="signals"></div> |
|
</div> |
|
|
|
<div class="control-panel"> |
|
<div class="panel"> |
|
<h3>Perceptron Network</h3> |
|
<div class="perceptron-layer"> |
|
<canvas id="networkCanvas"></canvas> |
|
</div> |
|
<div class="stats" id="networkStats"> |
|
Accuracy: 0%<br> |
|
Detections: 0<br> |
|
Signal Strength: 0 |
|
</div> |
|
</div> |
|
|
|
<div class="panel"> |
|
<h3>Frequency Analysis</h3> |
|
<div class="spectrum" id="spectrum"></div> |
|
</div> |
|
|
|
<div class="panel"> |
|
<button onclick="startRadar()">Start Detection</button> |
|
<button onclick="stopRadar()">Stop</button> |
|
<button onclick="trainPerceptrons()">Train Network</button> |
|
<button onclick="toggleLearning()">Toggle Learning</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
class Perceptron { |
|
constructor(inputs) { |
|
this.weights = new Array(inputs).fill(0).map(() => Math.random() * 2 - 1); |
|
this.bias = Math.random() * 2 - 1; |
|
this.learningRate = 0.1; |
|
} |
|
|
|
activate(sum) { |
|
return sum > 0 ? 1 : 0; |
|
} |
|
|
|
predict(inputs) { |
|
const sum = inputs.reduce((sum, input, i) => sum + input * this.weights[i], 0) + this.bias; |
|
return this.activate(sum); |
|
} |
|
|
|
train(inputs, target) { |
|
const prediction = this.predict(inputs); |
|
const error = target - prediction; |
|
|
|
for(let i = 0; i < this.weights.length; i++) { |
|
this.weights[i] += error * inputs[i] * this.learningRate; |
|
} |
|
this.bias += error * this.learningRate; |
|
|
|
return Math.abs(error); |
|
} |
|
} |
|
|
|
class PerceptronNetwork { |
|
constructor(inputSize, hiddenSize, outputSize) { |
|
this.inputLayer = new Array(hiddenSize) |
|
.fill(0) |
|
.map(() => new Perceptron(inputSize)); |
|
|
|
this.outputLayer = new Array(outputSize) |
|
.fill(0) |
|
.map(() => new Perceptron(hiddenSize)); |
|
} |
|
|
|
predict(inputs) { |
|
const hiddenOutputs = this.inputLayer.map(p => p.predict(inputs)); |
|
return this.outputLayer.map(p => p.predict(hiddenOutputs)); |
|
} |
|
|
|
train(inputs, targets) { |
|
const hiddenOutputs = this.inputLayer.map(p => p.predict(inputs)); |
|
const finalOutputs = this.outputLayer.map(p => p.predict(hiddenOutputs)); |
|
|
|
let error = 0; |
|
this.outputLayer.forEach((p, i) => { |
|
error += p.train(hiddenOutputs, targets[i]); |
|
}); |
|
this.inputLayer.forEach(p => { |
|
error += p.train(inputs, 1); |
|
}); |
|
|
|
return error / (this.inputLayer.length + this.outputLayer.length); |
|
} |
|
} |
|
|
|
|
|
const INPUT_SIZE = 32; |
|
const HIDDEN_SIZE = 16; |
|
const OUTPUT_SIZE = 4; |
|
|
|
let isRunning = false; |
|
let isLearning = false; |
|
let network = new PerceptronNetwork(INPUT_SIZE, HIDDEN_SIZE, OUTPUT_SIZE); |
|
let audioContext, analyser, dataArray; |
|
|
|
|
|
const spectrum = document.getElementById('spectrum'); |
|
for(let i = 0; i < INPUT_SIZE; i++) { |
|
const bar = document.createElement('div'); |
|
bar.className = 'bar'; |
|
spectrum.appendChild(bar); |
|
} |
|
|
|
|
|
const canvas = document.getElementById('networkCanvas'); |
|
canvas.width = 350; |
|
canvas.height = 180; |
|
const ctx = canvas.getContext('2d'); |
|
|
|
function drawNetwork(inputs, hiddenOutputs, finalOutputs) { |
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
ctx.strokeStyle = '#0f8'; |
|
ctx.lineWidth = 0.5; |
|
|
|
const drawLayer = (nodes, x, active) => { |
|
const spacing = canvas.height / (nodes.length + 1); |
|
return nodes.map((value, i) => { |
|
const y = spacing * (i + 1); |
|
ctx.beginPath(); |
|
ctx.arc(x, y, 4, 0, Math.PI * 2); |
|
ctx.fillStyle = `rgba(0,255,136,${active ? value : 0.3})`; |
|
ctx.fill(); |
|
return {x, y}; |
|
}); |
|
}; |
|
|
|
const inputNodes = drawLayer(inputs, 30, true); |
|
const hiddenNodes = drawLayer(hiddenOutputs, canvas.width/2, true); |
|
const outputNodes = drawLayer(finalOutputs, canvas.width - 30, true); |
|
|
|
|
|
ctx.beginPath(); |
|
inputNodes.forEach(input => { |
|
hiddenNodes.forEach(hidden => { |
|
ctx.moveTo(input.x, input.y); |
|
ctx.lineTo(hidden.x, hidden.y); |
|
}); |
|
}); |
|
hiddenNodes.forEach(hidden => { |
|
outputNodes.forEach(output => { |
|
ctx.moveTo(hidden.x, hidden.y); |
|
ctx.lineTo(output.x, output.y); |
|
}); |
|
}); |
|
ctx.stroke(); |
|
} |
|
|
|
async function startRadar() { |
|
try { |
|
if(!audioContext) { |
|
audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); |
|
const source = audioContext.createMediaStreamSource(stream); |
|
analyser = audioContext.createAnalyser(); |
|
analyser.fftSize = 64; |
|
source.connect(analyser); |
|
dataArray = new Uint8Array(analyser.frequencyBinCount); |
|
} |
|
|
|
isRunning = true; |
|
updateRadar(); |
|
} catch(err) { |
|
console.error('Error accessing microphone:', err); |
|
} |
|
} |
|
|
|
function stopRadar() { |
|
isRunning = false; |
|
} |
|
|
|
function updateRadar() { |
|
if(!isRunning) return; |
|
|
|
analyser.getByteFrequencyData(dataArray); |
|
|
|
|
|
const bars = spectrum.children; |
|
for(let i = 0; i < bars.length; i++) { |
|
const height = (dataArray[i] / 255) * 120; |
|
bars[i].style.height = height + 'px'; |
|
} |
|
|
|
|
|
const inputs = Array.from(dataArray).map(x => x / 255); |
|
|
|
|
|
const hiddenOutputs = network.inputLayer.map(p => p.predict(inputs)); |
|
const outputs = network.outputLayer.map(p => p.predict(hiddenOutputs)); |
|
|
|
|
|
drawNetwork(inputs, hiddenOutputs, outputs); |
|
|
|
|
|
outputs.forEach((output, i) => { |
|
if(output > 0.7) { |
|
createSignal(i); |
|
} |
|
}); |
|
|
|
|
|
const avgOutput = outputs.reduce((a,b) => a+b) / outputs.length; |
|
document.getElementById('networkStats').innerHTML = |
|
`Accuracy: ${Math.round(avgOutput * 100)}%<br>` + |
|
`Detections: ${outputs.filter(x => x > 0.7).length}<br>` + |
|
`Signal Strength: ${Math.round(inputs.reduce((a,b) => a+b) / inputs.length * 100)}`; |
|
|
|
if(isLearning) { |
|
const target = Array(OUTPUT_SIZE).fill(0); |
|
target[Math.floor(Math.random() * OUTPUT_SIZE)] = 1; |
|
network.train(inputs, target); |
|
} |
|
|
|
requestAnimationFrame(updateRadar); |
|
} |
|
|
|
function createSignal(type) { |
|
const radar = document.querySelector('.radar-container'); |
|
const angle = Math.random() * Math.PI * 2; |
|
const distance = Math.random() * (radar.offsetWidth / 3); |
|
|
|
const x = Math.cos(angle) * distance + radar.offsetWidth / 2; |
|
const y = Math.sin(angle) * distance + radar.offsetHeight / 2; |
|
|
|
const signal = document.createElement('div'); |
|
signal.className = 'signal'; |
|
signal.style.left = x + 'px'; |
|
signal.style.top = y + 'px'; |
|
radar.appendChild(signal); |
|
|
|
const echo = document.createElement('div'); |
|
echo.className = 'signal-echo'; |
|
echo.style.left = x + 'px'; |
|
echo.style.top = y + 'px'; |
|
radar.appendChild(echo); |
|
|
|
setTimeout(() => { |
|
signal.remove(); |
|
echo.remove(); |
|
}, 2000); |
|
} |
|
|
|
function trainPerceptrons() { |
|
let error = 0; |
|
for(let i = 0; i < 100; i++) { |
|
const inputs = Array(INPUT_SIZE).fill(0).map(() => Math.random()); |
|
const targets = Array(OUTPUT_SIZE).fill(0); |
|
targets[Math.floor(Math.random() * OUTPUT_SIZE)] = 1; |
|
error += network.train(inputs, targets); |
|
} |
|
document.getElementById('networkStats').innerHTML = |
|
`Training complete<br>Error: ${Math.round(error * 100) / 100}<br>Network ready`; |
|
} |
|
|
|
function toggleLearning() { |
|
isLearning = !isLearning; |
|
} |
|
</script> |
|
</body> |
|
</html> |