| | <!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> |