claude made convolution.ts and updated server.ts
This commit is contained in:
parent
2d4348906c
commit
1dd4a71527
2 changed files with 3599 additions and 0 deletions
398
convolution.ts
Normal file
398
convolution.ts
Normal file
|
|
@ -0,0 +1,398 @@
|
|||
// convolution.ts - Convolution operations for 1D and 2D data
|
||||
|
||||
export interface ConvolutionOptions {
|
||||
mode?: 'full' | 'same' | 'valid';
|
||||
boundary?: 'zero' | 'reflect' | 'symmetric';
|
||||
}
|
||||
|
||||
export interface ConvolutionResult1D {
|
||||
values: number[];
|
||||
originalLength: number;
|
||||
kernelLength: number;
|
||||
mode: string;
|
||||
}
|
||||
|
||||
export interface ConvolutionResult2D {
|
||||
matrix: number[][];
|
||||
originalDimensions: [number, number];
|
||||
kernelDimensions: [number, number];
|
||||
mode: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input array for convolution operations
|
||||
*/
|
||||
function validateArray(arr: number[], name: string): void {
|
||||
if (!Array.isArray(arr) || arr.length === 0) {
|
||||
throw new Error(`${name} must be a non-empty array`);
|
||||
}
|
||||
if (arr.some(val => typeof val !== 'number' || !isFinite(val))) {
|
||||
throw new Error(`${name} must contain only finite numbers`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates 2D matrix for convolution operations
|
||||
*/
|
||||
function validateMatrix(matrix: number[][], name: string): void {
|
||||
if (!Array.isArray(matrix) || matrix.length === 0) {
|
||||
throw new Error(`${name} must be a non-empty 2D array`);
|
||||
}
|
||||
|
||||
const rowLength = matrix[0].length;
|
||||
if (rowLength === 0) {
|
||||
throw new Error(`${name} rows must be non-empty`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < matrix.length; i++) {
|
||||
if (!Array.isArray(matrix[i]) || matrix[i].length !== rowLength) {
|
||||
throw new Error(`${name} must be a rectangular matrix`);
|
||||
}
|
||||
if (matrix[i].some(val => typeof val !== 'number' || !isFinite(val))) {
|
||||
throw new Error(`${name} must contain only finite numbers`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies boundary conditions to extend an array
|
||||
*/
|
||||
function applyBoundary1D(signal: number[], padding: number, boundary: string): number[] {
|
||||
if (padding <= 0) return signal;
|
||||
|
||||
let result = [...signal];
|
||||
|
||||
switch (boundary) {
|
||||
case 'zero':
|
||||
result = new Array(padding).fill(0).concat(result).concat(new Array(padding).fill(0));
|
||||
break;
|
||||
case 'reflect':
|
||||
const leftPad = [];
|
||||
const rightPad = [];
|
||||
for (let i = 0; i < padding; i++) {
|
||||
leftPad.unshift(signal[Math.min(i + 1, signal.length - 1)]);
|
||||
rightPad.push(signal[Math.max(signal.length - 2 - i, 0)]);
|
||||
}
|
||||
result = leftPad.concat(result).concat(rightPad);
|
||||
break;
|
||||
case 'symmetric':
|
||||
const leftSymPad = [];
|
||||
const rightSymPad = [];
|
||||
for (let i = 0; i < padding; i++) {
|
||||
leftSymPad.unshift(signal[Math.min(i, signal.length - 1)]);
|
||||
rightSymPad.push(signal[Math.max(signal.length - 1 - i, 0)]);
|
||||
}
|
||||
result = leftSymPad.concat(result).concat(rightSymPad);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported boundary condition: ${boundary}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs 1D convolution between signal and kernel
|
||||
*
|
||||
* @param signal - Input signal array
|
||||
* @param kernel - Convolution kernel array
|
||||
* @param options - Convolution options (mode, boundary)
|
||||
* @returns Convolution result with metadata
|
||||
*/
|
||||
export function convolve1D(
|
||||
signal: number[],
|
||||
kernel: number[],
|
||||
options: ConvolutionOptions = {}
|
||||
): ConvolutionResult1D {
|
||||
validateArray(signal, 'Signal');
|
||||
validateArray(kernel, 'Kernel');
|
||||
|
||||
const { mode = 'full', boundary = 'zero' } = options;
|
||||
|
||||
// Flip kernel for convolution (not correlation)
|
||||
const flippedKernel = [...kernel].reverse();
|
||||
|
||||
const signalLen = signal.length;
|
||||
const kernelLen = flippedKernel.length;
|
||||
|
||||
let result: number[] = [];
|
||||
let paddedSignal = signal;
|
||||
|
||||
// Apply boundary conditions based on mode
|
||||
if (mode === 'same' || mode === 'full') {
|
||||
const padding = mode === 'same' ? Math.floor(kernelLen / 2) : kernelLen - 1;
|
||||
paddedSignal = applyBoundary1D(signal, padding, boundary);
|
||||
}
|
||||
|
||||
// Perform convolution
|
||||
const outputLength = mode === 'full' ? signalLen + kernelLen - 1 :
|
||||
mode === 'same' ? signalLen :
|
||||
signalLen - kernelLen + 1;
|
||||
|
||||
const startIdx = mode === 'valid' ? 0 :
|
||||
mode === 'same' ? Math.floor(kernelLen / 2) : 0;
|
||||
|
||||
for (let i = 0; i < outputLength; i++) {
|
||||
let sum = 0;
|
||||
for (let j = 0; j < kernelLen; j++) {
|
||||
const signalIdx = startIdx + i + j;
|
||||
if (signalIdx >= 0 && signalIdx < paddedSignal.length) {
|
||||
sum += paddedSignal[signalIdx] * flippedKernel[j];
|
||||
}
|
||||
}
|
||||
result.push(sum);
|
||||
}
|
||||
|
||||
return {
|
||||
values: result,
|
||||
originalLength: signalLen,
|
||||
kernelLength: kernelLen,
|
||||
mode
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs 2D convolution between matrix and kernel
|
||||
*
|
||||
* @param matrix - Input 2D matrix
|
||||
* @param kernel - 2D convolution kernel
|
||||
* @param options - Convolution options (mode, boundary)
|
||||
* @returns 2D convolution result with metadata
|
||||
*/
|
||||
export function convolve2D(
|
||||
matrix: number[][],
|
||||
kernel: number[][],
|
||||
options: ConvolutionOptions = {}
|
||||
): ConvolutionResult2D {
|
||||
validateMatrix(matrix, 'Matrix');
|
||||
validateMatrix(kernel, 'Kernel');
|
||||
|
||||
const { mode = 'full', boundary = 'zero' } = options;
|
||||
|
||||
// Flip kernel for convolution
|
||||
const flippedKernel = kernel.map(row => [...row].reverse()).reverse();
|
||||
|
||||
const matrixRows = matrix.length;
|
||||
const matrixCols = matrix[0].length;
|
||||
const kernelRows = flippedKernel.length;
|
||||
const kernelCols = flippedKernel[0].length;
|
||||
|
||||
// Calculate output dimensions
|
||||
let outputRows: number, outputCols: number;
|
||||
let padTop: number, padLeft: number;
|
||||
|
||||
switch (mode) {
|
||||
case 'full':
|
||||
outputRows = matrixRows + kernelRows - 1;
|
||||
outputCols = matrixCols + kernelCols - 1;
|
||||
padTop = kernelRows - 1;
|
||||
padLeft = kernelCols - 1;
|
||||
break;
|
||||
case 'same':
|
||||
outputRows = matrixRows;
|
||||
outputCols = matrixCols;
|
||||
padTop = Math.floor(kernelRows / 2);
|
||||
padLeft = Math.floor(kernelCols / 2);
|
||||
break;
|
||||
case 'valid':
|
||||
outputRows = Math.max(0, matrixRows - kernelRows + 1);
|
||||
outputCols = Math.max(0, matrixCols - kernelCols + 1);
|
||||
padTop = 0;
|
||||
padLeft = 0;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported convolution mode: ${mode}`);
|
||||
}
|
||||
|
||||
// Create padded matrix based on boundary conditions
|
||||
const totalPadRows = mode === 'valid' ? 0 : kernelRows - 1;
|
||||
const totalPadCols = mode === 'valid' ? 0 : kernelCols - 1;
|
||||
|
||||
const paddedMatrix: number[][] = [];
|
||||
|
||||
// Initialize padded matrix with boundary conditions
|
||||
for (let i = -padTop; i < matrixRows + totalPadRows - padTop; i++) {
|
||||
const row: number[] = [];
|
||||
for (let j = -padLeft; j < matrixCols + totalPadCols - padLeft; j++) {
|
||||
let value = 0;
|
||||
|
||||
if (i >= 0 && i < matrixRows && j >= 0 && j < matrixCols) {
|
||||
value = matrix[i][j];
|
||||
} else if (boundary !== 'zero') {
|
||||
// Apply boundary conditions
|
||||
let boundaryI = i;
|
||||
let boundaryJ = j;
|
||||
|
||||
if (boundary === 'reflect') {
|
||||
boundaryI = i < 0 ? -i - 1 : i >= matrixRows ? 2 * matrixRows - i - 1 : i;
|
||||
boundaryJ = j < 0 ? -j - 1 : j >= matrixCols ? 2 * matrixCols - j - 1 : j;
|
||||
} else if (boundary === 'symmetric') {
|
||||
boundaryI = i < 0 ? -i : i >= matrixRows ? 2 * matrixRows - i - 2 : i;
|
||||
boundaryJ = j < 0 ? -j : j >= matrixCols ? 2 * matrixCols - j - 2 : j;
|
||||
}
|
||||
|
||||
boundaryI = Math.max(0, Math.min(boundaryI, matrixRows - 1));
|
||||
boundaryJ = Math.max(0, Math.min(boundaryJ, matrixCols - 1));
|
||||
value = matrix[boundaryI][boundaryJ];
|
||||
}
|
||||
|
||||
row.push(value);
|
||||
}
|
||||
paddedMatrix.push(row);
|
||||
}
|
||||
|
||||
// Perform 2D convolution
|
||||
const result: number[][] = [];
|
||||
|
||||
for (let i = 0; i < outputRows; i++) {
|
||||
const row: number[] = [];
|
||||
for (let j = 0; j < outputCols; j++) {
|
||||
let sum = 0;
|
||||
|
||||
for (let ki = 0; ki < kernelRows; ki++) {
|
||||
for (let kj = 0; kj < kernelCols; kj++) {
|
||||
const matrixI = i + ki;
|
||||
const matrixJ = j + kj;
|
||||
|
||||
if (matrixI >= 0 && matrixI < paddedMatrix.length &&
|
||||
matrixJ >= 0 && matrixJ < paddedMatrix[0].length) {
|
||||
sum += paddedMatrix[matrixI][matrixJ] * flippedKernel[ki][kj];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
row.push(sum);
|
||||
}
|
||||
result.push(row);
|
||||
}
|
||||
|
||||
return {
|
||||
matrix: result,
|
||||
originalDimensions: [matrixRows, matrixCols],
|
||||
kernelDimensions: [kernelRows, kernelCols],
|
||||
mode
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates common convolution kernels
|
||||
*/
|
||||
export class ConvolutionKernels {
|
||||
/**
|
||||
* Creates a Gaussian blur kernel
|
||||
*/
|
||||
static gaussian(size: number, sigma: number = 1.0): number[][] {
|
||||
if (size % 2 === 0) {
|
||||
throw new Error('Kernel size must be odd');
|
||||
}
|
||||
|
||||
const kernel: number[][] = [];
|
||||
const center = Math.floor(size / 2);
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
const row: number[] = [];
|
||||
for (let j = 0; j < size; j++) {
|
||||
const x = i - center;
|
||||
const y = j - center;
|
||||
const value = Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
|
||||
row.push(value);
|
||||
sum += value;
|
||||
}
|
||||
kernel.push(row);
|
||||
}
|
||||
|
||||
// Normalize kernel
|
||||
return kernel.map(row => row.map(val => val / sum));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Sobel edge detection kernel
|
||||
*/
|
||||
static sobel(direction: 'x' | 'y' = 'x'): number[][] {
|
||||
if (direction === 'x') {
|
||||
return [
|
||||
[-1, 0, 1],
|
||||
[-2, 0, 2],
|
||||
[-1, 0, 1]
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
[-1, -2, -1],
|
||||
[ 0, 0, 0],
|
||||
[ 1, 2, 1]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Laplacian edge detection kernel
|
||||
*/
|
||||
static laplacian(): number[][] {
|
||||
return [
|
||||
[ 0, -1, 0],
|
||||
[-1, 4, -1],
|
||||
[ 0, -1, 0]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a box/average blur kernel
|
||||
*/
|
||||
static box(size: number): number[][] {
|
||||
if (size % 2 === 0) {
|
||||
throw new Error('Kernel size must be odd');
|
||||
}
|
||||
|
||||
const value = 1 / (size * size);
|
||||
const kernel: number[][] = [];
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
kernel.push(new Array(size).fill(value));
|
||||
}
|
||||
|
||||
return kernel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 1D Gaussian kernel
|
||||
*/
|
||||
static gaussian1D(size: number, sigma: number = 1.0): number[] {
|
||||
if (size % 2 === 0) {
|
||||
throw new Error('Kernel size must be odd');
|
||||
}
|
||||
|
||||
const kernel: number[] = [];
|
||||
const center = Math.floor(size / 2);
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0; i < size; i++) {
|
||||
const x = i - center;
|
||||
const value = Math.exp(-(x * x) / (2 * sigma * sigma));
|
||||
kernel.push(value);
|
||||
sum += value;
|
||||
}
|
||||
|
||||
// Normalize kernel
|
||||
return kernel.map(val => val / sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 1D difference kernel for edge detection
|
||||
*/
|
||||
static difference1D(): number[] {
|
||||
return [-1, 0, 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a 1D moving average kernel
|
||||
*/
|
||||
static average1D(size: number): number[] {
|
||||
if (size <= 0) {
|
||||
throw new Error('Kernel size must be positive');
|
||||
}
|
||||
|
||||
const value = 1 / size;
|
||||
return new Array(size).fill(value);
|
||||
}
|
||||
}
|
||||
3201
server_addconvolution.ts
Normal file
3201
server_addconvolution.ts
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue