Download src/components/controls/OverlayControls.tsx from TechAvenger/kernel-convolution-visualizer: direct link, hf CLI and curl.
- Browser
- Download file 9.05 kB
-
https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/b43cf2ad48d9ffbc7ae4391ae06b1d7e327e1112/src/components/controls/OverlayControls.tsx
- Command line
-
hf download hf://spaces/TechAvenger/kernel-convolution-visualizer@b43cf2ad48d9ffbc7ae4391ae06b1d7e327e1112/src/components/controls/OverlayControls.tsx
-
curl -L -o OverlayControls.tsx https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/b43cf2ad48d9ffbc7ae4391ae06b1d7e327e1112/src/components/controls/OverlayControls.tsx
9.05 kB
| 'use client' | |
| import { useRef, useEffect } from 'react' | |
| import { useAppStore } from '@/store' | |
| import { PADDING_MODES } from '@/lib/kernels' | |
| import { Select, Card, CardContent } from '@/components/ui' | |
| import { cn } from '@/utils/helpers' | |
| import { Settings, Layers, Grid, ZoomIn, ZoomOut } from 'lucide-react' | |
| export function OverlayControls() { | |
| const { settings, updateSettings, toggleOverlay, setPadding } = useAppStore() | |
| const { showOverlay, padding } = settings | |
| return ( | |
| <Card className="w-full"> | |
| <CardContent className="p-4 space-y-4"> | |
| <h3 className="font-medium flex items-center gap-2"> | |
| <Settings className="h-4 w-4" /> | |
| Visualization Overlays | |
| </h3> | |
| <div className="grid grid-cols-2 gap-2"> | |
| {Object.entries(showOverlay).map(([key, value]) => ( | |
| <label key={key} className="flex items-center gap-2 cursor-pointer"> | |
| <input | |
| type="checkbox" | |
| checked={value} | |
| onChange={() => toggleOverlay(key as keyof typeof showOverlay)} | |
| className="rounded border-gray-300" | |
| /> | |
| <span className="text-sm capitalize">{key.replace(/([A-Z])/g, ' $1')}</span> | |
| </label> | |
| ))} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ) | |
| } | |
| export function PaddingControls() { | |
| const { settings, setPadding, originalImageData, selectedKernel } = useAppStore() | |
| const { padding } = settings | |
| return ( | |
| <Card className="w-full"> | |
| <CardContent className="p-4 space-y-4"> | |
| <h3 className="font-medium flex items-center gap-2"> | |
| <Grid className="h-4 w-4" /> | |
| Padding Strategy | |
| </h3> | |
| <Select | |
| value={padding} | |
| onChange={(e) => setPadding(e.target.value as typeof padding)} | |
| options={PADDING_MODES.map((p) => ({ value: p.value, label: p.label }))} | |
| placeholder="Select padding mode" | |
| /> | |
| <div className="text-sm text-gray-500 dark:text-gray-400"> | |
| {PADDING_MODES.find((p) => p.value === padding)?.description} | |
| </div> | |
| <PaddingVisualization | |
| imageData={originalImageData} | |
| padding={padding} | |
| kernelSize={selectedKernel?.size ?? 3} | |
| /> | |
| </CardContent> | |
| </Card> | |
| ) | |
| } | |
| function PaddingVisualization({ | |
| imageData, | |
| padding, | |
| kernelSize | |
| }: { | |
| imageData: ImageData | null | |
| padding: 'zero' | 'reflect' | 'replicate' | 'wrap' | |
| kernelSize: number | |
| }) { | |
| const canvasRef = useRef<HTMLCanvasElement>(null) | |
| const halfKernel = Math.floor(kernelSize / 2) | |
| const previewSize = 120 | |
| useEffect(() => { | |
| if (!imageData || !canvasRef.current) return | |
| const canvas = canvasRef.current | |
| const ctx = canvas.getContext('2d') | |
| if (!ctx) return | |
| // Small preview canvas | |
| canvas.width = previewSize | |
| canvas.height = previewSize | |
| ctx.clearRect(0, 0, previewSize, previewSize) | |
| const scale = Math.min(previewSize / (imageData.width + 2 * halfKernel), previewSize / (imageData.height + 2 * halfKernel)) | |
| const paddedWidth = (imageData.width + 2 * halfKernel) * scale | |
| const paddedHeight = (imageData.height + 2 * halfKernel) * scale | |
| const offsetX = (previewSize - paddedWidth) / 2 | |
| const offsetY = (previewSize - paddedHeight) / 2 | |
| // Draw padding visualization | |
| const getPixel = (x: number, y: number, channel: number): number => { | |
| let px = x | |
| let py = y | |
| const { width, height, data } = imageData | |
| switch (padding) { | |
| case 'zero': | |
| if (px < 0 || px >= width || py < 0 || py >= height) return 0 | |
| break | |
| case 'replicate': | |
| px = Math.max(0, Math.min(px, width - 1)) | |
| py = Math.max(0, Math.min(py, height - 1)) | |
| break | |
| case 'reflect': | |
| if (px < 0) px = -px | |
| if (px >= width) px = 2 * width - px - 2 | |
| if (py < 0) py = -py | |
| if (py >= height) py = 2 * height - py - 2 | |
| px = Math.max(0, Math.min(px, width - 1)) | |
| py = Math.max(0, Math.min(py, height - 1)) | |
| break | |
| case 'wrap': | |
| px = ((px % width) + width) % width | |
| py = ((py % height) + height) % height | |
| break | |
| } | |
| const index = (py * width + px) * 4 + channel | |
| return data[index] | |
| } | |
| // Draw padded image | |
| for (let py = -halfKernel; py < imageData.height + halfKernel; py++) { | |
| for (let px = -halfKernel; px < imageData.width + halfKernel; px++) { | |
| const r = getPixel(px, py, 0) | |
| const g = getPixel(px, py, 1) | |
| const b = getPixel(px, py, 2) | |
| const x = offsetX + (px + halfKernel) * scale | |
| const y = offsetY + (py + halfKernel) * scale | |
| ctx.fillStyle = `rgb(${r}, ${g}, ${b})` | |
| ctx.fillRect(x, y, Math.ceil(scale), Math.ceil(scale)) | |
| } | |
| } | |
| // Draw original image border | |
| ctx.strokeStyle = 'rgba(59, 130, 246, 0.8)' | |
| ctx.lineWidth = 2 | |
| ctx.setLineDash([4, 2]) | |
| ctx.strokeRect( | |
| offsetX + halfKernel * scale, | |
| offsetY + halfKernel * scale, | |
| imageData.width * scale, | |
| imageData.height * scale | |
| ) | |
| ctx.setLineDash([]) | |
| // Draw kernel center indicator | |
| ctx.fillStyle = 'rgba(239, 68, 68, 0.8)' | |
| ctx.beginPath() | |
| ctx.arc( | |
| offsetX + halfKernel * scale, | |
| offsetY + halfKernel * scale, | |
| 4, | |
| 0, | |
| Math.PI * 2 | |
| ) | |
| ctx.fill() | |
| // Draw kernel area outline | |
| ctx.strokeStyle = 'rgba(239, 68, 68, 0.5)' | |
| ctx.lineWidth = 1 | |
| ctx.strokeRect( | |
| offsetX, | |
| offsetY, | |
| paddedWidth, | |
| paddedHeight | |
| ) | |
| }, [imageData, padding, kernelSize]) | |
| return ( | |
| <div className="space-y-2"> | |
| <h4 className="text-xs font-medium text-gray-500 dark:text-gray-400">Padding Preview</h4> | |
| <div className="relative"> | |
| <canvas | |
| ref={canvasRef} | |
| width={previewSize} | |
| height={previewSize} | |
| className="bg-gray-100 dark:bg-gray-800 rounded border dark:border-gray-700" | |
| /> | |
| <div className="absolute bottom-1 left-1 right-1 flex justify-between text-[10px] text-gray-400 px-2"> | |
| <span>Original Image</span> | |
| <span className="text-red-400">Kernel Center</span> | |
| <span>Padding Area</span> | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| export function KernelInfoPanel() { | |
| const { selectedKernel } = useAppStore() | |
| if (!selectedKernel) { | |
| return ( | |
| <Card className="w-full"> | |
| <CardContent className="p-4 text-center text-gray-500"> | |
| No kernel selected | |
| </CardContent> | |
| </Card> | |
| ) | |
| } | |
| const { name, category, matrix, size, normalize, description, formula, reference } = selectedKernel | |
| return ( | |
| <Card className="w-full"> | |
| <CardContent className="p-4 space-y-4"> | |
| <div className="flex items-start justify-between gap-4"> | |
| <div> | |
| <h3 className="font-medium">{name}</h3> | |
| <div className="flex items-center gap-2 mt-1"> | |
| <span className="px-2 py-0.5 text-xs bg-blue-100 text-blue-800 rounded dark:bg-blue-900/30 dark:text-blue-300"> | |
| {category} | |
| </span> | |
| <span className="px-2 py-0.5 text-xs bg-gray-100 text-gray-800 rounded dark:bg-gray-800 dark:text-gray-300"> | |
| {size}×{size} | |
| </span> | |
| {normalize && ( | |
| <span className="px-2 py-0.5 text-xs bg-green-100 text-green-800 rounded dark:bg-green-900/30 dark:text-green-300"> | |
| Normalized | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| <p className="text-sm text-gray-600 dark:text-gray-400">{description}</p> | |
| {formula && ( | |
| <div className="p-3 bg-gray-50 dark:bg-gray-800 rounded font-mono text-sm"> | |
| <span className="text-gray-500">Formula: </span> | |
| <span>{formula}</span> | |
| </div> | |
| )} | |
| <div className="space-y-2"> | |
| <h4 className="text-sm font-medium">Kernel Matrix</h4> | |
| <div className="grid gap-1" style={{ gridTemplateColumns: `repeat(${size}, 1fr)` }}> | |
| {matrix.map((row, i) => | |
| row.map((val, j) => ( | |
| <div | |
| key={`${i}-${j}`} | |
| className={cn( | |
| 'p-2 text-center text-sm font-mono rounded', | |
| val > 0 ? 'bg-green-50 text-green-800 dark:bg-green-900/30 dark:text-green-300' : 'bg-red-50 text-red-800 dark:bg-red-900/30 dark:text-red-300' | |
| )} | |
| > | |
| {val.toFixed(val === Math.round(val) ? 0 : 2)} | |
| </div> | |
| )) | |
| )} | |
| </div> | |
| </div> | |
| {reference && ( | |
| <a | |
| href={reference} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="text-sm text-blue-600 hover:underline flex items-center gap-1" | |
| > | |
| <Layers className="h-3 w-3" /> | |
| Reference | |
| </a> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ) | |
| } |