Download src/lib/convolution/engine.ts from TechAvenger/kernel-convolution-visualizer: direct link, hf CLI and curl.
- Browser
- Download file 5.27 kB
-
https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/a371b772986b5140963410baab96fbb379877010/src/lib/convolution/engine.ts
- Command line
-
hf download hf://spaces/TechAvenger/kernel-convolution-visualizer@a371b772986b5140963410baab96fbb379877010/src/lib/convolution/engine.ts
-
curl -L -o engine.ts https://huggingface.co/spaces/TechAvenger/kernel-convolution-visualizer/resolve/a371b772986b5140963410baab96fbb379877010/src/lib/convolution/engine.ts
5.27 kB
| import { PaddingMode, ConvolutionStep, ImageDataExtended } from '@/types' | |
| export function getPixel( | |
| image: ImageDataExtended, | |
| x: number, | |
| y: number, | |
| channel: number, | |
| padding: PaddingMode | |
| ): number { | |
| const { width, height, channels, data } = image | |
| let px = x | |
| let py = y | |
| 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) * channels + channel | |
| return data[index] / 255 | |
| } | |
| export function setPixel( | |
| image: ImageDataExtended, | |
| x: number, | |
| y: number, | |
| channel: number, | |
| value: number | |
| ): void { | |
| const { width, channels, data } = image | |
| if (x < 0 || x >= width || y < 0 || y >= image.height) return | |
| const index = (y * width + x) * channels + channel | |
| data[index] = Math.max(0, Math.min(255, Math.round(value * 255))) | |
| } | |
| export function* convolutionSteps( | |
| image: ImageDataExtended, | |
| kernel: number[][], | |
| padding: PaddingMode, | |
| normalize: boolean, | |
| onProgress?: (progress: number) => void | |
| ): Generator<ConvolutionStep> { | |
| const { width, height, channels } = image | |
| const kernelSize = kernel.length | |
| const halfKernel = Math.floor(kernelSize / 2) | |
| const totalPixels = width * height * channels | |
| let processedPixels = 0 | |
| const kernelSum = kernel.flat().reduce((a, b) => a + b, 0) | |
| const normalizeFactor = normalize && kernelSum !== 0 ? 1 / kernelSum : 1 | |
| const outputData = new Float32Array(width * height * channels) | |
| for (let y = 0; y < height; y++) { | |
| for (let x = 0; x < width; x++) { | |
| for (let c = 0; c < channels; c++) { | |
| const inputPatch: number[][] = [] | |
| const multiplications: number[][] = [] | |
| let sum = 0 | |
| for (let ky = 0; ky < kernelSize; ky++) { | |
| const patchRow: number[] = [] | |
| const multRow: number[] = [] | |
| for (let kx = 0; kx < kernelSize; kx++) { | |
| const px = x + kx - halfKernel | |
| const py = y + ky - halfKernel | |
| const pixelValue = getPixel(image, px, py, c, padding) | |
| const kernelValue = kernel[ky][kx] | |
| patchRow.push(pixelValue) | |
| const mult = pixelValue * kernelValue | |
| multRow.push(mult) | |
| sum += mult | |
| } | |
| inputPatch.push(patchRow) | |
| multiplications.push(multRow) | |
| } | |
| const outputValue = sum | |
| const normalizedValue = sum * normalizeFactor | |
| const outputIndex = (y * width + x) * channels + c | |
| outputData[outputIndex] = normalizedValue | |
| processedPixels++ | |
| const progress = processedPixels / totalPixels | |
| yield { | |
| x, | |
| y, | |
| channel: c, | |
| inputPatch, | |
| kernel, | |
| multiplications, | |
| sum: outputValue, | |
| outputValue: normalizedValue, | |
| normalizedValue, | |
| progress, | |
| } | |
| if (onProgress) onProgress(progress) | |
| } | |
| } | |
| } | |
| // Convert output to ImageData | |
| const outputImageData = new ImageData(width, height) | |
| for (let i = 0; i < outputData.length; i++) { | |
| outputImageData.data[i] = Math.max(0, Math.min(255, Math.round(outputData[i] * 255))) | |
| } | |
| // Final yield with complete output | |
| yield { | |
| x: -1, | |
| y: -1, | |
| channel: -1, | |
| inputPatch: [], | |
| kernel, | |
| multiplications: [], | |
| sum: 0, | |
| outputValue: 0, | |
| normalizedValue: 0, | |
| progress: 1, | |
| } | |
| } | |
| export function convolveImage( | |
| imageData: ImageData, | |
| kernel: number[][], | |
| padding: PaddingMode = 'zero', | |
| normalize: boolean = true | |
| ): ImageData { | |
| const extended: ImageDataExtended = { | |
| data: imageData.data, | |
| width: imageData.width, | |
| height: imageData.height, | |
| channels: 4, | |
| } | |
| const { width, height } = imageData | |
| const kernelSize = kernel.length | |
| const halfKernel = Math.floor(kernelSize / 2) | |
| const kernelSum = kernel.flat().reduce((a, b) => a + b, 0) | |
| const normalizeFactor = normalize && kernelSum !== 0 ? 1 / kernelSum : 1 | |
| const outputData = new Uint8ClampedArray(imageData.data.length) | |
| for (let y = 0; y < height; y++) { | |
| for (let x = 0; x < width; x++) { | |
| for (let c = 0; c < 3; c++) { | |
| let sum = 0 | |
| for (let ky = 0; ky < kernelSize; ky++) { | |
| for (let kx = 0; kx < kernelSize; kx++) { | |
| const px = x + kx - halfKernel | |
| const py = y + ky - halfKernel | |
| const pixelValue = getPixel(extended, px, py, c, padding) | |
| sum += pixelValue * kernel[ky][kx] | |
| } | |
| } | |
| const outputIndex = (y * width + x) * 4 + c | |
| outputData[outputIndex] = Math.max(0, Math.min(255, Math.round(sum * normalizeFactor * 255))) | |
| } | |
| outputData[(y * width + x) * 4 + 3] = 255 | |
| } | |
| } | |
| return new ImageData(outputData, imageData.width, imageData.height) | |
| } |