|
|
import React, { useEffect, useState } from 'react'; |
|
|
import PanelContainer from '../ui/PanelContainer'; |
|
|
import GlowingBox from '../ui/GlowingBox'; |
|
|
import FeatureMap from '../ui/FeatureMap'; |
|
|
import { ScanSearch, MoveRight } from 'lucide-react'; |
|
|
|
|
|
const ConvolutionPanel: React.FC = () => { |
|
|
const [kernelPosition, setKernelPosition] = useState({ x: 0, y: 0 }); |
|
|
const [isAnimating, setIsAnimating] = useState(true); |
|
|
|
|
|
|
|
|
const kernel = [ |
|
|
[-1, -1, -1], |
|
|
[-1, 8, -1], |
|
|
[-1, -1, -1] |
|
|
]; |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
if (!isAnimating) return; |
|
|
|
|
|
const maxPos = 5; |
|
|
const interval = setInterval(() => { |
|
|
setKernelPosition(prev => { |
|
|
const newX = prev.x + 1 > maxPos ? 0 : prev.x + 1; |
|
|
const newY = newX === 0 ? (prev.y + 1 > maxPos ? 0 : prev.y + 1) : prev.y; |
|
|
return { x: newX, y: newY }; |
|
|
}); |
|
|
}, 800); |
|
|
|
|
|
return () => clearInterval(interval); |
|
|
}, [isAnimating]); |
|
|
|
|
|
return ( |
|
|
<PanelContainer |
|
|
title="Convolution Operation" |
|
|
description="The convolution operation slides a filter (kernel) across the input image to detect features like edges, textures, or patterns." |
|
|
panelNumber={2} |
|
|
> |
|
|
<div className="flex flex-col lg:flex-row gap-8 items-center"> |
|
|
<div className="flex-1 flex flex-col items-center"> |
|
|
<h3 className="text-xl font-semibold text-gray-100 mb-4">Kernel Sliding</h3> |
|
|
|
|
|
<div className="relative"> |
|
|
{/* Input image with overlay */} |
|
|
<div className="grid grid-cols-8 grid-rows-8 w-full max-w-[280px] gap-[1px]"> |
|
|
{Array(64).fill(0).map((_, index) => { |
|
|
const x = index % 8; |
|
|
const y = Math.floor(index / 8); |
|
|
|
|
|
// Determine if cell is within the current kernel position |
|
|
const isInKernel = x >= kernelPosition.x && x < kernelPosition.x + 3 && |
|
|
y >= kernelPosition.y && y < kernelPosition.y + 3; |
|
|
|
|
|
return ( |
|
|
<div |
|
|
key={index} |
|
|
className={` |
|
|
aspect-square flex items-center justify-center |
|
|
${isInKernel |
|
|
? 'bg-purple-600/30 border border-purple-400' |
|
|
: 'bg-gray-800 border border-gray-700'} |
|
|
`} |
|
|
></div> |
|
|
); |
|
|
})} |
|
|
</div> |
|
|
|
|
|
{/* Animated kernel */} |
|
|
<div |
|
|
className="absolute w-[calc(37.5%)] h-[calc(37.5%)] border-2 border-cyan-400 rounded-md shadow-lg shadow-cyan-500/50 transition-all duration-300 pointer-events-none" |
|
|
style={{ |
|
|
top: `${kernelPosition.y * 12.5}%`, |
|
|
left: `${kernelPosition.x * 12.5}%` |
|
|
}} |
|
|
> |
|
|
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-cyan-400"> |
|
|
<ScanSearch size={24} /> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<button |
|
|
className="mt-4 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md text-sm" |
|
|
onClick={() => setIsAnimating(!isAnimating)} |
|
|
> |
|
|
{isAnimating ? 'Pause Animation' : 'Resume Animation'} |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div className="hidden lg:flex items-center justify-center"> |
|
|
<MoveRight size={40} className="text-gray-500" /> |
|
|
</div> |
|
|
|
|
|
<div className="flex-1 flex flex-col items-center"> |
|
|
<div className="flex gap-8 items-start"> |
|
|
<div> |
|
|
<h3 className="text-xl font-semibold text-gray-100 mb-4">Kernel/Filter</h3> |
|
|
<GlowingBox color="from-purple-500 to-blue-500" className="max-w-[150px]"> |
|
|
<div className="grid grid-cols-3 gap-1"> |
|
|
{kernel.flat().map((value, i) => ( |
|
|
<div |
|
|
key={i} |
|
|
className="aspect-square flex items-center justify-center bg-gray-900 text-sm font-mono" |
|
|
> |
|
|
{value} |
|
|
</div> |
|
|
))} |
|
|
</div> |
|
|
</GlowingBox> |
|
|
<p className="mt-2 text-sm text-gray-400 max-w-[150px]">Edge detection filter</p> |
|
|
</div> |
|
|
|
|
|
<div> |
|
|
<h3 className="text-xl font-semibold text-gray-100 mb-4">Feature Map</h3> |
|
|
<GlowingBox color="from-blue-500 to-teal-500" className="max-w-[200px]"> |
|
|
<FeatureMap |
|
|
size={6} |
|
|
highlightPosition={kernelPosition.x < 6 && kernelPosition.y < 6 ? |
|
|
{ x: kernelPosition.x, y: kernelPosition.y } : null} |
|
|
/> |
|
|
</GlowingBox> |
|
|
<p className="mt-2 text-sm text-gray-400 max-w-[200px]"> |
|
|
Resulting feature map from convolution operation |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</PanelContainer> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default ConvolutionPanel; |