import type { Fn , Fn0 } from "@thi.ng/api" ; import { illegalArgs } from "@thi.ng/errors/illegal-arguments" ; import { add } from "./add.js" ; import type { Reducer , Transducer } from "./api.js" ; import { iterator1 } from "./iterator.js" ; import { map } from "./map.js" ; import { range } from "./range.js" ; import { range2d } from "./range2d.js" ; import { transduce } from "./transduce.js" ; import { zip } from "./zip.js" ; export type ConvolutionKernel1D = [ number , number ] [ ] ; export type ConvolutionKernel2D = [ number , [ number , number ] ] [ ] ; export type ConvolutionKernel3D = [ number , [ number , number , number ] ] [ ] ; export interface ConvolutionOpts < K > { /**
     * Current cell states
     */ src : ArrayLike < number > ; /**
     * Kernel weights
     */ weights ? : Iterable < number > ; /**
     * Convolution kernel, pre-build via `buildKernel*`
     */ kernel ? : K ; /**
     * Cell matrix width
     */ width : number ; /**
     * Kernel width (MUST be odd number)
     */ kwidth ? : number ; /**
     * True, if convolution is seamless / wraps around near edges.
     * Default: true
     */ wrap ? : boolean ; /**
     * Only used if `wrap = false`. Used as neighboring cell values when
     * processing edge cells. Default: 0
     */ border ? : number ; /**
     * Optional custom reducer to process convoluted results. Default:
     * `add`
     */ reduce ? : Fn0 < Reducer < number , number > > ; } export interface Convolution1DOpts extends ConvolutionOpts < ConvolutionKernel1D > { } export interface Convolution2DOpts extends ConvolutionOpts < ConvolutionKernel2D > { height : number ; kheight ? : number ; } export const buildKernel1d = ( weights , w ) : ConvolutionKernel1D => { const w2 = w >> 1 ; return [ ... zip ( weights , range ( - w2 , w2 + 1 ) ) ] ; } ; export const buildKernel2d = ( weights , w , h = w ) : ConvolutionKernel2D => { const w2 = w >> 1 ; const h2 = h >> 1 ; return [ ... zip ( weights , range2d ( - w2 , w2 + 1 , - h2 , h2 + 1 ) ) ] ; } ; const kernelLookup1d = ( src , x , width , wrap , border ) : Fn < [ number , number ] , number > => wrap ? ( { 0 : w , 1 : ox } ) => { const xx = x < - ox ? width + ox : x >= width - ox ? ox - 1 : x + ox ; return w * src [ xx ] ; } : ( { 0 : w , 1 : ox } ) => { return x < - ox || x >= width - ox ? border : w * src [ x + ox ] ; } ; const kernelLookup2d = ( src , x , y , width , height , wrap , border ) : Fn < [ number , [ number , number ] ] , number > => wrap ? ( { 0 : w , 1 : { 0 : ox , 1 : oy } } ) => { const xx = x < - ox ? width + ox : x >= width - ox ? ox - 1 : x + ox ; const yy = y < - oy ? height + oy : y >= height - oy ? oy - 1 : y + oy ; return w * src [ yy * width + xx ] ; } : ( { 0 : w , 1 : { 0 : ox , 1 : oy } } ) => { return x < - ox || y < - oy || x >= width - ox || y >= height - oy ? border : w * src [ ( y + oy ) * width + x + ox ] ; } ; const kernelError = ( ) => illegalArgs ( `no kernel or kernel config` ) ; export function convolve1d ( opts ) ; export function convolve1d ( opts , indices ) ; export function convolve1d ( opts , indices ? ) { if ( indices ) { return iterator1 ( convolve1d ( opts ) , indices ) ; } const { src , width } = opts ; const wrap = opts . wrap !== false ; const border = opts . border || 0 ; const rfn = opts . reduce || add ; let kernel = opts . kernel ; if ( ! kernel ) { ! ( opts . weights && opts . kwidth ) && kernelError ( ) ; kernel = buildKernel1d ( opts . weights ! , opts . kwidth ! ) ; } return map ( ( p ) => transduce ( map ( kernelLookup1d ( src , p , width , wrap , border ) ) , rfn ( ) , kernel ! ) ) ; } export function convolve2d ( opts ) ; export function convolve2d ( opts , indices ) ; export function convolve2d ( opts , indices ? ) { if ( indices ) { return iterator1 ( convolve2d ( opts ) , indices ) ; } const { src , width , height } = opts ; const wrap = opts . wrap !== false ; const border = opts . border || 0 ; const rfn = opts . reduce || add ; let kernel = opts . kernel ; if ( ! kernel ) { ! ( opts . weights && opts . kwidth && opts . kheight ) && kernelError ( ) ; kernel = buildKernel2d ( opts . weights ! , opts . kwidth ! , opts . kheight ! ) ; } return map ( ( p ) => transduce ( map ( kernelLookup2d ( src , p [ 0 ] , p [ 1 ] , width , height , wrap , border ) ) , rfn ( ) , kernel ! ) ) ; }