From bd48ed8c6add01e42b834af6a0856ce59d122977 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 11:30:20 +0200 Subject: [PATCH 01/10] refactor(imgproc): de-duplicate imgproc module (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second de-duplication step of #47, following the pattern proven in #62. - src/imgproc/imgproc.ts: replace the type-only stub with the REAL implementation moved verbatim from the monolith (grayscale, resample, box_blur_gray, gaussian_blur, hough_transform, pyrdown, scharr/sobel derivatives, compute_integral_image, equalize_histogram, canny, warp_perspective, warp_affine, skindetector). Only deliberate change: gaussian_blur instantiates the math module directly (import from ../math/math) instead of via the jsfeatNext.math static slot, removing an attach-order dependency on the aggregator. - src/jsfeatNext.ts: shrinks by ~1050 lines; attaches imgproc from its module (jsfeatNext.imgproc = imgproc). - Side effect: the latent trap in src/orb/rectify_patch.ts (it imports imgproc, which until now was the throwing stub) is healed — it resolves to the real implementation. Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (14 of those pin imgproc bit-for-bit vs original jsfeat); UMD build checked (instanceof chain, static-constant inheritance, grayscale + gaussian_blur smoke on the bundle). Co-Authored-By: Claude Fable 5 --- src/imgproc/imgproc.ts | 1052 +++++++++++++++++++++++++++++++++++++++- src/jsfeatNext.ts | 1052 +--------------------------------------- 2 files changed, 1037 insertions(+), 1067 deletions(-) diff --git a/src/imgproc/imgproc.ts b/src/imgproc/imgproc.ts index 8aaf57b..d8163e1 100644 --- a/src/imgproc/imgproc.ts +++ b/src/imgproc/imgproc.ts @@ -1,45 +1,1065 @@ +import jsfeatNext from "../core/core"; import { matrix_t } from "../matrix_t/matrix_t"; -export class imgproc { +import { JSFEAT_CONSTANTS } from "../constants/constants"; +import { _resample, _resample_u8 } from "./resample"; +import { _convol, _convol_u8 } from "./convol"; +import { math } from "../math/math"; + +/** + * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). + * This file previously held a type-only stub whose methods threw + * "Method not implemented." — the implementation below is the inline code + * from the monolith, verbatim (the only change: gaussian_blur instantiates + * the math module directly instead of via the jsfeatNext.math static slot). + */ +export class imgproc extends jsfeatNext { + constructor() { + super(); + } + grayscale(src: Uint8Array | Uint8ClampedArray, w: number, h: number, dst: matrix_t, code?: number): void { - throw new Error("Method not implemented."); + // this is default image data representation in browser + if (typeof code === "undefined") { + code = JSFEAT_CONSTANTS.COLOR_RGBA2GRAY; + } + let x = 0, + y = 0, + i = 0, + j = 0, + ir = 0, + jr = 0; + let coeff_r = 4899, + coeff_g = 9617, + coeff_b = 1868, + cn = 4; + + if (code == JSFEAT_CONSTANTS.COLOR_BGRA2GRAY || code == JSFEAT_CONSTANTS.COLOR_BGR2GRAY) { + coeff_r = 1868; + coeff_b = 4899; + } + if (code == JSFEAT_CONSTANTS.COLOR_RGB2GRAY || code == JSFEAT_CONSTANTS.COLOR_BGR2GRAY) { + cn = 3; + } + const cn2 = cn << 1, + cn3 = (cn * 3) | 0; + + dst.resize(w, h, 1); + const dst_u8 = dst.data; + + for (y = 0; y < h; ++y, j += w, i += w * cn) { + for (x = 0, ir = i, jr = j; x <= w - 4; x += 4, ir += cn << 2, jr += 4) { + dst_u8[jr] = (src[ir] * coeff_r + src[ir + 1] * coeff_g + src[ir + 2] * coeff_b + 8192) >> 14; + dst_u8[jr + 1] = + (src[ir + cn] * coeff_r + src[ir + cn + 1] * coeff_g + src[ir + cn + 2] * coeff_b + 8192) >> 14; + dst_u8[jr + 2] = + (src[ir + cn2] * coeff_r + src[ir + cn2 + 1] * coeff_g + src[ir + cn2 + 2] * coeff_b + 8192) >> 14; + dst_u8[jr + 3] = + (src[ir + cn3] * coeff_r + src[ir + cn3 + 1] * coeff_g + src[ir + cn3 + 2] * coeff_b + 8192) >> 14; + } + for (; x < w; ++x, ++jr, ir += cn) { + dst_u8[jr] = (src[ir] * coeff_r + src[ir + 1] * coeff_g + src[ir + 2] * coeff_b + 8192) >> 14; + } + } } + + // derived from CCV library resample(src: matrix_t, dst: matrix_t, nw: number, nh: number): void { - throw new Error("Method not implemented."); + const h = src.rows, + w = src.cols; + if (h > nh && w > nw) { + dst.resize(nw, nh, src.channel); + // using the fast alternative (fix point scale, 0x100 to avoid overflow) + if (src.type & JSFEAT_CONSTANTS.U8_t && dst.type & JSFEAT_CONSTANTS.U8_t && (h * w) / (nh * nw) < 0x100) { + _resample_u8(src, dst, this.cache, nw, nh); + } else { + _resample(src, dst, this.cache, nw, nh); + } + } } + box_blur_gray(src: matrix_t, dst: matrix_t, radius: number, options: number): void { - throw new Error("Method not implemented."); + if (typeof options === "undefined") { + options = 0; + } + const w = src.cols, + h = src.rows, + h2 = h << 1, + w2 = w << 1; + let i = 0, + x = 0, + y = 0, + end = 0; + const windowSize = ((radius << 1) + 1) | 0; + const radiusPlusOne = (radius + 1) | 0, + radiusPlus2 = (radiusPlusOne + 1) | 0; + const scale = options & JSFEAT_CONSTANTS.BOX_BLUR_NOSCALE ? 1 : 1.0 / (windowSize * windowSize); + + const tmp_buff = this.cache.get_buffer((w * h) << 2); + + let sum = 0, + dstIndex = 0, + srcIndex = 0, + nextPixelIndex = 0, + previousPixelIndex = 0; + const data_i32 = tmp_buff.i32; // to prevent overflow + let data_u8 = src.data; + let hold = 0; + + dst.resize(w, h, src.channel); + + // first pass + // no need to scale + //data_u8 = src.data; + //data_i32 = tmp; + for (y = 0; y < h; ++y) { + dstIndex = y; + sum = radiusPlusOne * data_u8[srcIndex]; + + for (i = (srcIndex + 1) | 0, end = (srcIndex + radius) | 0; i <= end; ++i) { + sum += data_u8[i]; + } + + nextPixelIndex = (srcIndex + radiusPlusOne) | 0; + previousPixelIndex = srcIndex; + hold = data_u8[previousPixelIndex]; + for (x = 0; x < radius; ++x, dstIndex += h) { + data_i32[dstIndex] = sum; + sum += data_u8[nextPixelIndex] - hold; + nextPixelIndex++; + } + for (; x < w - radiusPlus2; x += 2, dstIndex += h2) { + data_i32[dstIndex] = sum; + sum += data_u8[nextPixelIndex] - data_u8[previousPixelIndex]; + + data_i32[dstIndex + h] = sum; + sum += data_u8[nextPixelIndex + 1] - data_u8[previousPixelIndex + 1]; + + nextPixelIndex += 2; + previousPixelIndex += 2; + } + for (; x < w - radiusPlusOne; ++x, dstIndex += h) { + data_i32[dstIndex] = sum; + sum += data_u8[nextPixelIndex] - data_u8[previousPixelIndex]; + + nextPixelIndex++; + previousPixelIndex++; + } + + hold = data_u8[nextPixelIndex - 1]; + for (; x < w; ++x, dstIndex += h) { + data_i32[dstIndex] = sum; + + sum += hold - data_u8[previousPixelIndex]; + previousPixelIndex++; + } + + srcIndex += w; + } + // + // second pass + srcIndex = 0; + //data_i32 = tmp; // this is a transpose + data_u8 = dst.data; + + // dont scale result + if (scale == 1) { + for (y = 0; y < w; ++y) { + dstIndex = y; + sum = radiusPlusOne * data_i32[srcIndex]; + + for (i = (srcIndex + 1) | 0, end = (srcIndex + radius) | 0; i <= end; ++i) { + sum += data_i32[i]; + } + + nextPixelIndex = srcIndex + radiusPlusOne; + previousPixelIndex = srcIndex; + hold = data_i32[previousPixelIndex]; + + for (x = 0; x < radius; ++x, dstIndex += w) { + data_u8[dstIndex] = sum; + sum += data_i32[nextPixelIndex] - hold; + nextPixelIndex++; + } + for (; x < h - radiusPlus2; x += 2, dstIndex += w2) { + data_u8[dstIndex] = sum; + sum += data_i32[nextPixelIndex] - data_i32[previousPixelIndex]; + + data_u8[dstIndex + w] = sum; + sum += data_i32[nextPixelIndex + 1] - data_i32[previousPixelIndex + 1]; + + nextPixelIndex += 2; + previousPixelIndex += 2; + } + for (; x < h - radiusPlusOne; ++x, dstIndex += w) { + data_u8[dstIndex] = sum; + + sum += data_i32[nextPixelIndex] - data_i32[previousPixelIndex]; + nextPixelIndex++; + previousPixelIndex++; + } + hold = data_i32[nextPixelIndex - 1]; + for (; x < h; ++x, dstIndex += w) { + data_u8[dstIndex] = sum; + + sum += hold - data_i32[previousPixelIndex]; + previousPixelIndex++; + } + + srcIndex += h; + } + } else { + for (y = 0; y < w; ++y) { + dstIndex = y; + sum = radiusPlusOne * data_i32[srcIndex]; + + for (i = (srcIndex + 1) | 0, end = (srcIndex + radius) | 0; i <= end; ++i) { + sum += data_i32[i]; + } + + nextPixelIndex = srcIndex + radiusPlusOne; + previousPixelIndex = srcIndex; + hold = data_i32[previousPixelIndex]; + + for (x = 0; x < radius; ++x, dstIndex += w) { + data_u8[dstIndex] = sum * scale; + sum += data_i32[nextPixelIndex] - hold; + nextPixelIndex++; + } + for (; x < h - radiusPlus2; x += 2, dstIndex += w2) { + data_u8[dstIndex] = sum * scale; + sum += data_i32[nextPixelIndex] - data_i32[previousPixelIndex]; + + data_u8[dstIndex + w] = sum * scale; + sum += data_i32[nextPixelIndex + 1] - data_i32[previousPixelIndex + 1]; + + nextPixelIndex += 2; + previousPixelIndex += 2; + } + for (; x < h - radiusPlusOne; ++x, dstIndex += w) { + data_u8[dstIndex] = sum * scale; + + sum += data_i32[nextPixelIndex] - data_i32[previousPixelIndex]; + nextPixelIndex++; + previousPixelIndex++; + } + hold = data_i32[nextPixelIndex - 1]; + for (; x < h; ++x, dstIndex += w) { + data_u8[dstIndex] = sum * scale; + + sum += hold - data_i32[previousPixelIndex]; + previousPixelIndex++; + } + + srcIndex += h; + } + } + + this.cache.put_buffer(tmp_buff); } + gaussian_blur(src: matrix_t, dst: matrix_t, kernel_size: number, sigma: number): void { - throw new Error("Method not implemented."); + const jsfeatmath = new math(); + if (typeof sigma === "undefined") { + sigma = 0.0; + } + if (typeof kernel_size === "undefined") { + kernel_size = 0; + } + kernel_size = kernel_size == 0 ? (Math.max(1, 4.0 * sigma + 1.0 - 1e-8) * 2 + 1) | 0 : kernel_size; + const half_kernel = kernel_size >> 1; + const w = src.cols, + h = src.rows; + const data_type = src.type, + is_u8 = data_type & JSFEAT_CONSTANTS.U8_t; + + dst.resize(w, h, src.channel); + + const src_d = src.data, + dst_d = dst.data; + let buf, + filter, + buf_sz = (kernel_size + Math.max(h, w)) | 0; + + const buf_node = this.cache.get_buffer(buf_sz << 2); + const filt_node = this.cache.get_buffer(kernel_size << 2); + + if (is_u8) { + buf = buf_node.i32; + filter = filt_node.i32; + } else if (data_type & JSFEAT_CONSTANTS.S32_t) { + buf = buf_node.i32; + filter = filt_node.f32; + } else { + buf = buf_node.f32; + filter = filt_node.f32; + } + + jsfeatmath.get_gaussian_kernel(kernel_size, sigma, filter, data_type); + + if (is_u8) { + _convol_u8(buf, src_d, dst_d, w, h, filter, kernel_size, half_kernel); + } else { + _convol(buf, src_d, dst_d, w, h, filter, kernel_size, half_kernel); + } + + this.cache.put_buffer(buf_node); + this.cache.put_buffer(filt_node); } - hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): Array { - throw new Error("Method not implemented."); + + hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): number[] { + let r; + let i; + const image = img.data; + + const width = img.cols; + const height = img.rows; + const step = width; + + const min_theta = 0.0; + const max_theta = Math.PI; + + const numangle = Math.round((max_theta - min_theta) / theta_res); + const numrho = Math.round(((width + height) * 2 + 1) / rho_res); + const irho = 1.0 / rho_res; + + const accum = new Int32Array((numangle + 2) * (numrho + 2)); //typed arrays are initialized to 0 + const tabSin = new Float32Array(numangle); + const tabCos = new Float32Array(numangle); + + let n = 0; + let ang = min_theta; + for (; n < numangle; n++) { + tabSin[n] = Math.sin(ang) * irho; + tabCos[n] = Math.cos(ang) * irho; + ang += theta_res; + } + + // stage 1. fill accumulator + for (i = 0; i < height; i++) { + for (let j = 0; j < width; j++) { + if (image[i * step + j] != 0) { + //console.log(r, (n+1) * (numrho+2) + r+1, tabCos[n], tabSin[n]); + for (n = 0; n < numangle; n++) { + r = Math.round(j * tabCos[n] + i * tabSin[n]); + r += (numrho - 1) / 2; + accum[(n + 1) * (numrho + 2) + r + 1] += 1; + } + } + } + } + + // stage 2. find local maximums + //TODO: Consider making a vector class that uses typed arrays + const _sort_buf = []; + for (r = 0; r < numrho; r++) { + for (n = 0; n < numangle; n++) { + const base = (n + 1) * (numrho + 2) + r + 1; + if ( + accum[base] > threshold && + accum[base] > accum[base - 1] && + accum[base] >= accum[base + 1] && + accum[base] > accum[base - numrho - 2] && + accum[base] >= accum[base + numrho + 2] + ) { + _sort_buf.push(base); + } + } + } + + // stage 3. sort the detected lines by accumulator value + _sort_buf.sort(function (l1, l2) { + return ((accum[l1] > accum[l2] || (accum[l1] == accum[l2] && l1 < l2))); + }); + + // stage 4. store the first min(total,linesMax) lines to the output buffer + const linesMax = Math.min(numangle * numrho, _sort_buf.length); + const scale = 1.0 / (numrho + 2); + const lines = new Array(); + for (i = 0; i < linesMax; i++) { + const idx = _sort_buf[i]; + n = Math.floor(idx * scale) - 1; + r = idx - (n + 1) * (numrho + 2) - 1; + const lrho = (r - (numrho - 1) * 0.5) * rho_res; + const langle = n * theta_res; + lines.push([lrho, langle]); + } + return lines; } + pyrdown(src: matrix_t, dst: matrix_t, sx?: number, sy?: number): void { - throw new Error("Method not implemented."); + // this is needed for bbf + if (typeof sx === "undefined") { + sx = 0; + } + if (typeof sy === "undefined") { + sy = 0; + } + + const w = src.cols, + h = src.rows; + const w2 = w >> 1, + h2 = h >> 1; + const _w2 = w2 - (sx << 1), + _h2 = h2 - (sy << 1); + let x = 0, + y = 0, + sptr = sx + sy * w, + sline = 0, + dptr = 0, + dline = 0; + + dst.resize(w2, h2, src.channel); + + const src_d = src.data, + dst_d = dst.data; + + for (y = 0; y < _h2; ++y) { + sline = sptr; + dline = dptr; + for (x = 0; x <= _w2 - 2; x += 2, dline += 2, sline += 4) { + dst_d[dline] = (src_d[sline] + src_d[sline + 1] + src_d[sline + w] + src_d[sline + w + 1] + 2) >> 2; + dst_d[dline + 1] = + (src_d[sline + 2] + src_d[sline + 3] + src_d[sline + w + 2] + src_d[sline + w + 3] + 2) >> 2; + } + for (; x < _w2; ++x, ++dline, sline += 2) { + dst_d[dline] = (src_d[sline] + src_d[sline + 1] + src_d[sline + w] + src_d[sline + w + 1] + 2) >> 2; + } + sptr += w << 1; + dptr += w2; + } } + + // dst: [gx,gy,...] scharr_derivatives(src: matrix_t, dst: matrix_t): void { - throw new Error("Method not implemented."); + const w = src.cols, + h = src.rows; + let dstep = w << 1, + x = 0, + y = 0, + x1 = 0, + a, + b, + c, + d, + e, + f; + let srow0 = 0, + srow1 = 0, + srow2 = 0, + drow = 0; + let trow0, trow1; + + dst.resize(w, h, 2); // 2 channel output gx, gy + + const img = src.data, + gxgy = dst.data; + + const buf0_node = this.cache.get_buffer((w + 2) << 2); + const buf1_node = this.cache.get_buffer((w + 2) << 2); + + if (src.type & JSFEAT_CONSTANTS.U8_t || src.type & JSFEAT_CONSTANTS.S32_t) { + trow0 = buf0_node.i32; + trow1 = buf1_node.i32; + } else { + trow0 = buf0_node.f32; + trow1 = buf1_node.f32; + } + + for (; y < h; ++y, srow1 += w) { + srow0 = ((y > 0 ? y - 1 : 1) * w) | 0; + srow2 = ((y < h - 1 ? y + 1 : h - 2) * w) | 0; + drow = (y * dstep) | 0; + // do vertical convolution + for (x = 0, x1 = 1; x <= w - 2; x += 2, x1 += 2) { + (a = img[srow0 + x]), (b = img[srow2 + x]); + trow0[x1] = (a + b) * 3 + img[srow1 + x] * 10; + trow1[x1] = b - a; + // + (a = img[srow0 + x + 1]), (b = img[srow2 + x + 1]); + trow0[x1 + 1] = (a + b) * 3 + img[srow1 + x + 1] * 10; + trow1[x1 + 1] = b - a; + } + for (; x < w; ++x, ++x1) { + (a = img[srow0 + x]), (b = img[srow2 + x]); + trow0[x1] = (a + b) * 3 + img[srow1 + x] * 10; + trow1[x1] = b - a; + } + // make border + x = (w + 1) | 0; + trow0[0] = trow0[1]; + trow0[x] = trow0[w]; + trow1[0] = trow1[1]; + trow1[x] = trow1[w]; + // do horizontal convolution, interleave the results and store them + for (x = 0; x <= w - 4; x += 4) { + (a = trow1[x + 2]), + (b = trow1[x + 1]), + (c = trow1[x + 3]), + (d = trow1[x + 4]), + (e = trow0[x + 2]), + (f = trow0[x + 3]); + gxgy[drow++] = e - trow0[x]; + gxgy[drow++] = (a + trow1[x]) * 3 + b * 10; + gxgy[drow++] = f - trow0[x + 1]; + gxgy[drow++] = (c + b) * 3 + a * 10; + + gxgy[drow++] = trow0[x + 4] - e; + gxgy[drow++] = (d + a) * 3 + c * 10; + gxgy[drow++] = trow0[x + 5] - f; + gxgy[drow++] = (trow1[x + 5] + c) * 3 + d * 10; + } + for (; x < w; ++x) { + gxgy[drow++] = trow0[x + 2] - trow0[x]; + gxgy[drow++] = (trow1[x + 2] + trow1[x]) * 3 + trow1[x + 1] * 10; + } + } + this.cache.put_buffer(buf0_node); + this.cache.put_buffer(buf1_node); } + + // compute gradient using Sobel kernel [1 2 1] * [-1 0 1]^T + // dst: [gx,gy,...] sobel_derivatives(src: matrix_t, dst: matrix_t): void { - throw new Error("Method not implemented."); + const w = src.cols, + h = src.rows; + let dstep = w << 1, + x = 0, + y = 0, + x1 = 0, + a, + b, + c, + d, + e, + f; + let srow0 = 0, + srow1 = 0, + srow2 = 0, + drow = 0; + let trow0, trow1; + + dst.resize(w, h, 2); // 2 channel output gx, gy + + const img = src.data, + gxgy = dst.data; + + const buf0_node = this.cache.get_buffer((w + 2) << 2); + const buf1_node = this.cache.get_buffer((w + 2) << 2); + + if (src.type & JSFEAT_CONSTANTS.U8_t || src.type & JSFEAT_CONSTANTS.S32_t) { + trow0 = buf0_node.i32; + trow1 = buf1_node.i32; + } else { + trow0 = buf0_node.f32; + trow1 = buf1_node.f32; + } + + for (; y < h; ++y, srow1 += w) { + srow0 = ((y > 0 ? y - 1 : 1) * w) | 0; + srow2 = ((y < h - 1 ? y + 1 : h - 2) * w) | 0; + drow = (y * dstep) | 0; + // do vertical convolution + for (x = 0, x1 = 1; x <= w - 2; x += 2, x1 += 2) { + (a = img[srow0 + x]), (b = img[srow2 + x]); + trow0[x1] = a + b + img[srow1 + x] * 2; + trow1[x1] = b - a; + // + (a = img[srow0 + x + 1]), (b = img[srow2 + x + 1]); + trow0[x1 + 1] = a + b + img[srow1 + x + 1] * 2; + trow1[x1 + 1] = b - a; + } + for (; x < w; ++x, ++x1) { + (a = img[srow0 + x]), (b = img[srow2 + x]); + trow0[x1] = a + b + img[srow1 + x] * 2; + trow1[x1] = b - a; + } + // make border + x = (w + 1) | 0; + trow0[0] = trow0[1]; + trow0[x] = trow0[w]; + trow1[0] = trow1[1]; + trow1[x] = trow1[w]; + // do horizontal convolution, interleave the results and store them + for (x = 0; x <= w - 4; x += 4) { + (a = trow1[x + 2]), + (b = trow1[x + 1]), + (c = trow1[x + 3]), + (d = trow1[x + 4]), + (e = trow0[x + 2]), + (f = trow0[x + 3]); + gxgy[drow++] = e - trow0[x]; + gxgy[drow++] = a + trow1[x] + b * 2; + gxgy[drow++] = f - trow0[x + 1]; + gxgy[drow++] = c + b + a * 2; + + gxgy[drow++] = trow0[x + 4] - e; + gxgy[drow++] = d + a + c * 2; + gxgy[drow++] = trow0[x + 5] - f; + gxgy[drow++] = trow1[x + 5] + c + d * 2; + } + for (; x < w; ++x) { + gxgy[drow++] = trow0[x + 2] - trow0[x]; + gxgy[drow++] = trow1[x + 2] + trow1[x] + trow1[x + 1] * 2; + } + } + this.cache.put_buffer(buf0_node); + this.cache.put_buffer(buf1_node); } + + // please note: + // dst_(type) size should be cols = src.cols+1, rows = src.rows+1 compute_integral_image(src: matrix_t, dst_sum: number[], dst_sqsum: number[], dst_tilted: any[]): void { - throw new Error("Method not implemented."); + const w0 = src.cols | 0, + h0 = src.rows | 0, + src_d = src.data; + const w1 = (w0 + 1) | 0; + let s = 0, + s2 = 0, + p = 0, + pup = 0, + i = 0, + j = 0, + v = 0, + k = 0; + + if (dst_sum && dst_sqsum) { + // fill first row with zeros + for (; i < w1; ++i) { + (dst_sum[i] = 0), (dst_sqsum[i] = 0); + } + (p = (w1 + 1) | 0), (pup = 1); + for (i = 0, k = 0; i < h0; ++i, ++p, ++pup) { + s = s2 = 0; + for (j = 0; j <= w0 - 2; j += 2, k += 2, p += 2, pup += 2) { + v = src_d[k]; + (s += v), (s2 += v * v); + dst_sum[p] = dst_sum[pup] + s; + dst_sqsum[p] = dst_sqsum[pup] + s2; + + v = src_d[k + 1]; + (s += v), (s2 += v * v); + dst_sum[p + 1] = dst_sum[pup + 1] + s; + dst_sqsum[p + 1] = dst_sqsum[pup + 1] + s2; + } + for (; j < w0; ++j, ++k, ++p, ++pup) { + v = src_d[k]; + (s += v), (s2 += v * v); + dst_sum[p] = dst_sum[pup] + s; + dst_sqsum[p] = dst_sqsum[pup] + s2; + } + } + } else if (dst_sum) { + // fill first row with zeros + for (; i < w1; ++i) { + dst_sum[i] = 0; + } + (p = (w1 + 1) | 0), (pup = 1); + for (i = 0, k = 0; i < h0; ++i, ++p, ++pup) { + s = 0; + for (j = 0; j <= w0 - 2; j += 2, k += 2, p += 2, pup += 2) { + s += src_d[k]; + dst_sum[p] = dst_sum[pup] + s; + s += src_d[k + 1]; + dst_sum[p + 1] = dst_sum[pup + 1] + s; + } + for (; j < w0; ++j, ++k, ++p, ++pup) { + s += src_d[k]; + dst_sum[p] = dst_sum[pup] + s; + } + } + } else if (dst_sqsum) { + // fill first row with zeros + for (; i < w1; ++i) { + dst_sqsum[i] = 0; + } + (p = (w1 + 1) | 0), (pup = 1); + for (i = 0, k = 0; i < h0; ++i, ++p, ++pup) { + s2 = 0; + for (j = 0; j <= w0 - 2; j += 2, k += 2, p += 2, pup += 2) { + v = src_d[k]; + s2 += v * v; + dst_sqsum[p] = dst_sqsum[pup] + s2; + v = src_d[k + 1]; + s2 += v * v; + dst_sqsum[p + 1] = dst_sqsum[pup + 1] + s2; + } + for (; j < w0; ++j, ++k, ++p, ++pup) { + v = src_d[k]; + s2 += v * v; + dst_sqsum[p] = dst_sqsum[pup] + s2; + } + } + } + + if (dst_tilted) { + // fill first row with zeros + for (i = 0; i < w1; ++i) { + dst_tilted[i] = 0; + } + // diagonal + (p = (w1 + 1) | 0), (pup = 0); + for (i = 0, k = 0; i < h0; ++i, ++p, ++pup) { + for (j = 0; j <= w0 - 2; j += 2, k += 2, p += 2, pup += 2) { + dst_tilted[p] = src_d[k] + dst_tilted[pup]; + dst_tilted[p + 1] = src_d[k + 1] + dst_tilted[pup + 1]; + } + for (; j < w0; ++j, ++k, ++p, ++pup) { + dst_tilted[p] = src_d[k] + dst_tilted[pup]; + } + } + // diagonal + (p = (w1 + w0) | 0), (pup = w0); + for (i = 0; i < h0; ++i, p += w1, pup += w1) { + dst_tilted[p] += dst_tilted[pup]; + } + + for (j = w0 - 1; j > 0; --j) { + (p = j + h0 * w1), (pup = p - w1); + for (i = h0; i > 0; --i, p -= w1, pup -= w1) { + dst_tilted[p] += dst_tilted[pup] + dst_tilted[pup + 1]; + } + } + } } + equalize_histogram(src: matrix_t, dst: matrix_t): void { - throw new Error("Method not implemented."); + const w = src.cols, + h = src.rows, + src_d = src.data; + + dst.resize(w, h, src.channel); + + const dst_d = dst.data, + size = w * h; + let i = 0, + prev = 0, + hist0, + norm; + + const hist0_node = this.cache.get_buffer(256 << 2); + hist0 = hist0_node.i32; + for (; i < 256; ++i) hist0[i] = 0; + for (i = 0; i < size; ++i) { + ++hist0[src_d[i]]; + } + + prev = hist0[0]; + for (i = 1; i < 256; ++i) { + prev = hist0[i] += prev; + } + + norm = 255 / size; + for (i = 0; i < size; ++i) { + dst_d[i] = (hist0[src_d[i]] * norm + 0.5) | 0; + } + this.cache.put_buffer(hist0_node); } + canny(src: matrix_t, dst: matrix_t, low_thresh: number, high_thresh: number): void { - throw new Error("Method not implemented."); + const w = src.cols, + h = src.rows, + src_d = src.data; + + dst.resize(w, h, src.channel); + + const dst_d = dst.data; + let i = 0, + j: number = 0, + grad = 0, + w2 = w << 1, + _grad = 0, + suppress = 0, + f = 0, + x = 0, + y = 0, + s = 0; + let tg22x = 0, + tg67x = 0; + + // cache buffers + const dxdy_node = this.cache.get_buffer((h * w2) << 2); + const buf_node = this.cache.get_buffer((3 * (w + 2)) << 2); + const map_node = this.cache.get_buffer(((h + 2) * (w + 2)) << 2); + const stack_node = this.cache.get_buffer((h * w) << 2); + + const buf = buf_node.i32; + const map = map_node.i32; + const stack = stack_node.i32; + const dxdy = dxdy_node.i32; + const dxdy_m = new matrix_t(w, h, JSFEAT_CONSTANTS.S32C2_t, dxdy_node.data); + let row0 = 1, + row1 = (w + 2 + 1) | 0, + row2 = (2 * (w + 2) + 1) | 0, + map_w = (w + 2) | 0, + map_i: number = (map_w + 1) | 0, + stack_i = 0; + + this.sobel_derivatives(src, dxdy_m); + + if (low_thresh > high_thresh) { + i = low_thresh; + low_thresh = high_thresh; + high_thresh = i; + } + + i = (3 * (w + 2)) | 0; + while (--i >= 0) { + buf[i] = 0; + } + + i = ((h + 2) * (w + 2)) | 0; + while (--i >= 0) { + map[i] = 0; + } + + for (; j < w; ++j, grad += 2) { + //buf[row1+j] = Math.abs(dxdy[grad]) + Math.abs(dxdy[grad+1]); + (x = dxdy[grad]), (y = dxdy[grad + 1]); + //buf[row1+j] = x*x + y*y; + buf[row1 + j] = (x ^ (x >> 31)) - (x >> 31) + ((y ^ (y >> 31)) - (y >> 31)); + } + + for (i = 1; i <= h; ++i, grad += w2) { + if (i == h) { + j = row2 + w; + while (--j >= row2) { + buf[j] = 0; + } + } else { + for (j = 0; j < w; j++) { + //buf[row2+j] = Math.abs(dxdy[grad+(j<<1)]) + Math.abs(dxdy[grad+(j<<1)+1]); + (x = dxdy[grad + (j << 1)]), (y = dxdy[grad + (j << 1) + 1]); + //buf[row2+j] = x*x + y*y; + buf[row2 + j] = (x ^ (x >> 31)) - (x >> 31) + ((y ^ (y >> 31)) - (y >> 31)); + } + } + _grad = (grad - w2) | 0; + map[map_i - 1] = 0; + suppress = 0; + for (j = 0; j < w; ++j, _grad += 2) { + f = buf[row1 + j]; + if (f > low_thresh) { + x = dxdy[_grad]; + y = dxdy[_grad + 1]; + s = x ^ y; + // seems ot be faster than Math.abs + x = ((x ^ (x >> 31)) - (x >> 31)) | 0; + y = ((y ^ (y >> 31)) - (y >> 31)) | 0; + //x * tan(22.5) x * tan(67.5) == 2 * x + x * tan(22.5) + tg22x = x * 13573; + tg67x = tg22x + ((x + x) << 15); + y <<= 15; + if (y < tg22x) { + if (f > buf[row1 + j - 1] && f >= buf[row1 + j + 1]) { + if (f > high_thresh && !suppress && map[map_i + j - map_w] != 2) { + map[map_i + j] = 2; + suppress = 1; + stack[stack_i++] = map_i + j; + } else { + map[map_i + j] = 1; + } + continue; + } + } else if (y > tg67x) { + if (f > buf[row0 + j] && f >= buf[row2 + j]) { + if (f > high_thresh && !suppress && map[map_i + j - map_w] != 2) { + map[map_i + j] = 2; + suppress = 1; + stack[stack_i++] = map_i + j; + } else { + map[map_i + j] = 1; + } + continue; + } + } else { + s = s < 0 ? -1 : 1; + if (f > buf[row0 + j - s] && f > buf[row2 + j + s]) { + if (f > high_thresh && !suppress && map[map_i + j - map_w] != 2) { + map[map_i + j] = 2; + suppress = 1; + stack[stack_i++] = map_i + j; + } else { + map[map_i + j] = 1; + } + continue; + } + } + } + map[map_i + j] = 0; + suppress = 0; + } + map[map_i + w] = 0; + map_i += map_w; + j = row0; + row0 = row1; + row1 = row2; + row2 = j; + } + + j = map_i - map_w - 1; + for (i = 0; i < map_w; ++i, ++j) { + map[j] = 0; + } + // path following + while (stack_i > 0) { + map_i = stack[--stack_i]; + map_i -= map_w + 1; + if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); + map_i += 1; + if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); + map_i += 1; + if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); + map_i += map_w; + if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); + map_i -= 2; + if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); + map_i += map_w; + if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); + map_i += 1; + if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); + map_i += 1; + if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); + } + + map_i = map_w + 1; + row0 = 0; + for (i = 0; i < h; ++i, map_i += map_w) { + for (j = 0; j < w; ++j) { + dst_d[row0++] = Number(map[map_i + j] == 2) * 0xff; + } + } + + // free buffers + this.cache.put_buffer(dxdy_node); + this.cache.put_buffer(buf_node); + this.cache.put_buffer(map_node); + this.cache.put_buffer(stack_node); } + + // transform is 3x3 matrix_t warp_perspective(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { - throw new Error("Method not implemented."); + if (typeof fill_value === "undefined") { + fill_value = 0; + } + const src_width = src.cols | 0, + src_height = src.rows | 0, + dst_width = dst.cols | 0, + dst_height = dst.rows | 0; + const src_d = src.data, + dst_d = dst.data; + let x = 0, + y = 0, + off = 0, + ixs = 0, + iys = 0, + xs = 0.0, + ys = 0.0, + xs0 = 0.0, + ys0 = 0.0, + ws = 0.0, + sc = 0.0, + a = 0.0, + b = 0.0, + p0 = 0.0, + p1 = 0.0; + const td = transform.data; + const m00 = td[0], + m01 = td[1], + m02 = td[2], + m10 = td[3], + m11 = td[4], + m12 = td[5], + m20 = td[6], + m21 = td[7], + m22 = td[8]; + + for (let dptr = 0; y < dst_height; ++y) { + (xs0 = m01 * y + m02), (ys0 = m11 * y + m12), (ws = m21 * y + m22); + for (x = 0; x < dst_width; ++x, ++dptr, xs0 += m00, ys0 += m10, ws += m20) { + sc = 1.0 / ws; + (xs = xs0 * sc), (ys = ys0 * sc); + (ixs = xs | 0), (iys = ys | 0); + + if (xs > 0 && ys > 0 && ixs < src_width - 1 && iys < src_height - 1) { + a = Math.max(xs - ixs, 0.0); + b = Math.max(ys - iys, 0.0); + off = (src_width * iys + ixs) | 0; + + p0 = src_d[off] + a * (src_d[off + 1] - src_d[off]); + p1 = src_d[off + src_width] + a * (src_d[off + src_width + 1] - src_d[off + src_width]); + + dst_d[dptr] = p0 + b * (p1 - p0); + } else dst_d[dptr] = fill_value; + } + } } + + // transform is 3x3 or 2x3 matrix_t only first 6 values referenced warp_affine(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { - throw new Error("Method not implemented."); + if (typeof fill_value === "undefined") { + fill_value = 0; + } + const src_width = src.cols, + src_height = src.rows, + dst_width = dst.cols, + dst_height = dst.rows; + const src_d = src.data, + dst_d = dst.data; + let x = 0, + y = 0, + off = 0, + ixs = 0, + iys = 0, + xs = 0.0, + ys = 0.0, + a = 0.0, + b = 0.0, + p0 = 0.0, + p1 = 0.0; + const td = transform.data; + const m00 = td[0], + m01 = td[1], + m02 = td[2], + m10 = td[3], + m11 = td[4], + m12 = td[5]; + + for (let dptr = 0; y < dst_height; ++y) { + xs = m01 * y + m02; + ys = m11 * y + m12; + for (x = 0; x < dst_width; ++x, ++dptr, xs += m00, ys += m10) { + ixs = xs | 0; + iys = ys | 0; + + if (ixs >= 0 && iys >= 0 && ixs < src_width - 1 && iys < src_height - 1) { + a = xs - ixs; + b = ys - iys; + off = src_width * iys + ixs; + + p0 = src_d[off] + a * (src_d[off + 1] - src_d[off]); + p1 = src_d[off + src_width] + a * (src_d[off + src_width + 1] - src_d[off + src_width]); + + dst_d[dptr] = p0 + b * (p1 - p0); + } else dst_d[dptr] = fill_value; + } + } } + + // Basic RGB Skin detection filter + // from http://popscan.blogspot.fr/2012/08/skin-detection-in-digital-images.html skindetector(src: { width: number; height: number; data: any[] }, dst: number[]): void { - throw new Error("Method not implemented."); + let r, g, b, j; + let i = src.width * src.height; + while (i--) { + j = i * 4; + r = src.data[j]; + g = src.data[j + 1]; + b = src.data[j + 2]; + if (r > 95 && g > 40 && b > 20 && r > g && r > b && r - Math.min(g, b) > 15 && Math.abs(r - g) > 15) { + dst[i] = 255; + } else { + dst[i] = 0; + } + } } } diff --git a/src/jsfeatNext.ts b/src/jsfeatNext.ts index a41098f..e29e5ad 100644 --- a/src/jsfeatNext.ts +++ b/src/jsfeatNext.ts @@ -782,1057 +782,7 @@ jsfeatNext.fast_corners = class fast_corners extends jsfeatNext { } }; -jsfeatNext.imgproc = class imgproc extends jsfeatNext { - constructor() { - super(); - } - - grayscale(src: Uint8Array | Uint8ClampedArray, w: number, h: number, dst: matrix_t, code?: number): void { - // this is default image data representation in browser - if (typeof code === "undefined") { - code = JSFEAT_CONSTANTS.COLOR_RGBA2GRAY; - } - let x = 0, - y = 0, - i = 0, - j = 0, - ir = 0, - jr = 0; - let coeff_r = 4899, - coeff_g = 9617, - coeff_b = 1868, - cn = 4; - - if (code == JSFEAT_CONSTANTS.COLOR_BGRA2GRAY || code == JSFEAT_CONSTANTS.COLOR_BGR2GRAY) { - coeff_r = 1868; - coeff_b = 4899; - } - if (code == JSFEAT_CONSTANTS.COLOR_RGB2GRAY || code == JSFEAT_CONSTANTS.COLOR_BGR2GRAY) { - cn = 3; - } - const cn2 = cn << 1, - cn3 = (cn * 3) | 0; - - dst.resize(w, h, 1); - const dst_u8 = dst.data; - - for (y = 0; y < h; ++y, j += w, i += w * cn) { - for (x = 0, ir = i, jr = j; x <= w - 4; x += 4, ir += cn << 2, jr += 4) { - dst_u8[jr] = (src[ir] * coeff_r + src[ir + 1] * coeff_g + src[ir + 2] * coeff_b + 8192) >> 14; - dst_u8[jr + 1] = - (src[ir + cn] * coeff_r + src[ir + cn + 1] * coeff_g + src[ir + cn + 2] * coeff_b + 8192) >> 14; - dst_u8[jr + 2] = - (src[ir + cn2] * coeff_r + src[ir + cn2 + 1] * coeff_g + src[ir + cn2 + 2] * coeff_b + 8192) >> 14; - dst_u8[jr + 3] = - (src[ir + cn3] * coeff_r + src[ir + cn3 + 1] * coeff_g + src[ir + cn3 + 2] * coeff_b + 8192) >> 14; - } - for (; x < w; ++x, ++jr, ir += cn) { - dst_u8[jr] = (src[ir] * coeff_r + src[ir + 1] * coeff_g + src[ir + 2] * coeff_b + 8192) >> 14; - } - } - } - - // derived from CCV library - resample(src: matrix_t, dst: matrix_t, nw: number, nh: number): void { - const h = src.rows, - w = src.cols; - if (h > nh && w > nw) { - dst.resize(nw, nh, src.channel); - // using the fast alternative (fix point scale, 0x100 to avoid overflow) - if (src.type & JSFEAT_CONSTANTS.U8_t && dst.type & JSFEAT_CONSTANTS.U8_t && (h * w) / (nh * nw) < 0x100) { - _resample_u8(src, dst, this.cache, nw, nh); - } else { - _resample(src, dst, this.cache, nw, nh); - } - } - } - - box_blur_gray(src: matrix_t, dst: matrix_t, radius: number, options: number): void { - if (typeof options === "undefined") { - options = 0; - } - const w = src.cols, - h = src.rows, - h2 = h << 1, - w2 = w << 1; - let i = 0, - x = 0, - y = 0, - end = 0; - const windowSize = ((radius << 1) + 1) | 0; - const radiusPlusOne = (radius + 1) | 0, - radiusPlus2 = (radiusPlusOne + 1) | 0; - const scale = options & JSFEAT_CONSTANTS.BOX_BLUR_NOSCALE ? 1 : 1.0 / (windowSize * windowSize); - - const tmp_buff = this.cache.get_buffer((w * h) << 2); - - let sum = 0, - dstIndex = 0, - srcIndex = 0, - nextPixelIndex = 0, - previousPixelIndex = 0; - const data_i32 = tmp_buff.i32; // to prevent overflow - let data_u8 = src.data; - let hold = 0; - - dst.resize(w, h, src.channel); - - // first pass - // no need to scale - //data_u8 = src.data; - //data_i32 = tmp; - for (y = 0; y < h; ++y) { - dstIndex = y; - sum = radiusPlusOne * data_u8[srcIndex]; - - for (i = (srcIndex + 1) | 0, end = (srcIndex + radius) | 0; i <= end; ++i) { - sum += data_u8[i]; - } - - nextPixelIndex = (srcIndex + radiusPlusOne) | 0; - previousPixelIndex = srcIndex; - hold = data_u8[previousPixelIndex]; - for (x = 0; x < radius; ++x, dstIndex += h) { - data_i32[dstIndex] = sum; - sum += data_u8[nextPixelIndex] - hold; - nextPixelIndex++; - } - for (; x < w - radiusPlus2; x += 2, dstIndex += h2) { - data_i32[dstIndex] = sum; - sum += data_u8[nextPixelIndex] - data_u8[previousPixelIndex]; - - data_i32[dstIndex + h] = sum; - sum += data_u8[nextPixelIndex + 1] - data_u8[previousPixelIndex + 1]; - - nextPixelIndex += 2; - previousPixelIndex += 2; - } - for (; x < w - radiusPlusOne; ++x, dstIndex += h) { - data_i32[dstIndex] = sum; - sum += data_u8[nextPixelIndex] - data_u8[previousPixelIndex]; - - nextPixelIndex++; - previousPixelIndex++; - } - - hold = data_u8[nextPixelIndex - 1]; - for (; x < w; ++x, dstIndex += h) { - data_i32[dstIndex] = sum; - - sum += hold - data_u8[previousPixelIndex]; - previousPixelIndex++; - } - - srcIndex += w; - } - // - // second pass - srcIndex = 0; - //data_i32 = tmp; // this is a transpose - data_u8 = dst.data; - - // dont scale result - if (scale == 1) { - for (y = 0; y < w; ++y) { - dstIndex = y; - sum = radiusPlusOne * data_i32[srcIndex]; - - for (i = (srcIndex + 1) | 0, end = (srcIndex + radius) | 0; i <= end; ++i) { - sum += data_i32[i]; - } - - nextPixelIndex = srcIndex + radiusPlusOne; - previousPixelIndex = srcIndex; - hold = data_i32[previousPixelIndex]; - - for (x = 0; x < radius; ++x, dstIndex += w) { - data_u8[dstIndex] = sum; - sum += data_i32[nextPixelIndex] - hold; - nextPixelIndex++; - } - for (; x < h - radiusPlus2; x += 2, dstIndex += w2) { - data_u8[dstIndex] = sum; - sum += data_i32[nextPixelIndex] - data_i32[previousPixelIndex]; - - data_u8[dstIndex + w] = sum; - sum += data_i32[nextPixelIndex + 1] - data_i32[previousPixelIndex + 1]; - - nextPixelIndex += 2; - previousPixelIndex += 2; - } - for (; x < h - radiusPlusOne; ++x, dstIndex += w) { - data_u8[dstIndex] = sum; - - sum += data_i32[nextPixelIndex] - data_i32[previousPixelIndex]; - nextPixelIndex++; - previousPixelIndex++; - } - hold = data_i32[nextPixelIndex - 1]; - for (; x < h; ++x, dstIndex += w) { - data_u8[dstIndex] = sum; - - sum += hold - data_i32[previousPixelIndex]; - previousPixelIndex++; - } - - srcIndex += h; - } - } else { - for (y = 0; y < w; ++y) { - dstIndex = y; - sum = radiusPlusOne * data_i32[srcIndex]; - - for (i = (srcIndex + 1) | 0, end = (srcIndex + radius) | 0; i <= end; ++i) { - sum += data_i32[i]; - } - - nextPixelIndex = srcIndex + radiusPlusOne; - previousPixelIndex = srcIndex; - hold = data_i32[previousPixelIndex]; - - for (x = 0; x < radius; ++x, dstIndex += w) { - data_u8[dstIndex] = sum * scale; - sum += data_i32[nextPixelIndex] - hold; - nextPixelIndex++; - } - for (; x < h - radiusPlus2; x += 2, dstIndex += w2) { - data_u8[dstIndex] = sum * scale; - sum += data_i32[nextPixelIndex] - data_i32[previousPixelIndex]; - - data_u8[dstIndex + w] = sum * scale; - sum += data_i32[nextPixelIndex + 1] - data_i32[previousPixelIndex + 1]; - - nextPixelIndex += 2; - previousPixelIndex += 2; - } - for (; x < h - radiusPlusOne; ++x, dstIndex += w) { - data_u8[dstIndex] = sum * scale; - - sum += data_i32[nextPixelIndex] - data_i32[previousPixelIndex]; - nextPixelIndex++; - previousPixelIndex++; - } - hold = data_i32[nextPixelIndex - 1]; - for (; x < h; ++x, dstIndex += w) { - data_u8[dstIndex] = sum * scale; - - sum += hold - data_i32[previousPixelIndex]; - previousPixelIndex++; - } - - srcIndex += h; - } - } - - this.cache.put_buffer(tmp_buff); - } - - gaussian_blur(src: matrix_t, dst: matrix_t, kernel_size: number, sigma: number): void { - const jsfeatmath = new jsfeatNext.math(); - if (typeof sigma === "undefined") { - sigma = 0.0; - } - if (typeof kernel_size === "undefined") { - kernel_size = 0; - } - kernel_size = kernel_size == 0 ? (Math.max(1, 4.0 * sigma + 1.0 - 1e-8) * 2 + 1) | 0 : kernel_size; - const half_kernel = kernel_size >> 1; - const w = src.cols, - h = src.rows; - const data_type = src.type, - is_u8 = data_type & JSFEAT_CONSTANTS.U8_t; - - dst.resize(w, h, src.channel); - - const src_d = src.data, - dst_d = dst.data; - let buf, - filter, - buf_sz = (kernel_size + Math.max(h, w)) | 0; - - const buf_node = this.cache.get_buffer(buf_sz << 2); - const filt_node = this.cache.get_buffer(kernel_size << 2); - - if (is_u8) { - buf = buf_node.i32; - filter = filt_node.i32; - } else if (data_type & JSFEAT_CONSTANTS.S32_t) { - buf = buf_node.i32; - filter = filt_node.f32; - } else { - buf = buf_node.f32; - filter = filt_node.f32; - } - - jsfeatmath.get_gaussian_kernel(kernel_size, sigma, filter, data_type); - - if (is_u8) { - _convol_u8(buf, src_d, dst_d, w, h, filter, kernel_size, half_kernel); - } else { - _convol(buf, src_d, dst_d, w, h, filter, kernel_size, half_kernel); - } - - this.cache.put_buffer(buf_node); - this.cache.put_buffer(filt_node); - } - - hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): number[] { - let r; - let i; - const image = img.data; - - const width = img.cols; - const height = img.rows; - const step = width; - - const min_theta = 0.0; - const max_theta = Math.PI; - - const numangle = Math.round((max_theta - min_theta) / theta_res); - const numrho = Math.round(((width + height) * 2 + 1) / rho_res); - const irho = 1.0 / rho_res; - - const accum = new Int32Array((numangle + 2) * (numrho + 2)); //typed arrays are initialized to 0 - const tabSin = new Float32Array(numangle); - const tabCos = new Float32Array(numangle); - - let n = 0; - let ang = min_theta; - for (; n < numangle; n++) { - tabSin[n] = Math.sin(ang) * irho; - tabCos[n] = Math.cos(ang) * irho; - ang += theta_res; - } - - // stage 1. fill accumulator - for (i = 0; i < height; i++) { - for (let j = 0; j < width; j++) { - if (image[i * step + j] != 0) { - //console.log(r, (n+1) * (numrho+2) + r+1, tabCos[n], tabSin[n]); - for (n = 0; n < numangle; n++) { - r = Math.round(j * tabCos[n] + i * tabSin[n]); - r += (numrho - 1) / 2; - accum[(n + 1) * (numrho + 2) + r + 1] += 1; - } - } - } - } - - // stage 2. find local maximums - //TODO: Consider making a vector class that uses typed arrays - const _sort_buf = []; - for (r = 0; r < numrho; r++) { - for (n = 0; n < numangle; n++) { - const base = (n + 1) * (numrho + 2) + r + 1; - if ( - accum[base] > threshold && - accum[base] > accum[base - 1] && - accum[base] >= accum[base + 1] && - accum[base] > accum[base - numrho - 2] && - accum[base] >= accum[base + numrho + 2] - ) { - _sort_buf.push(base); - } - } - } - - // stage 3. sort the detected lines by accumulator value - _sort_buf.sort(function (l1, l2) { - return ((accum[l1] > accum[l2] || (accum[l1] == accum[l2] && l1 < l2))); - }); - - // stage 4. store the first min(total,linesMax) lines to the output buffer - const linesMax = Math.min(numangle * numrho, _sort_buf.length); - const scale = 1.0 / (numrho + 2); - const lines = new Array(); - for (i = 0; i < linesMax; i++) { - const idx = _sort_buf[i]; - n = Math.floor(idx * scale) - 1; - r = idx - (n + 1) * (numrho + 2) - 1; - const lrho = (r - (numrho - 1) * 0.5) * rho_res; - const langle = n * theta_res; - lines.push([lrho, langle]); - } - return lines; - } - - pyrdown(src: matrix_t, dst: matrix_t, sx?: number, sy?: number): void { - // this is needed for bbf - if (typeof sx === "undefined") { - sx = 0; - } - if (typeof sy === "undefined") { - sy = 0; - } - - const w = src.cols, - h = src.rows; - const w2 = w >> 1, - h2 = h >> 1; - const _w2 = w2 - (sx << 1), - _h2 = h2 - (sy << 1); - let x = 0, - y = 0, - sptr = sx + sy * w, - sline = 0, - dptr = 0, - dline = 0; - - dst.resize(w2, h2, src.channel); - - const src_d = src.data, - dst_d = dst.data; - - for (y = 0; y < _h2; ++y) { - sline = sptr; - dline = dptr; - for (x = 0; x <= _w2 - 2; x += 2, dline += 2, sline += 4) { - dst_d[dline] = (src_d[sline] + src_d[sline + 1] + src_d[sline + w] + src_d[sline + w + 1] + 2) >> 2; - dst_d[dline + 1] = - (src_d[sline + 2] + src_d[sline + 3] + src_d[sline + w + 2] + src_d[sline + w + 3] + 2) >> 2; - } - for (; x < _w2; ++x, ++dline, sline += 2) { - dst_d[dline] = (src_d[sline] + src_d[sline + 1] + src_d[sline + w] + src_d[sline + w + 1] + 2) >> 2; - } - sptr += w << 1; - dptr += w2; - } - } - - // dst: [gx,gy,...] - scharr_derivatives(src: matrix_t, dst: matrix_t): void { - const w = src.cols, - h = src.rows; - let dstep = w << 1, - x = 0, - y = 0, - x1 = 0, - a, - b, - c, - d, - e, - f; - let srow0 = 0, - srow1 = 0, - srow2 = 0, - drow = 0; - let trow0, trow1; - - dst.resize(w, h, 2); // 2 channel output gx, gy - - const img = src.data, - gxgy = dst.data; - - const buf0_node = this.cache.get_buffer((w + 2) << 2); - const buf1_node = this.cache.get_buffer((w + 2) << 2); - - if (src.type & JSFEAT_CONSTANTS.U8_t || src.type & JSFEAT_CONSTANTS.S32_t) { - trow0 = buf0_node.i32; - trow1 = buf1_node.i32; - } else { - trow0 = buf0_node.f32; - trow1 = buf1_node.f32; - } - - for (; y < h; ++y, srow1 += w) { - srow0 = ((y > 0 ? y - 1 : 1) * w) | 0; - srow2 = ((y < h - 1 ? y + 1 : h - 2) * w) | 0; - drow = (y * dstep) | 0; - // do vertical convolution - for (x = 0, x1 = 1; x <= w - 2; x += 2, x1 += 2) { - (a = img[srow0 + x]), (b = img[srow2 + x]); - trow0[x1] = (a + b) * 3 + img[srow1 + x] * 10; - trow1[x1] = b - a; - // - (a = img[srow0 + x + 1]), (b = img[srow2 + x + 1]); - trow0[x1 + 1] = (a + b) * 3 + img[srow1 + x + 1] * 10; - trow1[x1 + 1] = b - a; - } - for (; x < w; ++x, ++x1) { - (a = img[srow0 + x]), (b = img[srow2 + x]); - trow0[x1] = (a + b) * 3 + img[srow1 + x] * 10; - trow1[x1] = b - a; - } - // make border - x = (w + 1) | 0; - trow0[0] = trow0[1]; - trow0[x] = trow0[w]; - trow1[0] = trow1[1]; - trow1[x] = trow1[w]; - // do horizontal convolution, interleave the results and store them - for (x = 0; x <= w - 4; x += 4) { - (a = trow1[x + 2]), - (b = trow1[x + 1]), - (c = trow1[x + 3]), - (d = trow1[x + 4]), - (e = trow0[x + 2]), - (f = trow0[x + 3]); - gxgy[drow++] = e - trow0[x]; - gxgy[drow++] = (a + trow1[x]) * 3 + b * 10; - gxgy[drow++] = f - trow0[x + 1]; - gxgy[drow++] = (c + b) * 3 + a * 10; - - gxgy[drow++] = trow0[x + 4] - e; - gxgy[drow++] = (d + a) * 3 + c * 10; - gxgy[drow++] = trow0[x + 5] - f; - gxgy[drow++] = (trow1[x + 5] + c) * 3 + d * 10; - } - for (; x < w; ++x) { - gxgy[drow++] = trow0[x + 2] - trow0[x]; - gxgy[drow++] = (trow1[x + 2] + trow1[x]) * 3 + trow1[x + 1] * 10; - } - } - this.cache.put_buffer(buf0_node); - this.cache.put_buffer(buf1_node); - } - - // compute gradient using Sobel kernel [1 2 1] * [-1 0 1]^T - // dst: [gx,gy,...] - sobel_derivatives(src: matrix_t, dst: matrix_t): void { - const w = src.cols, - h = src.rows; - let dstep = w << 1, - x = 0, - y = 0, - x1 = 0, - a, - b, - c, - d, - e, - f; - let srow0 = 0, - srow1 = 0, - srow2 = 0, - drow = 0; - let trow0, trow1; - - dst.resize(w, h, 2); // 2 channel output gx, gy - - const img = src.data, - gxgy = dst.data; - - const buf0_node = this.cache.get_buffer((w + 2) << 2); - const buf1_node = this.cache.get_buffer((w + 2) << 2); - - if (src.type & JSFEAT_CONSTANTS.U8_t || src.type & JSFEAT_CONSTANTS.S32_t) { - trow0 = buf0_node.i32; - trow1 = buf1_node.i32; - } else { - trow0 = buf0_node.f32; - trow1 = buf1_node.f32; - } - - for (; y < h; ++y, srow1 += w) { - srow0 = ((y > 0 ? y - 1 : 1) * w) | 0; - srow2 = ((y < h - 1 ? y + 1 : h - 2) * w) | 0; - drow = (y * dstep) | 0; - // do vertical convolution - for (x = 0, x1 = 1; x <= w - 2; x += 2, x1 += 2) { - (a = img[srow0 + x]), (b = img[srow2 + x]); - trow0[x1] = a + b + img[srow1 + x] * 2; - trow1[x1] = b - a; - // - (a = img[srow0 + x + 1]), (b = img[srow2 + x + 1]); - trow0[x1 + 1] = a + b + img[srow1 + x + 1] * 2; - trow1[x1 + 1] = b - a; - } - for (; x < w; ++x, ++x1) { - (a = img[srow0 + x]), (b = img[srow2 + x]); - trow0[x1] = a + b + img[srow1 + x] * 2; - trow1[x1] = b - a; - } - // make border - x = (w + 1) | 0; - trow0[0] = trow0[1]; - trow0[x] = trow0[w]; - trow1[0] = trow1[1]; - trow1[x] = trow1[w]; - // do horizontal convolution, interleave the results and store them - for (x = 0; x <= w - 4; x += 4) { - (a = trow1[x + 2]), - (b = trow1[x + 1]), - (c = trow1[x + 3]), - (d = trow1[x + 4]), - (e = trow0[x + 2]), - (f = trow0[x + 3]); - gxgy[drow++] = e - trow0[x]; - gxgy[drow++] = a + trow1[x] + b * 2; - gxgy[drow++] = f - trow0[x + 1]; - gxgy[drow++] = c + b + a * 2; - - gxgy[drow++] = trow0[x + 4] - e; - gxgy[drow++] = d + a + c * 2; - gxgy[drow++] = trow0[x + 5] - f; - gxgy[drow++] = trow1[x + 5] + c + d * 2; - } - for (; x < w; ++x) { - gxgy[drow++] = trow0[x + 2] - trow0[x]; - gxgy[drow++] = trow1[x + 2] + trow1[x] + trow1[x + 1] * 2; - } - } - this.cache.put_buffer(buf0_node); - this.cache.put_buffer(buf1_node); - } - - // please note: - // dst_(type) size should be cols = src.cols+1, rows = src.rows+1 - compute_integral_image(src: matrix_t, dst_sum: number[], dst_sqsum: number[], dst_tilted: any[]): void { - const w0 = src.cols | 0, - h0 = src.rows | 0, - src_d = src.data; - const w1 = (w0 + 1) | 0; - let s = 0, - s2 = 0, - p = 0, - pup = 0, - i = 0, - j = 0, - v = 0, - k = 0; - - if (dst_sum && dst_sqsum) { - // fill first row with zeros - for (; i < w1; ++i) { - (dst_sum[i] = 0), (dst_sqsum[i] = 0); - } - (p = (w1 + 1) | 0), (pup = 1); - for (i = 0, k = 0; i < h0; ++i, ++p, ++pup) { - s = s2 = 0; - for (j = 0; j <= w0 - 2; j += 2, k += 2, p += 2, pup += 2) { - v = src_d[k]; - (s += v), (s2 += v * v); - dst_sum[p] = dst_sum[pup] + s; - dst_sqsum[p] = dst_sqsum[pup] + s2; - - v = src_d[k + 1]; - (s += v), (s2 += v * v); - dst_sum[p + 1] = dst_sum[pup + 1] + s; - dst_sqsum[p + 1] = dst_sqsum[pup + 1] + s2; - } - for (; j < w0; ++j, ++k, ++p, ++pup) { - v = src_d[k]; - (s += v), (s2 += v * v); - dst_sum[p] = dst_sum[pup] + s; - dst_sqsum[p] = dst_sqsum[pup] + s2; - } - } - } else if (dst_sum) { - // fill first row with zeros - for (; i < w1; ++i) { - dst_sum[i] = 0; - } - (p = (w1 + 1) | 0), (pup = 1); - for (i = 0, k = 0; i < h0; ++i, ++p, ++pup) { - s = 0; - for (j = 0; j <= w0 - 2; j += 2, k += 2, p += 2, pup += 2) { - s += src_d[k]; - dst_sum[p] = dst_sum[pup] + s; - s += src_d[k + 1]; - dst_sum[p + 1] = dst_sum[pup + 1] + s; - } - for (; j < w0; ++j, ++k, ++p, ++pup) { - s += src_d[k]; - dst_sum[p] = dst_sum[pup] + s; - } - } - } else if (dst_sqsum) { - // fill first row with zeros - for (; i < w1; ++i) { - dst_sqsum[i] = 0; - } - (p = (w1 + 1) | 0), (pup = 1); - for (i = 0, k = 0; i < h0; ++i, ++p, ++pup) { - s2 = 0; - for (j = 0; j <= w0 - 2; j += 2, k += 2, p += 2, pup += 2) { - v = src_d[k]; - s2 += v * v; - dst_sqsum[p] = dst_sqsum[pup] + s2; - v = src_d[k + 1]; - s2 += v * v; - dst_sqsum[p + 1] = dst_sqsum[pup + 1] + s2; - } - for (; j < w0; ++j, ++k, ++p, ++pup) { - v = src_d[k]; - s2 += v * v; - dst_sqsum[p] = dst_sqsum[pup] + s2; - } - } - } - - if (dst_tilted) { - // fill first row with zeros - for (i = 0; i < w1; ++i) { - dst_tilted[i] = 0; - } - // diagonal - (p = (w1 + 1) | 0), (pup = 0); - for (i = 0, k = 0; i < h0; ++i, ++p, ++pup) { - for (j = 0; j <= w0 - 2; j += 2, k += 2, p += 2, pup += 2) { - dst_tilted[p] = src_d[k] + dst_tilted[pup]; - dst_tilted[p + 1] = src_d[k + 1] + dst_tilted[pup + 1]; - } - for (; j < w0; ++j, ++k, ++p, ++pup) { - dst_tilted[p] = src_d[k] + dst_tilted[pup]; - } - } - // diagonal - (p = (w1 + w0) | 0), (pup = w0); - for (i = 0; i < h0; ++i, p += w1, pup += w1) { - dst_tilted[p] += dst_tilted[pup]; - } - - for (j = w0 - 1; j > 0; --j) { - (p = j + h0 * w1), (pup = p - w1); - for (i = h0; i > 0; --i, p -= w1, pup -= w1) { - dst_tilted[p] += dst_tilted[pup] + dst_tilted[pup + 1]; - } - } - } - } - - equalize_histogram(src: matrix_t, dst: matrix_t): void { - const w = src.cols, - h = src.rows, - src_d = src.data; - - dst.resize(w, h, src.channel); - - const dst_d = dst.data, - size = w * h; - let i = 0, - prev = 0, - hist0, - norm; - - const hist0_node = this.cache.get_buffer(256 << 2); - hist0 = hist0_node.i32; - for (; i < 256; ++i) hist0[i] = 0; - for (i = 0; i < size; ++i) { - ++hist0[src_d[i]]; - } - - prev = hist0[0]; - for (i = 1; i < 256; ++i) { - prev = hist0[i] += prev; - } - - norm = 255 / size; - for (i = 0; i < size; ++i) { - dst_d[i] = (hist0[src_d[i]] * norm + 0.5) | 0; - } - this.cache.put_buffer(hist0_node); - } - - canny(src: matrix_t, dst: matrix_t, low_thresh: number, high_thresh: number): void { - const w = src.cols, - h = src.rows, - src_d = src.data; - - dst.resize(w, h, src.channel); - - const dst_d = dst.data; - let i = 0, - j: number = 0, - grad = 0, - w2 = w << 1, - _grad = 0, - suppress = 0, - f = 0, - x = 0, - y = 0, - s = 0; - let tg22x = 0, - tg67x = 0; - - // cache buffers - const dxdy_node = this.cache.get_buffer((h * w2) << 2); - const buf_node = this.cache.get_buffer((3 * (w + 2)) << 2); - const map_node = this.cache.get_buffer(((h + 2) * (w + 2)) << 2); - const stack_node = this.cache.get_buffer((h * w) << 2); - - const buf = buf_node.i32; - const map = map_node.i32; - const stack = stack_node.i32; - const dxdy = dxdy_node.i32; - const dxdy_m = new matrix_t(w, h, JSFEAT_CONSTANTS.S32C2_t, dxdy_node.data); - let row0 = 1, - row1 = (w + 2 + 1) | 0, - row2 = (2 * (w + 2) + 1) | 0, - map_w = (w + 2) | 0, - map_i: number = (map_w + 1) | 0, - stack_i = 0; - - this.sobel_derivatives(src, dxdy_m); - - if (low_thresh > high_thresh) { - i = low_thresh; - low_thresh = high_thresh; - high_thresh = i; - } - - i = (3 * (w + 2)) | 0; - while (--i >= 0) { - buf[i] = 0; - } - - i = ((h + 2) * (w + 2)) | 0; - while (--i >= 0) { - map[i] = 0; - } - - for (; j < w; ++j, grad += 2) { - //buf[row1+j] = Math.abs(dxdy[grad]) + Math.abs(dxdy[grad+1]); - (x = dxdy[grad]), (y = dxdy[grad + 1]); - //buf[row1+j] = x*x + y*y; - buf[row1 + j] = (x ^ (x >> 31)) - (x >> 31) + ((y ^ (y >> 31)) - (y >> 31)); - } - - for (i = 1; i <= h; ++i, grad += w2) { - if (i == h) { - j = row2 + w; - while (--j >= row2) { - buf[j] = 0; - } - } else { - for (j = 0; j < w; j++) { - //buf[row2+j] = Math.abs(dxdy[grad+(j<<1)]) + Math.abs(dxdy[grad+(j<<1)+1]); - (x = dxdy[grad + (j << 1)]), (y = dxdy[grad + (j << 1) + 1]); - //buf[row2+j] = x*x + y*y; - buf[row2 + j] = (x ^ (x >> 31)) - (x >> 31) + ((y ^ (y >> 31)) - (y >> 31)); - } - } - _grad = (grad - w2) | 0; - map[map_i - 1] = 0; - suppress = 0; - for (j = 0; j < w; ++j, _grad += 2) { - f = buf[row1 + j]; - if (f > low_thresh) { - x = dxdy[_grad]; - y = dxdy[_grad + 1]; - s = x ^ y; - // seems ot be faster than Math.abs - x = ((x ^ (x >> 31)) - (x >> 31)) | 0; - y = ((y ^ (y >> 31)) - (y >> 31)) | 0; - //x * tan(22.5) x * tan(67.5) == 2 * x + x * tan(22.5) - tg22x = x * 13573; - tg67x = tg22x + ((x + x) << 15); - y <<= 15; - if (y < tg22x) { - if (f > buf[row1 + j - 1] && f >= buf[row1 + j + 1]) { - if (f > high_thresh && !suppress && map[map_i + j - map_w] != 2) { - map[map_i + j] = 2; - suppress = 1; - stack[stack_i++] = map_i + j; - } else { - map[map_i + j] = 1; - } - continue; - } - } else if (y > tg67x) { - if (f > buf[row0 + j] && f >= buf[row2 + j]) { - if (f > high_thresh && !suppress && map[map_i + j - map_w] != 2) { - map[map_i + j] = 2; - suppress = 1; - stack[stack_i++] = map_i + j; - } else { - map[map_i + j] = 1; - } - continue; - } - } else { - s = s < 0 ? -1 : 1; - if (f > buf[row0 + j - s] && f > buf[row2 + j + s]) { - if (f > high_thresh && !suppress && map[map_i + j - map_w] != 2) { - map[map_i + j] = 2; - suppress = 1; - stack[stack_i++] = map_i + j; - } else { - map[map_i + j] = 1; - } - continue; - } - } - } - map[map_i + j] = 0; - suppress = 0; - } - map[map_i + w] = 0; - map_i += map_w; - j = row0; - row0 = row1; - row1 = row2; - row2 = j; - } - - j = map_i - map_w - 1; - for (i = 0; i < map_w; ++i, ++j) { - map[j] = 0; - } - // path following - while (stack_i > 0) { - map_i = stack[--stack_i]; - map_i -= map_w + 1; - if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); - map_i += 1; - if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); - map_i += 1; - if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); - map_i += map_w; - if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); - map_i -= 2; - if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); - map_i += map_w; - if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); - map_i += 1; - if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); - map_i += 1; - if (map[map_i] == 1) (map[map_i] = 2), (stack[stack_i++] = map_i); - } - - map_i = map_w + 1; - row0 = 0; - for (i = 0; i < h; ++i, map_i += map_w) { - for (j = 0; j < w; ++j) { - dst_d[row0++] = Number(map[map_i + j] == 2) * 0xff; - } - } - - // free buffers - this.cache.put_buffer(dxdy_node); - this.cache.put_buffer(buf_node); - this.cache.put_buffer(map_node); - this.cache.put_buffer(stack_node); - } - - // transform is 3x3 matrix_t - warp_perspective(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { - if (typeof fill_value === "undefined") { - fill_value = 0; - } - const src_width = src.cols | 0, - src_height = src.rows | 0, - dst_width = dst.cols | 0, - dst_height = dst.rows | 0; - const src_d = src.data, - dst_d = dst.data; - let x = 0, - y = 0, - off = 0, - ixs = 0, - iys = 0, - xs = 0.0, - ys = 0.0, - xs0 = 0.0, - ys0 = 0.0, - ws = 0.0, - sc = 0.0, - a = 0.0, - b = 0.0, - p0 = 0.0, - p1 = 0.0; - const td = transform.data; - const m00 = td[0], - m01 = td[1], - m02 = td[2], - m10 = td[3], - m11 = td[4], - m12 = td[5], - m20 = td[6], - m21 = td[7], - m22 = td[8]; - - for (let dptr = 0; y < dst_height; ++y) { - (xs0 = m01 * y + m02), (ys0 = m11 * y + m12), (ws = m21 * y + m22); - for (x = 0; x < dst_width; ++x, ++dptr, xs0 += m00, ys0 += m10, ws += m20) { - sc = 1.0 / ws; - (xs = xs0 * sc), (ys = ys0 * sc); - (ixs = xs | 0), (iys = ys | 0); - - if (xs > 0 && ys > 0 && ixs < src_width - 1 && iys < src_height - 1) { - a = Math.max(xs - ixs, 0.0); - b = Math.max(ys - iys, 0.0); - off = (src_width * iys + ixs) | 0; - - p0 = src_d[off] + a * (src_d[off + 1] - src_d[off]); - p1 = src_d[off + src_width] + a * (src_d[off + src_width + 1] - src_d[off + src_width]); - - dst_d[dptr] = p0 + b * (p1 - p0); - } else dst_d[dptr] = fill_value; - } - } - } - - // transform is 3x3 or 2x3 matrix_t only first 6 values referenced - warp_affine(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { - if (typeof fill_value === "undefined") { - fill_value = 0; - } - const src_width = src.cols, - src_height = src.rows, - dst_width = dst.cols, - dst_height = dst.rows; - const src_d = src.data, - dst_d = dst.data; - let x = 0, - y = 0, - off = 0, - ixs = 0, - iys = 0, - xs = 0.0, - ys = 0.0, - a = 0.0, - b = 0.0, - p0 = 0.0, - p1 = 0.0; - const td = transform.data; - const m00 = td[0], - m01 = td[1], - m02 = td[2], - m10 = td[3], - m11 = td[4], - m12 = td[5]; - - for (let dptr = 0; y < dst_height; ++y) { - xs = m01 * y + m02; - ys = m11 * y + m12; - for (x = 0; x < dst_width; ++x, ++dptr, xs += m00, ys += m10) { - ixs = xs | 0; - iys = ys | 0; - - if (ixs >= 0 && iys >= 0 && ixs < src_width - 1 && iys < src_height - 1) { - a = xs - ixs; - b = ys - iys; - off = src_width * iys + ixs; - - p0 = src_d[off] + a * (src_d[off + 1] - src_d[off]); - p1 = src_d[off + src_width] + a * (src_d[off + src_width + 1] - src_d[off + src_width]); - - dst_d[dptr] = p0 + b * (p1 - p0); - } else dst_d[dptr] = fill_value; - } - } - } - - // Basic RGB Skin detection filter - // from http://popscan.blogspot.fr/2012/08/skin-detection-in-digital-images.html - skindetector(src: { width: number; height: number; data: any[] }, dst: number[]): void { - let r, g, b, j; - let i = src.width * src.height; - while (i--) { - j = i * 4; - r = src.data[j]; - g = src.data[j + 1]; - b = src.data[j + 2]; - if (r > 95 && g > 40 && b > 20 && r > g && r > b && r - Math.min(g, b) > 15 && Math.abs(r - g) > 15) { - dst[i] = 255; - } else { - dst[i] = 0; - } - } - } -}; +jsfeatNext.imgproc = imgproc; jsfeatNext.math = math; From 73ec9d4b239620f23bdb8f2db2e530d0a02ae42f Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 13:33:37 +0200 Subject: [PATCH 02/10] refactor(fast_corners): de-duplicate fast_corners module (#47) Third de-duplication step of #47, following the #62/#63 pattern. - src/fast_corners/fast_corners.ts: replace the type-only stub with the REAL implementation moved verbatim from the monolith (set_threshold, detect, _cmp_offsets; imports _cmp_score_16 from ./fast_private, which was already a real module). - src/jsfeatNext.ts: shrinks by ~225 lines; attaches fast_corners from its module. Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (detector parity suite pins fast_corners.detect against original jsfeat); UMD bundle smoke-checked (instanceof chain, static inheritance, corner detection on a synthetic image). Co-Authored-By: Claude Fable 5 --- src/fast_corners/fast_corners.ts | 231 ++++++++++++++++++++++++++++++- src/jsfeatNext.ts | 225 +----------------------------- 2 files changed, 229 insertions(+), 227 deletions(-) diff --git a/src/fast_corners/fast_corners.ts b/src/fast_corners/fast_corners.ts index 798de3d..17b9e21 100644 --- a/src/fast_corners/fast_corners.ts +++ b/src/fast_corners/fast_corners.ts @@ -1,10 +1,235 @@ +import jsfeatNext from "../core/core"; import { matrix_t } from "../matrix_t/matrix_t"; import { point_t } from "../point_t/point_t"; -export class fast_corners { +import { _cmp_score_16 } from "./fast_private"; + +/** + * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). + * This file previously held a type-only stub whose methods threw + * "Method not implemented." — the implementation below is the inline code + * from the monolith, verbatim. + */ +export class fast_corners extends jsfeatNext { + private offsets16: Int32Array; + public _threshold: number; + public threshold_tab: Uint8Array; + public pixel_off: Int32Array; + public score_diff: Int32Array; + + constructor() { + super(); + this.offsets16 = new Int32Array([ + 0, 3, 1, 3, 2, 2, 3, 1, 3, 0, 3, -1, 2, -2, 1, -3, 0, -3, -1, -3, -2, -2, -3, -1, -3, 0, -3, 1, -2, 2, -1, + 3, + ]); + this.threshold_tab = new Uint8Array(512); + this._threshold = 20; + this.pixel_off = new Int32Array(25); + this.score_diff = new Int32Array(25); + } + set_threshold(threshold: number): number { - throw new Error("Method not implemented."); + this._threshold = Math.min(Math.max(threshold, 0), 255); + for (let i = -255; i <= 255; ++i) { + this.threshold_tab[i + 255] = i < -this._threshold ? 1 : i > this._threshold ? 2 : 0; + } + return this._threshold; } + detect(src: matrix_t, corners: point_t[], border: number): number { - throw new Error("Method not implemented."); + if (typeof border === "undefined") { + border = 3; + } + + const K = 8, + N = 25; + const img = src.data, + w = src.cols, + h = src.rows; + let i = 0, + j = 0, + k = 0, + vt = 0, + x = 0, + m3 = 0; + const buf_node = this.cache.get_buffer(3 * w); + const cpbuf_node = this.cache.get_buffer(((w + 1) * 3) << 2); + const buf = buf_node.u8; + const cpbuf = cpbuf_node.i32; + const pixel = this.pixel_off; + const sd = this.score_diff; + const sy = Math.max(3, border); + const ey = Math.min(h - 2, h - border); + const sx = Math.max(3, border); + const ex = Math.min(w - 3, w - border); + let _count = 0, + corners_cnt = 0, + pt; + const score_func = _cmp_score_16; + const thresh_tab = this.threshold_tab; + const threshold = this._threshold; + + let v = 0, + tab = 0, + d = 0, + ncorners = 0, + cornerpos = 0, + curr = 0, + ptr = 0, + prev = 0, + pprev = 0; + let jp1 = 0, + jm1 = 0, + score = 0; + + this._cmp_offsets(pixel, w, 16); + + // local vars are faster? + const pixel0 = pixel[0]; + const pixel1 = pixel[1]; + const pixel2 = pixel[2]; + const pixel3 = pixel[3]; + const pixel4 = pixel[4]; + const pixel5 = pixel[5]; + const pixel6 = pixel[6]; + const pixel7 = pixel[7]; + const pixel8 = pixel[8]; + const pixel9 = pixel[9]; + const pixel10 = pixel[10]; + const pixel11 = pixel[11]; + const pixel12 = pixel[12]; + const pixel13 = pixel[13]; + const pixel14 = pixel[14]; + const pixel15 = pixel[15]; + + for (i = 0; i < w * 3; ++i) { + buf[i] = 0; + } + + for (i = sy; i < ey; ++i) { + ptr = (i * w + sx) | 0; + m3 = (i - 3) % 3; + curr = (m3 * w) | 0; + cornerpos = (m3 * (w + 1)) | 0; + for (j = 0; j < w; ++j) buf[curr + j] = 0; + ncorners = 0; + + if (i < ey - 1) { + j = sx; + + for (; j < ex; ++j, ++ptr) { + v = img[ptr]; + tab = -v + 255; + d = thresh_tab[tab + img[ptr + pixel0]] | thresh_tab[tab + img[ptr + pixel8]]; + + if (d == 0) { + continue; + } + + d &= thresh_tab[tab + img[ptr + pixel2]] | thresh_tab[tab + img[ptr + pixel10]]; + d &= thresh_tab[tab + img[ptr + pixel4]] | thresh_tab[tab + img[ptr + pixel12]]; + d &= thresh_tab[tab + img[ptr + pixel6]] | thresh_tab[tab + img[ptr + pixel14]]; + + if (d == 0) { + continue; + } + + d &= thresh_tab[tab + img[ptr + pixel1]] | thresh_tab[tab + img[ptr + pixel9]]; + d &= thresh_tab[tab + img[ptr + pixel3]] | thresh_tab[tab + img[ptr + pixel11]]; + d &= thresh_tab[tab + img[ptr + pixel5]] | thresh_tab[tab + img[ptr + pixel13]]; + d &= thresh_tab[tab + img[ptr + pixel7]] | thresh_tab[tab + img[ptr + pixel15]]; + + if (d & 1) { + vt = v - threshold; + _count = 0; + + for (k = 0; k < N; ++k) { + x = img[ptr + pixel[k]]; + if (x < vt) { + ++_count; + if (_count > K) { + ++ncorners; + cpbuf[cornerpos + ncorners] = j; + buf[curr + j] = score_func(img, ptr, pixel, sd, threshold); + break; + } + } else { + _count = 0; + } + } + } + + if (d & 2) { + vt = v + threshold; + _count = 0; + + for (k = 0; k < N; ++k) { + x = img[ptr + pixel[k]]; + if (x > vt) { + ++_count; + if (_count > K) { + ++ncorners; + cpbuf[cornerpos + ncorners] = j; + buf[curr + j] = score_func(img, ptr, pixel, sd, threshold); + break; + } + } else { + _count = 0; + } + } + } + } + } + + cpbuf[cornerpos + w] = ncorners; + + if (i == sy) { + continue; + } + + m3 = (i - 4 + 3) % 3; + prev = (m3 * w) | 0; + cornerpos = (m3 * (w + 1)) | 0; + m3 = (i - 5 + 3) % 3; + pprev = (m3 * w) | 0; + + ncorners = cpbuf[cornerpos + w]; + + for (k = 0; k < ncorners; ++k) { + j = cpbuf[cornerpos + k]; + jp1 = (j + 1) | 0; + jm1 = (j - 1) | 0; + score = buf[prev + j]; + if ( + score > buf[prev + jp1] && + score > buf[prev + jm1] && + score > buf[pprev + jm1] && + score > buf[pprev + j] && + score > buf[pprev + jp1] && + score > buf[curr + jm1] && + score > buf[curr + j] && + score > buf[curr + jp1] + ) { + // save corner + pt = corners[corners_cnt]; + (pt.x = j), (pt.y = i - 1), (pt.score = score); + corners_cnt++; + } + } + } // y loop + this.cache.put_buffer(buf_node); + this.cache.put_buffer(cpbuf_node); + return corners_cnt; + } + + private _cmp_offsets(pixel: Uint8Array | Int32Array, step: number, pattern_size: number): void { + let k = 0; + const offsets = this.offsets16; + for (; k < pattern_size; ++k) { + pixel[k] = offsets[k << 1] + offsets[(k << 1) + 1] * step; + } + for (; k < 25; ++k) { + pixel[k] = pixel[k - pattern_size]; + } } } diff --git a/src/jsfeatNext.ts b/src/jsfeatNext.ts index e29e5ad..00cb868 100644 --- a/src/jsfeatNext.ts +++ b/src/jsfeatNext.ts @@ -557,230 +557,7 @@ jsfeatNext.matrix_t = matrix_t; jsfeatNext.keypoint_t = keypoint_t; -jsfeatNext.fast_corners = class fast_corners extends jsfeatNext { - private offsets16: Int32Array; - public _threshold: number; - public threshold_tab: Uint8Array; - public pixel_off: Int32Array; - public score_diff: Int32Array; - - constructor() { - super(); - this.offsets16 = new Int32Array([ - 0, 3, 1, 3, 2, 2, 3, 1, 3, 0, 3, -1, 2, -2, 1, -3, 0, -3, -1, -3, -2, -2, -3, -1, -3, 0, -3, 1, -2, 2, -1, - 3, - ]); - this.threshold_tab = new Uint8Array(512); - this._threshold = 20; - this.pixel_off = new Int32Array(25); - this.score_diff = new Int32Array(25); - } - - set_threshold(threshold: number): number { - this._threshold = Math.min(Math.max(threshold, 0), 255); - for (let i = -255; i <= 255; ++i) { - this.threshold_tab[i + 255] = i < -this._threshold ? 1 : i > this._threshold ? 2 : 0; - } - return this._threshold; - } - - detect(src: matrix_t, corners: point_t[], border: number): number { - if (typeof border === "undefined") { - border = 3; - } - - const K = 8, - N = 25; - const img = src.data, - w = src.cols, - h = src.rows; - let i = 0, - j = 0, - k = 0, - vt = 0, - x = 0, - m3 = 0; - const buf_node = this.cache.get_buffer(3 * w); - const cpbuf_node = this.cache.get_buffer(((w + 1) * 3) << 2); - const buf = buf_node.u8; - const cpbuf = cpbuf_node.i32; - const pixel = this.pixel_off; - const sd = this.score_diff; - const sy = Math.max(3, border); - const ey = Math.min(h - 2, h - border); - const sx = Math.max(3, border); - const ex = Math.min(w - 3, w - border); - let _count = 0, - corners_cnt = 0, - pt; - const score_func = _cmp_score_16; - const thresh_tab = this.threshold_tab; - const threshold = this._threshold; - - let v = 0, - tab = 0, - d = 0, - ncorners = 0, - cornerpos = 0, - curr = 0, - ptr = 0, - prev = 0, - pprev = 0; - let jp1 = 0, - jm1 = 0, - score = 0; - - this._cmp_offsets(pixel, w, 16); - - // local vars are faster? - const pixel0 = pixel[0]; - const pixel1 = pixel[1]; - const pixel2 = pixel[2]; - const pixel3 = pixel[3]; - const pixel4 = pixel[4]; - const pixel5 = pixel[5]; - const pixel6 = pixel[6]; - const pixel7 = pixel[7]; - const pixel8 = pixel[8]; - const pixel9 = pixel[9]; - const pixel10 = pixel[10]; - const pixel11 = pixel[11]; - const pixel12 = pixel[12]; - const pixel13 = pixel[13]; - const pixel14 = pixel[14]; - const pixel15 = pixel[15]; - - for (i = 0; i < w * 3; ++i) { - buf[i] = 0; - } - - for (i = sy; i < ey; ++i) { - ptr = (i * w + sx) | 0; - m3 = (i - 3) % 3; - curr = (m3 * w) | 0; - cornerpos = (m3 * (w + 1)) | 0; - for (j = 0; j < w; ++j) buf[curr + j] = 0; - ncorners = 0; - - if (i < ey - 1) { - j = sx; - - for (; j < ex; ++j, ++ptr) { - v = img[ptr]; - tab = -v + 255; - d = thresh_tab[tab + img[ptr + pixel0]] | thresh_tab[tab + img[ptr + pixel8]]; - - if (d == 0) { - continue; - } - - d &= thresh_tab[tab + img[ptr + pixel2]] | thresh_tab[tab + img[ptr + pixel10]]; - d &= thresh_tab[tab + img[ptr + pixel4]] | thresh_tab[tab + img[ptr + pixel12]]; - d &= thresh_tab[tab + img[ptr + pixel6]] | thresh_tab[tab + img[ptr + pixel14]]; - - if (d == 0) { - continue; - } - - d &= thresh_tab[tab + img[ptr + pixel1]] | thresh_tab[tab + img[ptr + pixel9]]; - d &= thresh_tab[tab + img[ptr + pixel3]] | thresh_tab[tab + img[ptr + pixel11]]; - d &= thresh_tab[tab + img[ptr + pixel5]] | thresh_tab[tab + img[ptr + pixel13]]; - d &= thresh_tab[tab + img[ptr + pixel7]] | thresh_tab[tab + img[ptr + pixel15]]; - - if (d & 1) { - vt = v - threshold; - _count = 0; - - for (k = 0; k < N; ++k) { - x = img[ptr + pixel[k]]; - if (x < vt) { - ++_count; - if (_count > K) { - ++ncorners; - cpbuf[cornerpos + ncorners] = j; - buf[curr + j] = score_func(img, ptr, pixel, sd, threshold); - break; - } - } else { - _count = 0; - } - } - } - - if (d & 2) { - vt = v + threshold; - _count = 0; - - for (k = 0; k < N; ++k) { - x = img[ptr + pixel[k]]; - if (x > vt) { - ++_count; - if (_count > K) { - ++ncorners; - cpbuf[cornerpos + ncorners] = j; - buf[curr + j] = score_func(img, ptr, pixel, sd, threshold); - break; - } - } else { - _count = 0; - } - } - } - } - } - - cpbuf[cornerpos + w] = ncorners; - - if (i == sy) { - continue; - } - - m3 = (i - 4 + 3) % 3; - prev = (m3 * w) | 0; - cornerpos = (m3 * (w + 1)) | 0; - m3 = (i - 5 + 3) % 3; - pprev = (m3 * w) | 0; - - ncorners = cpbuf[cornerpos + w]; - - for (k = 0; k < ncorners; ++k) { - j = cpbuf[cornerpos + k]; - jp1 = (j + 1) | 0; - jm1 = (j - 1) | 0; - score = buf[prev + j]; - if ( - score > buf[prev + jp1] && - score > buf[prev + jm1] && - score > buf[pprev + jm1] && - score > buf[pprev + j] && - score > buf[pprev + jp1] && - score > buf[curr + jm1] && - score > buf[curr + j] && - score > buf[curr + jp1] - ) { - // save corner - pt = corners[corners_cnt]; - (pt.x = j), (pt.y = i - 1), (pt.score = score); - corners_cnt++; - } - } - } // y loop - this.cache.put_buffer(buf_node); - this.cache.put_buffer(cpbuf_node); - return corners_cnt; - } - - private _cmp_offsets(pixel: Uint8Array | Int32Array, step: number, pattern_size: number): void { - let k = 0; - const offsets = this.offsets16; - for (; k < pattern_size; ++k) { - pixel[k] = offsets[k << 1] + offsets[(k << 1) + 1] * step; - } - for (; k < 25; ++k) { - pixel[k] = pixel[k - pattern_size]; - } - } -}; +jsfeatNext.fast_corners = fast_corners; jsfeatNext.imgproc = imgproc; From 201752339673307d4d83b635843f44fc5fd29931 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 14:40:34 +0200 Subject: [PATCH 03/10] refactor(pyramid_t): de-duplicate pyramid_t module (#47) Fourth de-duplication step of #47, following the established pattern. - src/pyramid_t/pyramid_t.ts: replace the type-only stub with the REAL implementation moved verbatim from the monolith (allocate, build). Only deliberate change: the constructor instantiates imgproc via direct module import instead of the jsfeatNext.imgproc static slot. - src/jsfeatNext.ts: shrinks by ~43 lines. Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (the optical_flow_lk parity test exercises pyramid_t against the oracle); UMD bundle smoke-checked (instanceof, allocate/build on a synthetic image with correct per-level dimensions). Co-Authored-By: Claude Fable 5 --- src/jsfeatNext.ts | 44 +---------------------------- src/pyramid_t/pyramid_t.ts | 58 ++++++++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 49 deletions(-) diff --git a/src/jsfeatNext.ts b/src/jsfeatNext.ts index 00cb868..7b4e7ea 100644 --- a/src/jsfeatNext.ts +++ b/src/jsfeatNext.ts @@ -507,49 +507,7 @@ class homography2d extends motion_model { jsfeatNext.cache = cache; -jsfeatNext.pyramid_t = class pyramid_t extends jsfeatNext { - public levels: number; - public data: any; - private pyrdown: any; - - constructor(levels: number) { - super(); - this.levels = levels | 0; - this.data = new Array(levels); - const _imgproc = new jsfeatNext.imgproc(); - this.pyrdown = _imgproc.pyrdown; - } - - allocate(start_w: number, start_h: number, data_type: number): void { - let i = this.levels; - while (--i >= 0) { - this.data[i] = new matrix_t(start_w >> i, start_h >> i, data_type); - } - } - - build(input: matrix_t, skip_first_level: boolean): void { - if (typeof skip_first_level === "undefined") { - skip_first_level = true; - } - // just copy data to first level - let i = 2, - a = input, - b: any = this.data[0]; - if (!skip_first_level) { - let j = input.cols * input.rows; - while (--j >= 0) { - b.data[j] = input.data[j]; - } - } - b = this.data[1]; - this.pyrdown(a, b); - for (; i < this.levels; ++i) { - a = b; - b = this.data[i]; - this.pyrdown(a, b); - } - } -}; +jsfeatNext.pyramid_t = pyramid_t; jsfeatNext.transform = transform; diff --git a/src/pyramid_t/pyramid_t.ts b/src/pyramid_t/pyramid_t.ts index 8295464..f2ddb7c 100644 --- a/src/pyramid_t/pyramid_t.ts +++ b/src/pyramid_t/pyramid_t.ts @@ -1,8 +1,54 @@ +import jsfeatNext from "../core/core"; import { matrix_t } from "../matrix_t/matrix_t"; -export class pyramid_t { - data: any; - levels: number; - constructor(levels: number) {} - allocate(start_w: number, start_h: number, data_type: number): void {} - build(input: matrix_t, skip_first_level: boolean): void {} +import { imgproc } from "../imgproc/imgproc"; + +/** + * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). + * This file previously held a type-only stub — the implementation below is the + * inline code from the monolith, verbatim (the only change: the constructor + * instantiates the imgproc module directly instead of via the + * jsfeatNext.imgproc static slot). + */ +export class pyramid_t extends jsfeatNext { + public levels: number; + public data: any; + private pyrdown: any; + + constructor(levels: number) { + super(); + this.levels = levels | 0; + this.data = new Array(levels); + const _imgproc = new imgproc(); + this.pyrdown = _imgproc.pyrdown; + } + + allocate(start_w: number, start_h: number, data_type: number): void { + let i = this.levels; + while (--i >= 0) { + this.data[i] = new matrix_t(start_w >> i, start_h >> i, data_type); + } + } + + build(input: matrix_t, skip_first_level: boolean): void { + if (typeof skip_first_level === "undefined") { + skip_first_level = true; + } + // just copy data to first level + let i = 2, + a = input, + b: any = this.data[0]; + if (!skip_first_level) { + let j = input.cols * input.rows; + while (--j >= 0) { + b.data[j] = input.data[j]; + } + } + b = this.data[1]; + this.pyrdown(a, b); + for (; i < this.levels; ++i) { + a = b; + b = this.data[i]; + this.pyrdown(a, b); + } + } } From 916febf92c66aa06377756479c931a5f8dd0b797 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 16:24:43 +0200 Subject: [PATCH 04/10] refactor(linalg): de-duplicate linalg module (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fifth de-duplication step of #47, following the established pattern. - src/linalg/linalg.ts: replace the type-only stub with the REAL implementation moved verbatim from the monolith (JacobiImpl, JacobiSVDImpl, lu_solve, cholesky_solve, svd_decompose, svd_solve, svd_invert, eigenVV). Imports swap/hypot from ./linalg_base and matmath — both already real modules. - src/jsfeatNext.ts: shrinks by ~740 lines (largest single extraction). Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (6 parity tests pin linalg: LU, Cholesky, SVD x3, eigenVV vs original jsfeat); UMD bundle smoke-checked (instanceof, static inheritance, lu_solve on an SPD system). Co-Authored-By: Claude Fable 5 --- src/jsfeatNext.ts | 745 +------------------------------------------ src/linalg/linalg.ts | 731 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 723 insertions(+), 753 deletions(-) diff --git a/src/jsfeatNext.ts b/src/jsfeatNext.ts index 7b4e7ea..9763759 100644 --- a/src/jsfeatNext.ts +++ b/src/jsfeatNext.ts @@ -523,750 +523,7 @@ jsfeatNext.math = math; jsfeatNext.matmath = matmath; -jsfeatNext.linalg = class linalg extends jsfeatNext { - public matmath: matmath; - - constructor() { - super(); - this.matmath = new matmath(); - } - - JacobiImpl( - A: Int32Array | Float32Array | Float64Array, - astep: number, - W: Int32Array | Float32Array | Float64Array, - V: Int32Array | Float32Array | Float64Array, - vstep: number, - n: number - ): void { - const eps = JSFEAT_CONSTANTS.EPSILON; - let i = 0, - j = 0, - k = 0, - m = 0, - l = 0, - idx = 0, - _in = 0, - _in2 = 0; - let iters = 0, - max_iter = n * n * 30; - let mv = 0.0, - val = 0.0, - p = 0.0, - y = 0.0, - t = 0.0, - s = 0.0, - c = 0.0, - a0 = 0.0, - b0 = 0.0; - - const indR_buff = this.cache.get_buffer(n << 2); - const indC_buff = this.cache.get_buffer(n << 2); - const indR = indR_buff.i32; - const indC = indC_buff.i32; - - if (V) { - for (; i < n; i++) { - k = i * vstep; - for (j = 0; j < n; j++) { - V[k + j] = 0.0; - } - V[k + i] = 1.0; - } - } - - for (k = 0; k < n; k++) { - W[k] = A[(astep + 1) * k]; - if (k < n - 1) { - for (m = k + 1, mv = Math.abs(A[astep * k + m]), i = k + 2; i < n; i++) { - val = Math.abs(A[astep * k + i]); - if (mv < val) (mv = val), (m = i); - } - indR[k] = m; - } - if (k > 0) { - for (m = 0, mv = Math.abs(A[k]), i = 1; i < k; i++) { - val = Math.abs(A[astep * i + k]); - if (mv < val) (mv = val), (m = i); - } - indC[k] = m; - } - } - - if (n > 1) - for (; iters < max_iter; iters++) { - // find index (k,l) of pivot p - for (k = 0, mv = Math.abs(A[indR[0]]), i = 1; i < n - 1; i++) { - val = Math.abs(A[astep * i + indR[i]]); - if (mv < val) (mv = val), (k = i); - } - l = indR[k]; - for (i = 1; i < n; i++) { - val = Math.abs(A[astep * indC[i] + i]); - if (mv < val) (mv = val), (k = indC[i]), (l = i); - } - - p = A[astep * k + l]; - - if (Math.abs(p) <= eps) break; - - y = (W[l] - W[k]) * 0.5; - t = Math.abs(y) + hypot(p, y); - s = hypot(p, t); - c = t / s; - s = p / s; - t = (p / t) * p; - if (y < 0) (s = -s), (t = -t); - A[astep * k + l] = 0; - - W[k] -= t; - W[l] += t; - - // rotate rows and columns k and l - for (i = 0; i < k; i++) { - _in = astep * i + k; - _in2 = astep * i + l; - a0 = A[_in]; - b0 = A[_in2]; - A[_in] = a0 * c - b0 * s; - A[_in2] = a0 * s + b0 * c; - } - for (i = k + 1; i < l; i++) { - _in = astep * k + i; - _in2 = astep * i + l; - a0 = A[_in]; - b0 = A[_in2]; - A[_in] = a0 * c - b0 * s; - A[_in2] = a0 * s + b0 * c; - } - i = l + 1; - _in = astep * k + i; - _in2 = astep * l + i; - for (; i < n; i++, _in++, _in2++) { - a0 = A[_in]; - b0 = A[_in2]; - A[_in] = a0 * c - b0 * s; - A[_in2] = a0 * s + b0 * c; - } - - // rotate eigenvectors - if (V) { - _in = vstep * k; - _in2 = vstep * l; - for (i = 0; i < n; i++, _in++, _in2++) { - a0 = V[_in]; - b0 = V[_in2]; - V[_in] = a0 * c - b0 * s; - V[_in2] = a0 * s + b0 * c; - } - } - - for (j = 0; j < 2; j++) { - idx = j == 0 ? k : l; - if (idx < n - 1) { - for (m = idx + 1, mv = Math.abs(A[astep * idx + m]), i = idx + 2; i < n; i++) { - val = Math.abs(A[astep * idx + i]); - if (mv < val) (mv = val), (m = i); - } - indR[idx] = m; - } - if (idx > 0) { - for (m = 0, mv = Math.abs(A[idx]), i = 1; i < idx; i++) { - val = Math.abs(A[astep * i + idx]); - if (mv < val) (mv = val), (m = i); - } - indC[idx] = m; - } - } - } - - // sort eigenvalues & eigenvectors - for (k = 0; k < n - 1; k++) { - m = k; - for (i = k + 1; i < n; i++) { - if (W[m] < W[i]) m = i; - } - if (k != m) { - swap(W, m, k, mv); - if (V) { - for (i = 0; i < n; i++) { - swap(V, vstep * m + i, vstep * k + i, mv); - } - } - } - } - - this.cache.put_buffer(indR_buff); - this.cache.put_buffer(indC_buff); - } - - JacobiSVDImpl( - At: Int32Array | Float32Array | Float64Array, - astep: number, - _W: Int32Array | Float32Array | Float64Array, - Vt: Int32Array | Float32Array | Float64Array, - vstep: number, - m: number, - n: number, - n1: number - ): void { - const eps = JSFEAT_CONSTANTS.EPSILON * 2.0; - const minval = JSFEAT_CONSTANTS.FLT_MIN; - let i = 0, - j = 0, - k = 0, - iter = 0, - max_iter = Math.max(m, 30); - let Ai = 0, - Aj = 0, - Vi = 0, - Vj = 0, - changed = 0; - let c = 0.0, - s = 0.0, - t = 0.0; - let t0 = 0.0, - t1 = 0.0, - sd = 0.0, - beta = 0.0, - gamma = 0.0, - delta = 0.0, - a = 0.0, - p = 0.0, - b = 0.0; - let seed = 0x1234; - let val = 0.0, - val0 = 0.0, - asum = 0.0; - - const W_buff = this.cache.get_buffer(n << 3); - const W = W_buff.f64; - - for (; i < n; i++) { - for (k = 0, sd = 0; k < m; k++) { - t = At[i * astep + k]; - sd += t * t; - } - W[i] = sd; - - if (Vt) { - for (k = 0; k < n; k++) { - Vt[i * vstep + k] = 0; - } - Vt[i * vstep + i] = 1; - } - } - - for (; iter < max_iter; iter++) { - changed = 0; - - for (i = 0; i < n - 1; i++) { - for (j = i + 1; j < n; j++) { - (Ai = (i * astep) | 0), (Aj = (j * astep) | 0); - (a = W[i]), (p = 0), (b = W[j]); - - k = 2; - p += At[Ai] * At[Aj]; - p += At[Ai + 1] * At[Aj + 1]; - - for (; k < m; k++) p += At[Ai + k] * At[Aj + k]; - - if (Math.abs(p) <= eps * Math.sqrt(a * b)) continue; - - p *= 2.0; - (beta = a - b), (gamma = hypot(p, beta)); - if (beta < 0) { - delta = (gamma - beta) * 0.5; - s = Math.sqrt(delta / gamma); - c = p / (gamma * s * 2.0); - } else { - c = Math.sqrt((gamma + beta) / (gamma * 2.0)); - s = p / (gamma * c * 2.0); - } - - (a = 0.0), (b = 0.0); - - k = 2; // unroll - t0 = c * At[Ai] + s * At[Aj]; - t1 = -s * At[Ai] + c * At[Aj]; - At[Ai] = t0; - At[Aj] = t1; - a += t0 * t0; - b += t1 * t1; - - t0 = c * At[Ai + 1] + s * At[Aj + 1]; - t1 = -s * At[Ai + 1] + c * At[Aj + 1]; - At[Ai + 1] = t0; - At[Aj + 1] = t1; - a += t0 * t0; - b += t1 * t1; - - for (; k < m; k++) { - t0 = c * At[Ai + k] + s * At[Aj + k]; - t1 = -s * At[Ai + k] + c * At[Aj + k]; - At[Ai + k] = t0; - At[Aj + k] = t1; - - a += t0 * t0; - b += t1 * t1; - } - - W[i] = a; - W[j] = b; - - changed = 1; - - if (Vt) { - (Vi = (i * vstep) | 0), (Vj = (j * vstep) | 0); - - k = 2; - t0 = c * Vt[Vi] + s * Vt[Vj]; - t1 = -s * Vt[Vi] + c * Vt[Vj]; - Vt[Vi] = t0; - Vt[Vj] = t1; - - t0 = c * Vt[Vi + 1] + s * Vt[Vj + 1]; - t1 = -s * Vt[Vi + 1] + c * Vt[Vj + 1]; - Vt[Vi + 1] = t0; - Vt[Vj + 1] = t1; - - for (; k < n; k++) { - t0 = c * Vt[Vi + k] + s * Vt[Vj + k]; - t1 = -s * Vt[Vi + k] + c * Vt[Vj + k]; - Vt[Vi + k] = t0; - Vt[Vj + k] = t1; - } - } - } - } - if (changed == 0) break; - } - - for (i = 0; i < n; i++) { - for (k = 0, sd = 0; k < m; k++) { - t = At[i * astep + k]; - sd += t * t; - } - W[i] = Math.sqrt(sd); - } - - for (i = 0; i < n - 1; i++) { - j = i; - for (k = i + 1; k < n; k++) { - if (W[j] < W[k]) j = k; - } - if (i != j) { - swap(W, i, j, sd); - if (Vt) { - for (k = 0; k < m; k++) { - swap(At, i * astep + k, j * astep + k, t); - } - - for (k = 0; k < n; k++) { - swap(Vt, i * vstep + k, j * vstep + k, t); - } - } - } - } - - for (i = 0; i < n; i++) { - _W[i] = W[i]; - } - - if (!Vt) { - this.cache.put_buffer(W_buff); - return; - } - - for (i = 0; i < n1; i++) { - sd = i < n ? W[i] : 0; - - while (sd <= minval) { - // if we got a zero singular value, then in order to get the corresponding left singular vector - // we generate a random vector, project it to the previously computed left singular vectors, - // subtract the projection and normalize the difference. - val0 = 1.0 / m; - for (k = 0; k < m; k++) { - seed = seed * 214013 + 2531011; - val = ((seed >> 16) & 0x7fff & 256) != 0 ? val0 : -val0; - At[i * astep + k] = val; - } - for (iter = 0; iter < 2; iter++) { - for (j = 0; j < i; j++) { - sd = 0; - for (k = 0; k < m; k++) { - sd += At[i * astep + k] * At[j * astep + k]; - } - asum = 0.0; - for (k = 0; k < m; k++) { - t = At[i * astep + k] - sd * At[j * astep + k]; - At[i * astep + k] = t; - asum += Math.abs(t); - } - asum = asum ? 1.0 / asum : 0; - for (k = 0; k < m; k++) { - At[i * astep + k] *= asum; - } - } - } - sd = 0; - for (k = 0; k < m; k++) { - t = At[i * astep + k]; - sd += t * t; - } - sd = Math.sqrt(sd); - } - - s = 1.0 / sd; - for (k = 0; k < m; k++) { - At[i * astep + k] *= s; - } - } - - this.cache.put_buffer(W_buff); - } - - lu_solve(A: matrix_t, B: matrix_t): number { - let i = 0, - j = 0, - k = 0, - p = 1, - astep = A.cols; - const ad = A.data, - bd = B.data; - let t, alpha, d, s; - - for (i = 0; i < astep; i++) { - k = i; - for (j = i + 1; j < astep; j++) { - if (Math.abs(ad[j * astep + i]) > Math.abs(ad[k * astep + i])) { - k = j; - } - } - - if (Math.abs(ad[k * astep + i]) < JSFEAT_CONSTANTS.EPSILON) { - return 0; // FAILED - } - - if (k != i) { - for (j = i; j < astep; j++) { - swap(ad, i * astep + j, k * astep + j, t); - } - - swap(bd, i, k, t); - p = -p; - } - - d = -1.0 / ad[i * astep + i]; - - for (j = i + 1; j < astep; j++) { - alpha = ad[j * astep + i] * d; - - for (k = i + 1; k < astep; k++) { - ad[j * astep + k] += alpha * ad[i * astep + k]; - } - - bd[j] += alpha * bd[i]; - } - - ad[i * astep + i] = -d; - } - - for (i = astep - 1; i >= 0; i--) { - s = bd[i]; - for (k = i + 1; k < astep; k++) { - s -= ad[i * astep + k] * bd[k]; - } - bd[i] = s * ad[i * astep + i]; - } - - return 1; // OK - } - - cholesky_solve(A: matrix_t, B: matrix_t): number { - let col = 0, - row = 0, - col2 = 0, - cs = 0, - rs = 0, - i = 0, - j = 0; - const size = A.cols; - const ad = A.data, - bd = B.data; - let val, inv_diag; - - for (col = 0; col < size; col++) { - inv_diag = 1.0; - cs = col * size; - rs = cs; - for (row = col; row < size; row++) { - // correct for the parts of cholesky already computed - val = ad[rs + col]; - for (col2 = 0; col2 < col; col2++) { - val -= ad[col2 * size + col] * ad[rs + col2]; - } - if (row == col) { - // this is the diagonal element so don't divide - ad[rs + col] = val; - if (val == 0) { - return 0; - } - inv_diag = 1.0 / val; - } else { - // cache the value without division in the upper half - ad[cs + row] = val; - // divide my the diagonal element for all others - ad[rs + col] = val * inv_diag; - } - rs = rs + size; - } - } - - // first backsub through L - cs = 0; - for (i = 0; i < size; i++) { - val = bd[i]; - for (j = 0; j < i; j++) { - val -= ad[cs + j] * bd[j]; - } - bd[i] = val; - cs = cs + size; - } - // backsub through diagonal - cs = 0; - for (i = 0; i < size; i++) { - bd[i] /= ad[cs + i]; - cs = cs + size; - } - // backsub through L Transpose - i = size - 1; - for (; i >= 0; i--) { - val = bd[i]; - j = i + 1; - cs = j * size; - for (; j < size; j++) { - val -= ad[cs + i] * bd[j]; - cs = cs + size; - } - bd[i] = val; - } - - return 1; - } - - svd_decompose(A: any, W: matrix_t, U: matrix_t, V: matrix_t, options: number): void { - if (typeof options === "undefined") { - options = 0; - } - let at = 0, - i = 0, - j = 0, - _m = A.rows, - _n = A.cols, - m = _m, - n = _n; - const dt = A.type | JSFEAT_CONSTANTS.C1_t; // we only work with single channel - - if (m < n) { - at = 1; - i = m; - m = n; - n = i; - } - - const a_buff = this.cache.get_buffer((m * m) << 3); - const w_buff = this.cache.get_buffer(n << 3); - const v_buff = this.cache.get_buffer((n * n) << 3); - - const a_mt = new matrix_t(m, m, dt, a_buff.data); - const w_mt = new matrix_t(1, n, dt, w_buff.data); - const v_mt = new matrix_t(n, n, dt, v_buff.data); - - if (at == 0) { - // transpose - this.matmath.transpose(a_mt, A); - } else { - for (i = 0; i < _n * _m; i++) { - a_mt.data[i] = A.data[i]; - } - for (; i < n * m; i++) { - a_mt.data[i] = 0; - } - } - - this.JacobiSVDImpl(a_mt.data, m, w_mt.data, v_mt.data, n, m, n, m); - - if (W) { - for (i = 0; i < n; i++) { - W.data[i] = w_mt.data[i]; - } - for (; i < _n; i++) { - W.data[i] = 0; - } - } - - if (at == 0) { - if (U && options & JSFEAT_CONSTANTS.SVD_U_T) { - i = m * m; - while (--i >= 0) { - U.data[i] = a_mt.data[i]; - } - } else if (U) { - this.matmath.transpose(U, a_mt); - } - - if (V && options & JSFEAT_CONSTANTS.SVD_V_T) { - i = n * n; - while (--i >= 0) { - V.data[i] = v_mt.data[i]; - } - } else if (V) { - this.matmath.transpose(V, v_mt); - } - } else { - if (U && options & JSFEAT_CONSTANTS.SVD_U_T) { - i = n * n; - while (--i >= 0) { - U.data[i] = v_mt.data[i]; - } - } else if (U) { - this.matmath.transpose(U, v_mt); - } - - if (V && options & JSFEAT_CONSTANTS.SVD_V_T) { - i = m * m; - while (--i >= 0) { - V.data[i] = a_mt.data[i]; - } - } else if (V) { - this.matmath.transpose(V, a_mt); - } - } - - this.cache.put_buffer(a_buff); - this.cache.put_buffer(w_buff); - this.cache.put_buffer(v_buff); - } - - svd_solve(A: matrix_t, X: matrix_t, B: matrix_t): void { - let i = 0, - j = 0, - k = 0; - let pu = 0, - pv = 0; - const nrows = A.rows, - ncols = A.cols; - let sum = 0.0, - xsum = 0.0, - tol = 0.0; - const dt = A.type | JSFEAT_CONSTANTS.C1_t; - - const u_buff = this.cache.get_buffer((nrows * nrows) << 3); - const w_buff = this.cache.get_buffer(ncols << 3); - const v_buff = this.cache.get_buffer((ncols * ncols) << 3); - - const u_mt = new matrix_t(nrows, nrows, dt, u_buff.data); - const w_mt = new matrix_t(1, ncols, dt, w_buff.data); - const v_mt = new matrix_t(ncols, ncols, dt, v_buff.data); - - const bd = B.data, - ud = u_mt.data, - wd = w_mt.data, - vd = v_mt.data; - - this.svd_decompose(A, w_mt, u_mt, v_mt, 0); - - tol = JSFEAT_CONSTANTS.EPSILON * wd[0] * ncols; - - for (; i < ncols; i++, pv += ncols) { - xsum = 0.0; - for (j = 0; j < ncols; j++) { - if (wd[j] > tol) { - for (k = 0, sum = 0.0, pu = 0; k < nrows; k++, pu += ncols) { - sum += ud[pu + j] * bd[k]; - } - xsum += (sum * vd[pv + j]) / wd[j]; - } - } - X.data[i] = xsum; - } - - this.cache.put_buffer(u_buff); - this.cache.put_buffer(w_buff); - this.cache.put_buffer(v_buff); - } - - svd_invert(Ai: matrix_t, A: matrix_t): void { - let i = 0, - j = 0, - k = 0; - let pu = 0, - pv = 0, - pa = 0; - const nrows = A.rows, - ncols = A.cols; - let sum = 0.0, - tol = 0.0; - const dt = A.type | JSFEAT_CONSTANTS.C1_t; - - //const u_buff = cache1.get_buffer((nrows * nrows) << 3); - const u_buff = this.cache.get_buffer((nrows * nrows) << 3); - const w_buff = this.cache.get_buffer(ncols << 3); - const v_buff = this.cache.get_buffer((ncols * ncols) << 3); - const u_mt = new matrix_t(nrows, nrows, dt, u_buff.data); - const w_mt = new matrix_t(1, ncols, dt, w_buff.data); - const v_mt = new matrix_t(ncols, ncols, dt, v_buff.data); - - const id = Ai.data, - ud = u_mt.data, - wd = w_mt.data, - vd = v_mt.data; - - this.svd_decompose(A, w_mt, u_mt, v_mt, 0); - - tol = JSFEAT_CONSTANTS.EPSILON * wd[0] * ncols; - - for (; i < ncols; i++, pv += ncols) { - for (j = 0, pu = 0; j < nrows; j++, pa++) { - for (k = 0, sum = 0.0; k < ncols; k++, pu++) { - if (wd[k] > tol) sum += (vd[pv + k] * ud[pu]) / wd[k]; - } - id[pa] = sum; - } - } - - this.cache.put_buffer(u_buff); - this.cache.put_buffer(w_buff); - this.cache.put_buffer(v_buff); - } - - eigenVV(A: matrix_t, vects: matrix_t, vals?: matrix_t): void { - let n = A.cols, - i = n * n; - const dt = A.type | JSFEAT_CONSTANTS.C1_t; - - const a_buff = this.cache.get_buffer((n * n) << 3); - const w_buff = this.cache.get_buffer(n << 3); - const a_mt = new matrix_t(n, n, dt, a_buff.data); - const w_mt = new matrix_t(1, n, dt, w_buff.data); - - while (--i >= 0) { - a_mt.data[i] = A.data[i]; - } - - this.JacobiImpl(a_mt.data, n, w_mt.data, vects ? vects.data : null, n, n); - - if (vals) { - while (--n >= 0) { - vals.data[n] = w_mt.data[n]; - } - } - - this.cache.put_buffer(a_buff); - this.cache.put_buffer(w_buff); - } -}; +jsfeatNext.linalg = linalg; jsfeatNext.orb = class orb extends jsfeatNext { public bit_pattern_31_: Int32Array; diff --git a/src/linalg/linalg.ts b/src/linalg/linalg.ts index de4bc14..f5cc296 100644 --- a/src/linalg/linalg.ts +++ b/src/linalg/linalg.ts @@ -1,5 +1,23 @@ +import jsfeatNext from "../core/core"; import { matrix_t } from "../matrix_t/matrix_t"; -export class linalg { +import { JSFEAT_CONSTANTS } from "../constants/constants"; +import { swap, hypot } from "./linalg_base"; +import matmath from "../matmath/matmath"; + +/** + * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). + * This file previously held a type-only stub whose methods threw + * "Method not implemented." — the implementation below is the inline code + * from the monolith, verbatim. + */ +export class linalg extends jsfeatNext { + public matmath: matmath; + + constructor() { + super(); + this.matmath = new matmath(); + } + JacobiImpl( A: Int32Array | Float32Array | Float64Array, astep: number, @@ -8,8 +26,167 @@ export class linalg { vstep: number, n: number ): void { - throw new Error("Method not implemented."); + const eps = JSFEAT_CONSTANTS.EPSILON; + let i = 0, + j = 0, + k = 0, + m = 0, + l = 0, + idx = 0, + _in = 0, + _in2 = 0; + let iters = 0, + max_iter = n * n * 30; + let mv = 0.0, + val = 0.0, + p = 0.0, + y = 0.0, + t = 0.0, + s = 0.0, + c = 0.0, + a0 = 0.0, + b0 = 0.0; + + const indR_buff = this.cache.get_buffer(n << 2); + const indC_buff = this.cache.get_buffer(n << 2); + const indR = indR_buff.i32; + const indC = indC_buff.i32; + + if (V) { + for (; i < n; i++) { + k = i * vstep; + for (j = 0; j < n; j++) { + V[k + j] = 0.0; + } + V[k + i] = 1.0; + } + } + + for (k = 0; k < n; k++) { + W[k] = A[(astep + 1) * k]; + if (k < n - 1) { + for (m = k + 1, mv = Math.abs(A[astep * k + m]), i = k + 2; i < n; i++) { + val = Math.abs(A[astep * k + i]); + if (mv < val) (mv = val), (m = i); + } + indR[k] = m; + } + if (k > 0) { + for (m = 0, mv = Math.abs(A[k]), i = 1; i < k; i++) { + val = Math.abs(A[astep * i + k]); + if (mv < val) (mv = val), (m = i); + } + indC[k] = m; + } + } + + if (n > 1) + for (; iters < max_iter; iters++) { + // find index (k,l) of pivot p + for (k = 0, mv = Math.abs(A[indR[0]]), i = 1; i < n - 1; i++) { + val = Math.abs(A[astep * i + indR[i]]); + if (mv < val) (mv = val), (k = i); + } + l = indR[k]; + for (i = 1; i < n; i++) { + val = Math.abs(A[astep * indC[i] + i]); + if (mv < val) (mv = val), (k = indC[i]), (l = i); + } + + p = A[astep * k + l]; + + if (Math.abs(p) <= eps) break; + + y = (W[l] - W[k]) * 0.5; + t = Math.abs(y) + hypot(p, y); + s = hypot(p, t); + c = t / s; + s = p / s; + t = (p / t) * p; + if (y < 0) (s = -s), (t = -t); + A[astep * k + l] = 0; + + W[k] -= t; + W[l] += t; + + // rotate rows and columns k and l + for (i = 0; i < k; i++) { + _in = astep * i + k; + _in2 = astep * i + l; + a0 = A[_in]; + b0 = A[_in2]; + A[_in] = a0 * c - b0 * s; + A[_in2] = a0 * s + b0 * c; + } + for (i = k + 1; i < l; i++) { + _in = astep * k + i; + _in2 = astep * i + l; + a0 = A[_in]; + b0 = A[_in2]; + A[_in] = a0 * c - b0 * s; + A[_in2] = a0 * s + b0 * c; + } + i = l + 1; + _in = astep * k + i; + _in2 = astep * l + i; + for (; i < n; i++, _in++, _in2++) { + a0 = A[_in]; + b0 = A[_in2]; + A[_in] = a0 * c - b0 * s; + A[_in2] = a0 * s + b0 * c; + } + + // rotate eigenvectors + if (V) { + _in = vstep * k; + _in2 = vstep * l; + for (i = 0; i < n; i++, _in++, _in2++) { + a0 = V[_in]; + b0 = V[_in2]; + V[_in] = a0 * c - b0 * s; + V[_in2] = a0 * s + b0 * c; + } + } + + for (j = 0; j < 2; j++) { + idx = j == 0 ? k : l; + if (idx < n - 1) { + for (m = idx + 1, mv = Math.abs(A[astep * idx + m]), i = idx + 2; i < n; i++) { + val = Math.abs(A[astep * idx + i]); + if (mv < val) (mv = val), (m = i); + } + indR[idx] = m; + } + if (idx > 0) { + for (m = 0, mv = Math.abs(A[idx]), i = 1; i < idx; i++) { + val = Math.abs(A[astep * i + idx]); + if (mv < val) (mv = val), (m = i); + } + indC[idx] = m; + } + } + } + + // sort eigenvalues & eigenvectors + for (k = 0; k < n - 1; k++) { + m = k; + for (i = k + 1; i < n; i++) { + if (W[m] < W[i]) m = i; + } + if (k != m) { + swap(W, m, k, mv); + if (V) { + for (i = 0; i < n; i++) { + swap(V, vstep * m + i, vstep * k + i, mv); + } + } + } + } + + this.cache.put_buffer(indR_buff); + this.cache.put_buffer(indC_buff); } + JacobiSVDImpl( At: Int32Array | Float32Array | Float64Array, astep: number, @@ -20,24 +197,560 @@ export class linalg { n: number, n1: number ): void { - throw new Error("Method not implemented."); + const eps = JSFEAT_CONSTANTS.EPSILON * 2.0; + const minval = JSFEAT_CONSTANTS.FLT_MIN; + let i = 0, + j = 0, + k = 0, + iter = 0, + max_iter = Math.max(m, 30); + let Ai = 0, + Aj = 0, + Vi = 0, + Vj = 0, + changed = 0; + let c = 0.0, + s = 0.0, + t = 0.0; + let t0 = 0.0, + t1 = 0.0, + sd = 0.0, + beta = 0.0, + gamma = 0.0, + delta = 0.0, + a = 0.0, + p = 0.0, + b = 0.0; + let seed = 0x1234; + let val = 0.0, + val0 = 0.0, + asum = 0.0; + + const W_buff = this.cache.get_buffer(n << 3); + const W = W_buff.f64; + + for (; i < n; i++) { + for (k = 0, sd = 0; k < m; k++) { + t = At[i * astep + k]; + sd += t * t; + } + W[i] = sd; + + if (Vt) { + for (k = 0; k < n; k++) { + Vt[i * vstep + k] = 0; + } + Vt[i * vstep + i] = 1; + } + } + + for (; iter < max_iter; iter++) { + changed = 0; + + for (i = 0; i < n - 1; i++) { + for (j = i + 1; j < n; j++) { + (Ai = (i * astep) | 0), (Aj = (j * astep) | 0); + (a = W[i]), (p = 0), (b = W[j]); + + k = 2; + p += At[Ai] * At[Aj]; + p += At[Ai + 1] * At[Aj + 1]; + + for (; k < m; k++) p += At[Ai + k] * At[Aj + k]; + + if (Math.abs(p) <= eps * Math.sqrt(a * b)) continue; + + p *= 2.0; + (beta = a - b), (gamma = hypot(p, beta)); + if (beta < 0) { + delta = (gamma - beta) * 0.5; + s = Math.sqrt(delta / gamma); + c = p / (gamma * s * 2.0); + } else { + c = Math.sqrt((gamma + beta) / (gamma * 2.0)); + s = p / (gamma * c * 2.0); + } + + (a = 0.0), (b = 0.0); + + k = 2; // unroll + t0 = c * At[Ai] + s * At[Aj]; + t1 = -s * At[Ai] + c * At[Aj]; + At[Ai] = t0; + At[Aj] = t1; + a += t0 * t0; + b += t1 * t1; + + t0 = c * At[Ai + 1] + s * At[Aj + 1]; + t1 = -s * At[Ai + 1] + c * At[Aj + 1]; + At[Ai + 1] = t0; + At[Aj + 1] = t1; + a += t0 * t0; + b += t1 * t1; + + for (; k < m; k++) { + t0 = c * At[Ai + k] + s * At[Aj + k]; + t1 = -s * At[Ai + k] + c * At[Aj + k]; + At[Ai + k] = t0; + At[Aj + k] = t1; + + a += t0 * t0; + b += t1 * t1; + } + + W[i] = a; + W[j] = b; + + changed = 1; + + if (Vt) { + (Vi = (i * vstep) | 0), (Vj = (j * vstep) | 0); + + k = 2; + t0 = c * Vt[Vi] + s * Vt[Vj]; + t1 = -s * Vt[Vi] + c * Vt[Vj]; + Vt[Vi] = t0; + Vt[Vj] = t1; + + t0 = c * Vt[Vi + 1] + s * Vt[Vj + 1]; + t1 = -s * Vt[Vi + 1] + c * Vt[Vj + 1]; + Vt[Vi + 1] = t0; + Vt[Vj + 1] = t1; + + for (; k < n; k++) { + t0 = c * Vt[Vi + k] + s * Vt[Vj + k]; + t1 = -s * Vt[Vi + k] + c * Vt[Vj + k]; + Vt[Vi + k] = t0; + Vt[Vj + k] = t1; + } + } + } + } + if (changed == 0) break; + } + + for (i = 0; i < n; i++) { + for (k = 0, sd = 0; k < m; k++) { + t = At[i * astep + k]; + sd += t * t; + } + W[i] = Math.sqrt(sd); + } + + for (i = 0; i < n - 1; i++) { + j = i; + for (k = i + 1; k < n; k++) { + if (W[j] < W[k]) j = k; + } + if (i != j) { + swap(W, i, j, sd); + if (Vt) { + for (k = 0; k < m; k++) { + swap(At, i * astep + k, j * astep + k, t); + } + + for (k = 0; k < n; k++) { + swap(Vt, i * vstep + k, j * vstep + k, t); + } + } + } + } + + for (i = 0; i < n; i++) { + _W[i] = W[i]; + } + + if (!Vt) { + this.cache.put_buffer(W_buff); + return; + } + + for (i = 0; i < n1; i++) { + sd = i < n ? W[i] : 0; + + while (sd <= minval) { + // if we got a zero singular value, then in order to get the corresponding left singular vector + // we generate a random vector, project it to the previously computed left singular vectors, + // subtract the projection and normalize the difference. + val0 = 1.0 / m; + for (k = 0; k < m; k++) { + seed = seed * 214013 + 2531011; + val = ((seed >> 16) & 0x7fff & 256) != 0 ? val0 : -val0; + At[i * astep + k] = val; + } + for (iter = 0; iter < 2; iter++) { + for (j = 0; j < i; j++) { + sd = 0; + for (k = 0; k < m; k++) { + sd += At[i * astep + k] * At[j * astep + k]; + } + asum = 0.0; + for (k = 0; k < m; k++) { + t = At[i * astep + k] - sd * At[j * astep + k]; + At[i * astep + k] = t; + asum += Math.abs(t); + } + asum = asum ? 1.0 / asum : 0; + for (k = 0; k < m; k++) { + At[i * astep + k] *= asum; + } + } + } + sd = 0; + for (k = 0; k < m; k++) { + t = At[i * astep + k]; + sd += t * t; + } + sd = Math.sqrt(sd); + } + + s = 1.0 / sd; + for (k = 0; k < m; k++) { + At[i * astep + k] *= s; + } + } + + this.cache.put_buffer(W_buff); } + lu_solve(A: matrix_t, B: matrix_t): number { - throw new Error("Method not implemented."); + let i = 0, + j = 0, + k = 0, + p = 1, + astep = A.cols; + const ad = A.data, + bd = B.data; + let t, alpha, d, s; + + for (i = 0; i < astep; i++) { + k = i; + for (j = i + 1; j < astep; j++) { + if (Math.abs(ad[j * astep + i]) > Math.abs(ad[k * astep + i])) { + k = j; + } + } + + if (Math.abs(ad[k * astep + i]) < JSFEAT_CONSTANTS.EPSILON) { + return 0; // FAILED + } + + if (k != i) { + for (j = i; j < astep; j++) { + swap(ad, i * astep + j, k * astep + j, t); + } + + swap(bd, i, k, t); + p = -p; + } + + d = -1.0 / ad[i * astep + i]; + + for (j = i + 1; j < astep; j++) { + alpha = ad[j * astep + i] * d; + + for (k = i + 1; k < astep; k++) { + ad[j * astep + k] += alpha * ad[i * astep + k]; + } + + bd[j] += alpha * bd[i]; + } + + ad[i * astep + i] = -d; + } + + for (i = astep - 1; i >= 0; i--) { + s = bd[i]; + for (k = i + 1; k < astep; k++) { + s -= ad[i * astep + k] * bd[k]; + } + bd[i] = s * ad[i * astep + i]; + } + + return 1; // OK } + cholesky_solve(A: matrix_t, B: matrix_t): number { - throw new Error("Method not implemented."); + let col = 0, + row = 0, + col2 = 0, + cs = 0, + rs = 0, + i = 0, + j = 0; + const size = A.cols; + const ad = A.data, + bd = B.data; + let val, inv_diag; + + for (col = 0; col < size; col++) { + inv_diag = 1.0; + cs = col * size; + rs = cs; + for (row = col; row < size; row++) { + // correct for the parts of cholesky already computed + val = ad[rs + col]; + for (col2 = 0; col2 < col; col2++) { + val -= ad[col2 * size + col] * ad[rs + col2]; + } + if (row == col) { + // this is the diagonal element so don't divide + ad[rs + col] = val; + if (val == 0) { + return 0; + } + inv_diag = 1.0 / val; + } else { + // cache the value without division in the upper half + ad[cs + row] = val; + // divide my the diagonal element for all others + ad[rs + col] = val * inv_diag; + } + rs = rs + size; + } + } + + // first backsub through L + cs = 0; + for (i = 0; i < size; i++) { + val = bd[i]; + for (j = 0; j < i; j++) { + val -= ad[cs + j] * bd[j]; + } + bd[i] = val; + cs = cs + size; + } + // backsub through diagonal + cs = 0; + for (i = 0; i < size; i++) { + bd[i] /= ad[cs + i]; + cs = cs + size; + } + // backsub through L Transpose + i = size - 1; + for (; i >= 0; i--) { + val = bd[i]; + j = i + 1; + cs = j * size; + for (; j < size; j++) { + val -= ad[cs + i] * bd[j]; + cs = cs + size; + } + bd[i] = val; + } + + return 1; } + svd_decompose(A: any, W: matrix_t, U: matrix_t, V: matrix_t, options: number): void { - throw new Error("Method not implemented."); + if (typeof options === "undefined") { + options = 0; + } + let at = 0, + i = 0, + j = 0, + _m = A.rows, + _n = A.cols, + m = _m, + n = _n; + const dt = A.type | JSFEAT_CONSTANTS.C1_t; // we only work with single channel + + if (m < n) { + at = 1; + i = m; + m = n; + n = i; + } + + const a_buff = this.cache.get_buffer((m * m) << 3); + const w_buff = this.cache.get_buffer(n << 3); + const v_buff = this.cache.get_buffer((n * n) << 3); + + const a_mt = new matrix_t(m, m, dt, a_buff.data); + const w_mt = new matrix_t(1, n, dt, w_buff.data); + const v_mt = new matrix_t(n, n, dt, v_buff.data); + + if (at == 0) { + // transpose + this.matmath.transpose(a_mt, A); + } else { + for (i = 0; i < _n * _m; i++) { + a_mt.data[i] = A.data[i]; + } + for (; i < n * m; i++) { + a_mt.data[i] = 0; + } + } + + this.JacobiSVDImpl(a_mt.data, m, w_mt.data, v_mt.data, n, m, n, m); + + if (W) { + for (i = 0; i < n; i++) { + W.data[i] = w_mt.data[i]; + } + for (; i < _n; i++) { + W.data[i] = 0; + } + } + + if (at == 0) { + if (U && options & JSFEAT_CONSTANTS.SVD_U_T) { + i = m * m; + while (--i >= 0) { + U.data[i] = a_mt.data[i]; + } + } else if (U) { + this.matmath.transpose(U, a_mt); + } + + if (V && options & JSFEAT_CONSTANTS.SVD_V_T) { + i = n * n; + while (--i >= 0) { + V.data[i] = v_mt.data[i]; + } + } else if (V) { + this.matmath.transpose(V, v_mt); + } + } else { + if (U && options & JSFEAT_CONSTANTS.SVD_U_T) { + i = n * n; + while (--i >= 0) { + U.data[i] = v_mt.data[i]; + } + } else if (U) { + this.matmath.transpose(U, v_mt); + } + + if (V && options & JSFEAT_CONSTANTS.SVD_V_T) { + i = m * m; + while (--i >= 0) { + V.data[i] = a_mt.data[i]; + } + } else if (V) { + this.matmath.transpose(V, a_mt); + } + } + + this.cache.put_buffer(a_buff); + this.cache.put_buffer(w_buff); + this.cache.put_buffer(v_buff); } + svd_solve(A: matrix_t, X: matrix_t, B: matrix_t): void { - throw new Error("Method not implemented."); + let i = 0, + j = 0, + k = 0; + let pu = 0, + pv = 0; + const nrows = A.rows, + ncols = A.cols; + let sum = 0.0, + xsum = 0.0, + tol = 0.0; + const dt = A.type | JSFEAT_CONSTANTS.C1_t; + + const u_buff = this.cache.get_buffer((nrows * nrows) << 3); + const w_buff = this.cache.get_buffer(ncols << 3); + const v_buff = this.cache.get_buffer((ncols * ncols) << 3); + + const u_mt = new matrix_t(nrows, nrows, dt, u_buff.data); + const w_mt = new matrix_t(1, ncols, dt, w_buff.data); + const v_mt = new matrix_t(ncols, ncols, dt, v_buff.data); + + const bd = B.data, + ud = u_mt.data, + wd = w_mt.data, + vd = v_mt.data; + + this.svd_decompose(A, w_mt, u_mt, v_mt, 0); + + tol = JSFEAT_CONSTANTS.EPSILON * wd[0] * ncols; + + for (; i < ncols; i++, pv += ncols) { + xsum = 0.0; + for (j = 0; j < ncols; j++) { + if (wd[j] > tol) { + for (k = 0, sum = 0.0, pu = 0; k < nrows; k++, pu += ncols) { + sum += ud[pu + j] * bd[k]; + } + xsum += (sum * vd[pv + j]) / wd[j]; + } + } + X.data[i] = xsum; + } + + this.cache.put_buffer(u_buff); + this.cache.put_buffer(w_buff); + this.cache.put_buffer(v_buff); } + svd_invert(Ai: matrix_t, A: matrix_t): void { - throw new Error("Method not implemented."); + let i = 0, + j = 0, + k = 0; + let pu = 0, + pv = 0, + pa = 0; + const nrows = A.rows, + ncols = A.cols; + let sum = 0.0, + tol = 0.0; + const dt = A.type | JSFEAT_CONSTANTS.C1_t; + + //const u_buff = cache1.get_buffer((nrows * nrows) << 3); + const u_buff = this.cache.get_buffer((nrows * nrows) << 3); + const w_buff = this.cache.get_buffer(ncols << 3); + const v_buff = this.cache.get_buffer((ncols * ncols) << 3); + const u_mt = new matrix_t(nrows, nrows, dt, u_buff.data); + const w_mt = new matrix_t(1, ncols, dt, w_buff.data); + const v_mt = new matrix_t(ncols, ncols, dt, v_buff.data); + + const id = Ai.data, + ud = u_mt.data, + wd = w_mt.data, + vd = v_mt.data; + + this.svd_decompose(A, w_mt, u_mt, v_mt, 0); + + tol = JSFEAT_CONSTANTS.EPSILON * wd[0] * ncols; + + for (; i < ncols; i++, pv += ncols) { + for (j = 0, pu = 0; j < nrows; j++, pa++) { + for (k = 0, sum = 0.0; k < ncols; k++, pu++) { + if (wd[k] > tol) sum += (vd[pv + k] * ud[pu]) / wd[k]; + } + id[pa] = sum; + } + } + + this.cache.put_buffer(u_buff); + this.cache.put_buffer(w_buff); + this.cache.put_buffer(v_buff); } + eigenVV(A: matrix_t, vects: matrix_t, vals?: matrix_t): void { - throw new Error("Method not implemented."); + let n = A.cols, + i = n * n; + const dt = A.type | JSFEAT_CONSTANTS.C1_t; + + const a_buff = this.cache.get_buffer((n * n) << 3); + const w_buff = this.cache.get_buffer(n << 3); + const a_mt = new matrix_t(n, n, dt, a_buff.data); + const w_mt = new matrix_t(1, n, dt, w_buff.data); + + while (--i >= 0) { + a_mt.data[i] = A.data[i]; + } + + this.JacobiImpl(a_mt.data, n, w_mt.data, vects ? vects.data : null, n, n); + + if (vals) { + while (--n >= 0) { + vals.data[n] = w_mt.data[n]; + } + } + + this.cache.put_buffer(a_buff); + this.cache.put_buffer(w_buff); } } From ce3078febaf281c130bfbe591b53c03fde7fbc7c Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 17:24:52 +0200 Subject: [PATCH 05/10] refactor(orb): de-duplicate orb module (#47) Sixth de-duplication step of #47, following the established pattern. - src/orb/orb.ts: replace the type-only stub with the REAL implementation moved verbatim from the monolith (describe). Imports bit_pattern_31 and rectify_patch from their existing real modules; the constructor now instantiates imgproc via direct module import instead of the jsfeatNext.imgproc static slot. - src/jsfeatNext.ts: shrinks by ~107 lines. Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (the orb parity test pins describe() output byte-for-byte vs original jsfeat); UMD bundle smoke-checked (instanceof, descriptor computation). Co-Authored-By: Claude Fable 5 --- src/jsfeatNext.ts | 108 +---------------------------------------- src/orb/orb.ts | 119 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 109 deletions(-) diff --git a/src/jsfeatNext.ts b/src/jsfeatNext.ts index 9763759..c213560 100644 --- a/src/jsfeatNext.ts +++ b/src/jsfeatNext.ts @@ -525,113 +525,7 @@ jsfeatNext.matmath = matmath; jsfeatNext.linalg = linalg; -jsfeatNext.orb = class orb extends jsfeatNext { - public bit_pattern_31_: Int32Array; - public H: matrix_t; - public patch_img: matrix_t; - public imgproc: imgproc; - - constructor() { - super(); - this.bit_pattern_31_ = new Int32Array(bit_pattern_31); - this.H = new matrix_t(3, 3, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); - this.patch_img = new matrix_t(32, 32, JSFEAT_CONSTANTS.U8_t | JSFEAT_CONSTANTS.C1_t); - this.imgproc = new jsfeatNext.imgproc(); - } - - describe(src: matrix_t, corners: keypoint_t[], count: number, descriptors: matrix_t): void { - const DESCR_SIZE = 32; // bytes; - let i = 0, - b = 0, - px = 0.0, - py = 0.0, - angle = 0.0; - let t0 = 0, - t1 = 0, - val = 0; - //let img = src.data, w = src.cols, h = src.rows; - const patch_d = this.patch_img.data; - const patch_off = 16 * 32 + 16; // center of patch - let patt = 0; - - if (!(descriptors.type & JSFEAT_CONSTANTS.U8_t)) { - // relocate to U8 type - descriptors.type = JSFEAT_CONSTANTS.U8_t; - descriptors.cols = DESCR_SIZE; - descriptors.rows = count; - descriptors.channel = 1; - descriptors.allocate(); - } else { - descriptors.resize(DESCR_SIZE, count, 1); - } - - const descr_d = descriptors.data; - let descr_off = 0; - - for (i = 0; i < count; ++i) { - px = corners[i].x; - py = corners[i].y; - angle = corners[i].angle; - - rectify_patch(src, this.patch_img, angle, px, py, 32, this.H, this.imgproc); - - // describe the patch - patt = 0; - for (b = 0; b < DESCR_SIZE; ++b) { - t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - val = (((t0 < t1))) | 0; - - t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - val |= (((t0 < t1))) << 1; - - t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - val |= (((t0 < t1))) << 2; - - t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - val |= (((t0 < t1))) << 3; - - t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - val |= (((t0 < t1))) << 4; - - t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - val |= (((t0 < t1))) << 5; - - t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - val |= (((t0 < t1))) << 6; - - t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; - patt += 2; - val |= (((t0 < t1))) << 7; - - descr_d[descr_off + b] = val; - } - descr_off += DESCR_SIZE; - } - } -}; +jsfeatNext.orb = orb; jsfeatNext.yape = yape; diff --git a/src/orb/orb.ts b/src/orb/orb.ts index 6798653..5c7e0ff 100644 --- a/src/orb/orb.ts +++ b/src/orb/orb.ts @@ -1,7 +1,122 @@ +import jsfeatNext from "../core/core"; import { matrix_t } from "../matrix_t/matrix_t"; import { keypoint_t } from "../keypoint_t/keypoint_t"; -export class orb { +import { JSFEAT_CONSTANTS } from "../constants/constants"; +import { imgproc } from "../imgproc/imgproc"; +import { bit_pattern_31 } from "./bit_pattern_31"; +import { rectify_patch } from "./rectify_patch"; + +/** + * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). + * This file previously held a type-only stub — the implementation below is the + * inline code from the monolith, verbatim (the only change: the constructor + * instantiates the imgproc module directly instead of via the + * jsfeatNext.imgproc static slot). + */ +export class orb extends jsfeatNext { + public bit_pattern_31_: Int32Array; + public H: matrix_t; + public patch_img: matrix_t; + public imgproc: imgproc; + + constructor() { + super(); + this.bit_pattern_31_ = new Int32Array(bit_pattern_31); + this.H = new matrix_t(3, 3, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); + this.patch_img = new matrix_t(32, 32, JSFEAT_CONSTANTS.U8_t | JSFEAT_CONSTANTS.C1_t); + this.imgproc = new imgproc(); + } + describe(src: matrix_t, corners: keypoint_t[], count: number, descriptors: matrix_t): void { - throw new Error("Method not implemented."); + const DESCR_SIZE = 32; // bytes; + let i = 0, + b = 0, + px = 0.0, + py = 0.0, + angle = 0.0; + let t0 = 0, + t1 = 0, + val = 0; + //let img = src.data, w = src.cols, h = src.rows; + const patch_d = this.patch_img.data; + const patch_off = 16 * 32 + 16; // center of patch + let patt = 0; + + if (!(descriptors.type & JSFEAT_CONSTANTS.U8_t)) { + // relocate to U8 type + descriptors.type = JSFEAT_CONSTANTS.U8_t; + descriptors.cols = DESCR_SIZE; + descriptors.rows = count; + descriptors.channel = 1; + descriptors.allocate(); + } else { + descriptors.resize(DESCR_SIZE, count, 1); + } + + const descr_d = descriptors.data; + let descr_off = 0; + + for (i = 0; i < count; ++i) { + px = corners[i].x; + py = corners[i].y; + angle = corners[i].angle; + + rectify_patch(src, this.patch_img, angle, px, py, 32, this.H, this.imgproc); + + // describe the patch + patt = 0; + for (b = 0; b < DESCR_SIZE; ++b) { + t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + val = (((t0 < t1))) | 0; + + t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + val |= (((t0 < t1))) << 1; + + t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + val |= (((t0 < t1))) << 2; + + t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + val |= (((t0 < t1))) << 3; + + t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + val |= (((t0 < t1))) << 4; + + t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + val |= (((t0 < t1))) << 5; + + t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + val |= (((t0 < t1))) << 6; + + t0 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + t1 = patch_d[patch_off + this.bit_pattern_31_[patt + 1] * 32 + this.bit_pattern_31_[patt]]; + patt += 2; + val |= (((t0 < t1))) << 7; + + descr_d[descr_off + b] = val; + } + descr_off += DESCR_SIZE; + } } } From 34ba982db4f611dbc2331d9331d2cf6ad218a3db Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 18:37:37 +0200 Subject: [PATCH 06/10] refactor(yape06): de-duplicate yape06 module (#47) Seventh de-duplication step of #47, following the established pattern. - src/yape06/yape06.ts: replace the type-only stub with the REAL implementation moved verbatim from the monolith (detect + laplacian / min-eigen-value thresholds). Imports compute_laplacian and hessian_min_eigen_value from ./yape06_utils (already a real module). - src/jsfeatNext.ts: shrinks by ~85 lines. Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (the yape06 parity test pins detect() against original jsfeat); UMD bundle smoke-checked (instanceof, default thresholds, detection on a synthetic image). Co-Authored-By: Claude Fable 5 --- src/jsfeatNext.ts | 86 +---------------------------------------- src/yape06/yape06.ts | 92 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 87 deletions(-) diff --git a/src/jsfeatNext.ts b/src/jsfeatNext.ts index c213560..c744601 100644 --- a/src/jsfeatNext.ts +++ b/src/jsfeatNext.ts @@ -529,91 +529,7 @@ jsfeatNext.orb = orb; jsfeatNext.yape = yape; -jsfeatNext.yape06 = class yape06 extends jsfeatNext { - public laplacian_threshold: number; - public min_eigen_value_threshold: number; - - constructor() { - super(); - this.laplacian_threshold = 30; - this.min_eigen_value_threshold = 25; - } - - detect(src: matrix_t, points: keypoint_t[], border: number): number { - if (typeof border === "undefined") { - border = 5; - } - let x = 0, - y = 0; - const w = src.cols, - h = src.rows, - srd_d = src.data; - const Dxx = 5, - Dyy = (5 * w) | 0; - const Dxy = (3 + 3 * w) | 0, - Dyx = (3 - 3 * w) | 0; - const lap_buf = this.cache.get_buffer((w * h) << 2); - const laplacian = lap_buf.i32; - let lv = 0, - row = 0, - rowx = 0, - min_eigen_value = 0, - pt; - let number_of_points = 0; - const lap_thresh = this.laplacian_threshold; - const eigen_thresh = this.min_eigen_value_threshold; - - const sx = Math.max(5, border) | 0; - const sy = Math.max(3, border) | 0; - const ex = Math.min(w - 5, w - border) | 0; - const ey = Math.min(h - 3, h - border) | 0; - - x = w * h; - while (--x >= 0) { - laplacian[x] = 0; - } - compute_laplacian(srd_d, laplacian, w, Dxx, Dyy, sx, sy, ex, ey); - - row = (sy * w + sx) | 0; - for (y = sy; y < ey; ++y, row += w) { - for (x = sx, rowx = row; x < ex; ++x, ++rowx) { - lv = laplacian[rowx]; - if ( - (lv < -lap_thresh && - lv < laplacian[rowx - 1] && - lv < laplacian[rowx + 1] && - lv < laplacian[rowx - w] && - lv < laplacian[rowx + w] && - lv < laplacian[rowx - w - 1] && - lv < laplacian[rowx + w - 1] && - lv < laplacian[rowx - w + 1] && - lv < laplacian[rowx + w + 1]) || - (lv > lap_thresh && - lv > laplacian[rowx - 1] && - lv > laplacian[rowx + 1] && - lv > laplacian[rowx - w] && - lv > laplacian[rowx + w] && - lv > laplacian[rowx - w - 1] && - lv > laplacian[rowx + w - 1] && - lv > laplacian[rowx - w + 1] && - lv > laplacian[rowx + w + 1]) - ) { - min_eigen_value = hessian_min_eigen_value(srd_d, rowx, lv, Dxx, Dyy, Dxy, Dyx); - if (min_eigen_value > eigen_thresh) { - pt = points[number_of_points]; - (pt.x = x), (pt.y = y), (pt.score = min_eigen_value); - ++number_of_points; - ++x, ++rowx; // skip next pixel since this is maxima in 3x3 - } - } - } - } - - this.cache.put_buffer(lap_buf); - - return number_of_points; - } -}; +jsfeatNext.yape06 = yape06; jsfeatNext.motion_estimator = class motion_estimator extends jsfeatNext { constructor() { diff --git a/src/yape06/yape06.ts b/src/yape06/yape06.ts index 2815ccc..b2cd21a 100644 --- a/src/yape06/yape06.ts +++ b/src/yape06/yape06.ts @@ -1,7 +1,95 @@ +import jsfeatNext from "../core/core"; import { matrix_t } from "../matrix_t/matrix_t"; import { keypoint_t } from "../keypoint_t/keypoint_t"; -export class yape06 { +import { compute_laplacian, hessian_min_eigen_value } from "./yape06_utils"; + +/** + * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). + * This file previously held a type-only stub — the implementation below is the + * inline code from the monolith, verbatim. + */ +export class yape06 extends jsfeatNext { + public laplacian_threshold: number; + public min_eigen_value_threshold: number; + + constructor() { + super(); + this.laplacian_threshold = 30; + this.min_eigen_value_threshold = 25; + } + detect(src: matrix_t, points: keypoint_t[], border: number): number { - throw new Error("Method not implemented."); + if (typeof border === "undefined") { + border = 5; + } + let x = 0, + y = 0; + const w = src.cols, + h = src.rows, + srd_d = src.data; + const Dxx = 5, + Dyy = (5 * w) | 0; + const Dxy = (3 + 3 * w) | 0, + Dyx = (3 - 3 * w) | 0; + const lap_buf = this.cache.get_buffer((w * h) << 2); + const laplacian = lap_buf.i32; + let lv = 0, + row = 0, + rowx = 0, + min_eigen_value = 0, + pt; + let number_of_points = 0; + const lap_thresh = this.laplacian_threshold; + const eigen_thresh = this.min_eigen_value_threshold; + + const sx = Math.max(5, border) | 0; + const sy = Math.max(3, border) | 0; + const ex = Math.min(w - 5, w - border) | 0; + const ey = Math.min(h - 3, h - border) | 0; + + x = w * h; + while (--x >= 0) { + laplacian[x] = 0; + } + compute_laplacian(srd_d, laplacian, w, Dxx, Dyy, sx, sy, ex, ey); + + row = (sy * w + sx) | 0; + for (y = sy; y < ey; ++y, row += w) { + for (x = sx, rowx = row; x < ex; ++x, ++rowx) { + lv = laplacian[rowx]; + if ( + (lv < -lap_thresh && + lv < laplacian[rowx - 1] && + lv < laplacian[rowx + 1] && + lv < laplacian[rowx - w] && + lv < laplacian[rowx + w] && + lv < laplacian[rowx - w - 1] && + lv < laplacian[rowx + w - 1] && + lv < laplacian[rowx - w + 1] && + lv < laplacian[rowx + w + 1]) || + (lv > lap_thresh && + lv > laplacian[rowx - 1] && + lv > laplacian[rowx + 1] && + lv > laplacian[rowx - w] && + lv > laplacian[rowx + w] && + lv > laplacian[rowx - w - 1] && + lv > laplacian[rowx + w - 1] && + lv > laplacian[rowx - w + 1] && + lv > laplacian[rowx + w + 1]) + ) { + min_eigen_value = hessian_min_eigen_value(srd_d, rowx, lv, Dxx, Dyy, Dxy, Dyx); + if (min_eigen_value > eigen_thresh) { + pt = points[number_of_points]; + (pt.x = x), (pt.y = y), (pt.score = min_eigen_value); + ++number_of_points; + ++x, ++rowx; // skip next pixel since this is maxima in 3x3 + } + } + } + } + + this.cache.put_buffer(lap_buf); + + return number_of_points; } } From 2f29664c6b65cfb855072a66b62dd1451b4f8d82 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 20:25:26 +0200 Subject: [PATCH 07/10] refactor(motion_estimator): de-duplicate motion_estimator and extract kernels (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eighth de-duplication step of #47 — the biggest structural win. - NEW src/motion_model/motion_model.ts: motion_model + affine2d + homography2d kernel classes, moved verbatim from the monolith (they were module-local classes there). Kernels instantiate linalg via direct module import instead of the jsfeatNext.linalg static slot. - src/motion_estimator/motion_estimator.ts: replace the type-only stub with the REAL implementation (get_subset, find_inliers, ransac, lmeds); lmeds instantiates math via direct module import. - src/core/core.ts: the temporary any-typed affine2d/homography2d static slots (a known TODO since #62) now have precise typeof types via type-only imports. - src/jsfeatNext.ts: down to ~380 lines — pure aggregator except for optical_flow_lk, the last inline module. Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (3 parity tests pin ransac/lmeds with identical models AND inlier masks vs original jsfeat on seeded data); UMD bundle smoke-checked (instanceof for estimator and both kernels, affine RANSAC recovers the synthetic model). Co-Authored-By: Claude Fable 5 --- src/core/core.ts | 7 +- src/jsfeatNext.ts | 763 +---------------------- src/motion_estimator/motion_estimator.ts | 262 +++++++- src/motion_model/motion_model.ts | 488 +++++++++++++++ 4 files changed, 748 insertions(+), 772 deletions(-) create mode 100644 src/motion_model/motion_model.ts diff --git a/src/core/core.ts b/src/core/core.ts index 920ca0b..54ec167 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -17,6 +17,7 @@ import type { ransac_params_t } from "../motion_estimator/ransac_params_t"; import type { motion_estimator } from "../motion_estimator/motion_estimator"; import type { optical_flow_lk } from "../optical_flow_lk/optical_flow_lk"; import type { orb } from "../orb/orb"; +import type { affine2d, homography2d } from "../motion_model/motion_model"; /** * Base class of the library: holds the shared constants, the per-instance @@ -43,10 +44,8 @@ export default class jsfeatNext { static yape: typeof yape; static yape06: typeof yape06; static ransac_params_t: typeof ransac_params_t; - // affine2d / homography2d are still implemented inline in src/jsfeatNext.ts; - // these slots get precise `typeof` types as #47 extracts them into modules. - static affine2d: any; - static homography2d: any; + static affine2d: typeof affine2d; + static homography2d: typeof homography2d; static motion_estimator: typeof motion_estimator; static optical_flow_lk: typeof optical_flow_lk; static orb: typeof orb; diff --git a/src/jsfeatNext.ts b/src/jsfeatNext.ts index c744601..518dc5a 100644 --- a/src/jsfeatNext.ts +++ b/src/jsfeatNext.ts @@ -22,6 +22,7 @@ import { compute_laplacian, hessian_min_eigen_value } from "./yape06/yape06_util import { yape06 } from "./yape06/yape06"; import { ransac_params_t } from "./motion_estimator/ransac_params_t"; import { motion_estimator } from "./motion_estimator/motion_estimator"; +import { motion_model, affine2d, homography2d } from "./motion_model/motion_model"; import { optical_flow_lk } from "./optical_flow_lk/optical_flow_lk"; import { JSFEAT_CONSTANTS } from "./constants/constants"; @@ -30,481 +31,6 @@ import { JSFEAT_CONSTANTS } from "./constants/constants"; // way to becoming a thin aggregator that only attaches the modules. export default jsfeatNext; -class motion_model extends jsfeatNext { - public T0: matrix_t; - public T1: matrix_t; - public AtA: matrix_t; - public AtB: matrix_t; - - constructor() { - super(); - this.T0 = new matrix_t(3, 3, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); - this.T1 = new matrix_t(3, 3, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); - this.AtA = new matrix_t(6, 6, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); - this.AtB = new matrix_t(6, 1, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); - } - - sqr(x: number): number { - return x * x; - } - - // does isotropic normalization - iso_normalize_points(from: point_t[], to: point_t[], T0: number[], T1: number[], count: number): void { - let i = 0; - let cx0 = 0.0, - cy0 = 0.0, - d0 = 0.0, - s0 = 0.0; - let cx1 = 0.0, - cy1 = 0.0, - d1 = 0.0, - s1 = 0.0; - let dx = 0.0, - dy = 0.0; - - for (; i < count; ++i) { - cx0 += from[i].x; - cy0 += from[i].y; - cx1 += to[i].x; - cy1 += to[i].y; - } - - cx0 /= count; - cy0 /= count; - cx1 /= count; - cy1 /= count; - - for (i = 0; i < count; ++i) { - dx = from[i].x - cx0; - dy = from[i].y - cy0; - d0 += Math.sqrt(dx * dx + dy * dy); - dx = to[i].x - cx1; - dy = to[i].y - cy1; - d1 += Math.sqrt(dx * dx + dy * dy); - } - - d0 /= count; - d1 /= count; - - s0 = Math.SQRT2 / d0; - s1 = Math.SQRT2 / d1; - - T0[0] = T0[4] = s0; - T0[2] = -cx0 * s0; - T0[5] = -cy0 * s0; - T0[1] = T0[3] = T0[6] = T0[7] = 0.0; - T0[8] = 1.0; - - T1[0] = T1[4] = s1; - T1[2] = -cx1 * s1; - T1[5] = -cy1 * s1; - T1[1] = T1[3] = T1[6] = T1[7] = 0.0; - T1[8] = 1.0; - } - - have_collinear_points(points: point_t[], count: number): boolean { - let j = 0, - k = 0, - i = (count - 1) | 0; - let dx1 = 0.0, - dy1 = 0.0, - dx2 = 0.0, - dy2 = 0.0; - - // check that the i-th selected point does not belong - // to a line connecting some previously selected points - for (; j < i; ++j) { - dx1 = points[j].x - points[i].x; - dy1 = points[j].y - points[i].y; - for (k = 0; k < j; ++k) { - dx2 = points[k].x - points[i].x; - dy2 = points[k].y - points[i].y; - if ( - Math.abs(dx2 * dy1 - dy2 * dx1) <= - JSFEAT_CONSTANTS.EPSILON * (Math.abs(dx1) + Math.abs(dy1) + Math.abs(dx2) + Math.abs(dy2)) - ) - return true; - } - } - return false; - } -} - -class affine2d extends motion_model { - constructor() { - super(); - } - - run(from: point_t[], to: point_t[], model: matrix_t, count: number): number { - let i = 0, - j = 0; - const dt = model.type | JSFEAT_CONSTANTS.C1_t; - const md = model.data, - t0d = this.T0.data, - t1d = this.T1.data; - let pt0, - pt1, - px = 0.0, - py = 0.0; - const _matmath = new matmath(); - const _linalg = new jsfeatNext.linalg(); - - this.iso_normalize_points(from, to, t0d, t1d, count); - - const a_buff = this.cache.get_buffer((2 * count * 6) << 3); - const b_buff = this.cache.get_buffer((2 * count) << 3); - - const a_mt = new matrix_t(6, 2 * count, dt, a_buff.data); - const b_mt = new matrix_t(1, 2 * count, dt, b_buff.data); - const ad = a_mt.data, - bd = b_mt.data; - - for (; i < count; ++i) { - pt0 = from[i]; - pt1 = to[i]; - - px = t0d[0] * pt0.x + t0d[1] * pt0.y + t0d[2]; - py = t0d[3] * pt0.x + t0d[4] * pt0.y + t0d[5]; - - j = i * 2 * 6; - (ad[j] = px), (ad[j + 1] = py), (ad[j + 2] = 1.0), (ad[j + 3] = 0.0), (ad[j + 4] = 0.0), (ad[j + 5] = 0.0); - - j += 6; - (ad[j] = 0.0), (ad[j + 1] = 0.0), (ad[j + 2] = 0.0), (ad[j + 3] = px), (ad[j + 4] = py), (ad[j + 5] = 1.0); - - bd[i << 1] = t1d[0] * pt1.x + t1d[1] * pt1.y + t1d[2]; - bd[(i << 1) + 1] = t1d[3] * pt1.x + t1d[4] * pt1.y + t1d[5]; - } - - _matmath.multiply_AtA(this.AtA, a_mt); - _matmath.multiply_AtB(this.AtB, a_mt, b_mt); - - _linalg.lu_solve(this.AtA, this.AtB); - - (md[0] = this.AtB.data[0]), (md[1] = this.AtB.data[1]), (md[2] = this.AtB.data[2]); - (md[3] = this.AtB.data[3]), (md[4] = this.AtB.data[4]), (md[5] = this.AtB.data[5]); - (md[6] = 0.0), (md[7] = 0.0), (md[8] = 1.0); // fill last row - - // denormalize - _matmath.invert_3x3(this.T1, this.T1); - _matmath.multiply_3x3(model, this.T1, model); - _matmath.multiply_3x3(model, model, this.T0); - - // free buffer - this.cache.put_buffer(a_buff); - this.cache.put_buffer(b_buff); - - return 1; - } - - // Per-point reprojection error for the affine model. Ported from original - // jsfeat's affine2d; jsfeatNext was missing it, which made RANSAC/LMEDS - // with an affine2d kernel throw. See issue #51. - error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void { - let i = 0; - let pt0, pt1; - const m = model.data; - - for (; i < count; ++i) { - pt0 = from[i]; - pt1 = to[i]; - - err[i] = - this.sqr(pt1.x - m[0] * pt0.x - m[1] * pt0.y - m[2]) + - this.sqr(pt1.y - m[3] * pt0.x - m[4] * pt0.y - m[5]); - } - } - - check_subset(from: point_t[], to: point_t[], count: number): boolean { - return true; // all good - } -} - -class homography2d extends motion_model { - public mLtL: matrix_t; - public Evec: matrix_t; - - constructor() { - super(); - this.mLtL = new matrix_t(9, 9, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); - this.Evec = new matrix_t(9, 9, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); - } - - run(from: point_t[], to: point_t[], model: matrix_t, count: number): number { - let i = 0, - j = 0; - const md = model.data, - t0d = this.T0.data, - t1d = this.T1.data; - const LtL = this.mLtL.data, - evd = this.Evec.data; - let x = 0.0, - y = 0.0, - X = 0.0, - Y = 0.0; - const _linalg = new jsfeatNext.linalg(); - const _matmath = new matmath(); - - // norm - let smx = 0.0, - smy = 0.0, - cmx = 0.0, - cmy = 0.0, - sMx = 0.0, - sMy = 0.0, - cMx = 0.0, - cMy = 0.0; - - for (; i < count; ++i) { - cmx += to[i].x; - cmy += to[i].y; - cMx += from[i].x; - cMy += from[i].y; - } - - cmx /= count; - cmy /= count; - cMx /= count; - cMy /= count; - - for (i = 0; i < count; ++i) { - smx += Math.abs(to[i].x - cmx); - smy += Math.abs(to[i].y - cmy); - sMx += Math.abs(from[i].x - cMx); - sMy += Math.abs(from[i].y - cMy); - } - - if ( - Math.abs(smx) < JSFEAT_CONSTANTS.EPSILON || - Math.abs(smy) < JSFEAT_CONSTANTS.EPSILON || - Math.abs(sMx) < JSFEAT_CONSTANTS.EPSILON || - Math.abs(sMy) < JSFEAT_CONSTANTS.EPSILON - ) - return 0; - - smx = count / smx; - smy = count / smy; - sMx = count / sMx; - sMy = count / sMy; - - t0d[0] = sMx; - t0d[1] = 0; - t0d[2] = -cMx * sMx; - t0d[3] = 0; - t0d[4] = sMy; - t0d[5] = -cMy * sMy; - t0d[6] = 0; - t0d[7] = 0; - t0d[8] = 1; - - t1d[0] = 1.0 / smx; - t1d[1] = 0; - t1d[2] = cmx; - t1d[3] = 0; - t1d[4] = 1.0 / smy; - t1d[5] = cmy; - t1d[6] = 0; - t1d[7] = 0; - t1d[8] = 1; - // - - // construct system - i = 81; - while (--i >= 0) { - LtL[i] = 0.0; - } - for (i = 0; i < count; ++i) { - x = (to[i].x - cmx) * smx; - y = (to[i].y - cmy) * smy; - X = (from[i].x - cMx) * sMx; - Y = (from[i].y - cMy) * sMy; - - LtL[0] += X * X; - LtL[1] += X * Y; - LtL[2] += X; - - LtL[6] += X * -x * X; - LtL[7] += X * -x * Y; - LtL[8] += X * -x; - LtL[10] += Y * Y; - LtL[11] += Y; - - LtL[15] += Y * -x * X; - LtL[16] += Y * -x * Y; - LtL[17] += Y * -x; - LtL[20] += 1.0; - - LtL[24] += -x * X; - LtL[25] += -x * Y; - LtL[26] += -x; - LtL[30] += X * X; - LtL[31] += X * Y; - LtL[32] += X; - LtL[33] += X * -y * X; - LtL[34] += X * -y * Y; - LtL[35] += X * -y; - LtL[40] += Y * Y; - LtL[41] += Y; - LtL[42] += Y * -y * X; - LtL[43] += Y * -y * Y; - LtL[44] += Y * -y; - LtL[50] += 1.0; - LtL[51] += -y * X; - LtL[52] += -y * Y; - LtL[53] += -y; - LtL[60] += -x * X * -x * X + -y * X * -y * X; - LtL[61] += -x * X * -x * Y + -y * X * -y * Y; - LtL[62] += -x * X * -x + -y * X * -y; - LtL[70] += -x * Y * -x * Y + -y * Y * -y * Y; - LtL[71] += -x * Y * -x + -y * Y * -y; - LtL[80] += -x * -x + -y * -y; - } - // - - // symmetry - for (i = 0; i < 9; ++i) { - for (j = 0; j < i; ++j) LtL[i * 9 + j] = LtL[j * 9 + i]; - } - - _linalg.eigenVV(this.mLtL, this.Evec); - - (md[0] = evd[72]), (md[1] = evd[73]), (md[2] = evd[74]); - (md[3] = evd[75]), (md[4] = evd[76]), (md[5] = evd[77]); - (md[6] = evd[78]), (md[7] = evd[79]), (md[8] = evd[80]); - - // denormalize - _matmath.multiply_3x3(model, this.T1, model); - _matmath.multiply_3x3(model, model, this.T0); - - // set bottom right to 1.0 - x = 1.0 / md[8]; - md[0] *= x; - md[1] *= x; - md[2] *= x; - md[3] *= x; - md[4] *= x; - md[5] *= x; - md[6] *= x; - md[7] *= x; - md[8] = 1.0; - - return 1; - } - - error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void { - let i = 0; - let pt0, - pt1, - ww = 0.0, - dx = 0.0, - dy = 0.0; - const m = model.data; - - for (; i < count; ++i) { - pt0 = from[i]; - pt1 = to[i]; - - ww = 1.0 / (m[6] * pt0.x + m[7] * pt0.y + 1.0); - dx = (m[0] * pt0.x + m[1] * pt0.y + m[2]) * ww - pt1.x; - dy = (m[3] * pt0.x + m[4] * pt0.y + m[5]) * ww - pt1.y; - err[i] = dx * dx + dy * dy; - } - } - - check_subset(from: point_t[], to: point_t[], count: number): boolean { - // seems to reject good subsets actually - //if( have_collinear_points(from, count) || have_collinear_points(to, count) ) { - //return false; - //} - const _matmath = new matmath(); - if (count == 4) { - let negative = 0; - - const fp0 = from[0], - fp1 = from[1], - fp2 = from[2], - fp3 = from[3]; - const tp0 = to[0], - tp1 = to[1], - tp2 = to[2], - tp3 = to[3]; - - // set1 - let A11 = fp0.x, - A12 = fp0.y, - A13 = 1.0; - let A21 = fp1.x, - A22 = fp1.y, - A23 = 1.0; - let A31 = fp2.x, - A32 = fp2.y, - A33 = 1.0; - - let B11 = tp0.x, - B12 = tp0.y, - B13 = 1.0; - let B21 = tp1.x, - B22 = tp1.y, - B23 = 1.0; - let B31 = tp2.x, - B32 = tp2.y, - B33 = 1.0; - - let detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); - let detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); - - if (detA * detB < 0) negative++; - - // set2 - (A11 = fp1.x), (A12 = fp1.y); - (A21 = fp2.x), (A22 = fp2.y); - (A31 = fp3.x), (A32 = fp3.y); - - (B11 = tp1.x), (B12 = tp1.y); - (B21 = tp2.x), (B22 = tp2.y); - (B31 = tp3.x), (B32 = tp3.y); - - detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); - detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); - - if (detA * detB < 0) negative++; - - // set3 - (A11 = fp0.x), (A12 = fp0.y); - (A21 = fp2.x), (A22 = fp2.y); - (A31 = fp3.x), (A32 = fp3.y); - - (B11 = tp0.x), (B12 = tp0.y); - (B21 = tp2.x), (B22 = tp2.y); - (B31 = tp3.x), (B32 = tp3.y); - - detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); - detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); - - if (detA * detB < 0) negative++; - - // set4 - (A11 = fp0.x), (A12 = fp0.y); - (A21 = fp1.x), (A22 = fp1.y); - (A31 = fp3.x), (A32 = fp3.y); - - (B11 = tp0.x), (B12 = tp0.y); - (B21 = tp1.x), (B22 = tp1.y); - (B31 = tp3.x), (B32 = tp3.y); - - detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); - detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); - - if (detA * detB < 0) negative++; - - if (negative != 0 && negative != 4) { - return false; - } - } - return true; // all good - } -} - jsfeatNext.cache = cache; jsfeatNext.pyramid_t = pyramid_t; @@ -531,292 +57,7 @@ jsfeatNext.yape = yape; jsfeatNext.yape06 = yape06; -jsfeatNext.motion_estimator = class motion_estimator extends jsfeatNext { - constructor() { - super(); - } - - get_subset( - kernel: homography2d, - from: point_t[], - to: point_t[], - need_cnt: number, - max_cnt: number, - from_sub: point_t[], - to_sub: point_t[] - ): boolean { - const max_try = 1000; - const indices = []; - let i = 0, - j = 0, - ssiter = 0, - idx_i = 0, - ok = false; - for (; ssiter < max_try; ++ssiter) { - i = 0; - for (; i < need_cnt && ssiter < max_try; ) { - ok = false; - idx_i = 0; - while (!ok) { - ok = true; - idx_i = indices[i] = Math.floor(Math.random() * max_cnt) | 0; - for (j = 0; j < i; ++j) { - if (idx_i == indices[j]) { - ok = false; - break; - } - } - } - from_sub[i] = from[idx_i]; - to_sub[i] = to[idx_i]; - if (!kernel.check_subset(from_sub, to_sub, i + 1)) { - ssiter++; - continue; - } - ++i; - } - break; - } - - return i == need_cnt && ssiter < max_try; - } - - find_inliers( - kernel: homography2d, - model: matrix_t, - from: point_t[], - to: point_t[], - count: number, - thresh: number, - err: Int32Array | Float32Array, - mask: number[] - ): number { - let numinliers: number = 0, - i = 0, - f = 0; - const t = thresh * thresh; - - kernel.error(from, to, model, err, count); - - for (; i < count; ++i) { - f = ((err[i] <= t)); - mask[i] = f; - numinliers += f; - } - return numinliers; - } - - ransac( - params: ransac_params_t, - kernel: any, - from: point_t[], - to: point_t[], - count: number, - model: matrix_t, - mask: matrix_t, - max_iters: number - ): boolean { - if (typeof max_iters === "undefined") { - max_iters = 1000; - } - - if (count < params.size) return false; - - const model_points = params.size; - let niters = max_iters, - iter = 0; - let result: boolean = false; - - const subset0: any = []; - const subset1: any = []; - let found = false; - - const mc = model.cols, - mr = model.rows; - const dt = model.type | JSFEAT_CONSTANTS.C1_t; - - const m_buff = this.cache.get_buffer((mc * mr) << 3); - const ms_buff = this.cache.get_buffer(count); - const err_buff = this.cache.get_buffer(count << 2); - const M = new matrix_t(mc, mr, dt, m_buff.data); - const curr_mask = new matrix_t(count, 1, JSFEAT_CONSTANTS.U8C1_t, ms_buff.data); - - let inliers_max = -1, - numinliers = 0; - let nmodels = 0; - - const err = err_buff.f32; - - // special case - if (count == model_points) { - if (kernel.run(from, to, M, count) <= 0) { - this.cache.put_buffer(m_buff); - this.cache.put_buffer(ms_buff); - this.cache.put_buffer(err_buff); - return false; - } - - M.copy_to(model); - if (mask) { - while (--count >= 0) { - mask.data[count] = 1; - } - } - this.cache.put_buffer(m_buff); - this.cache.put_buffer(ms_buff); - this.cache.put_buffer(err_buff); - return true; - } - - for (; iter < niters; ++iter) { - // generate subset - found = this.get_subset(kernel, from, to, model_points, count, subset0, subset1); - if (!found) { - if (iter == 0) { - this.cache.put_buffer(m_buff); - this.cache.put_buffer(ms_buff); - this.cache.put_buffer(err_buff); - return false; - } - break; - } - - nmodels = kernel.run(subset0, subset1, M, model_points); - if (nmodels <= 0) continue; - - // TODO handle multimodel output - - numinliers = this.find_inliers(kernel, M, from, to, count, params.thresh, err, curr_mask.data); - - if (numinliers > Math.max(inliers_max, model_points - 1)) { - M.copy_to(model); - inliers_max = numinliers; - if (mask) curr_mask.copy_to(mask); - niters = params.update_iters((count - numinliers) / count, niters); - result = true; - } - } - - this.cache.put_buffer(m_buff); - this.cache.put_buffer(ms_buff); - this.cache.put_buffer(err_buff); - - return result; - } - - lmeds( - params: ransac_params_t, - kernel: any, - from: point_t[], - to: point_t[], - count: number, - model: matrix_t, - mask: matrix_t, - max_iters: number - ): boolean { - if (typeof max_iters === "undefined") { - max_iters = 1000; - } - - if (count < params.size) return false; - - const model_points = params.size; - let niters = max_iters, - iter = 0; - let result: boolean = false; - const _math = new jsfeatNext.math(); - - const subset0: any = []; - const subset1: any = []; - let found = false; - - const mc = model.cols, - mr = model.rows; - const dt = model.type | JSFEAT_CONSTANTS.C1_t; - - const m_buff = this.cache.get_buffer((mc * mr) << 3); - const ms_buff = this.cache.get_buffer(count); - const err_buff = this.cache.get_buffer(count << 2); - const M = new matrix_t(mc, mr, dt, m_buff.data); - const curr_mask = new matrix_t(count, 1, JSFEAT_CONSTANTS.U8_t | JSFEAT_CONSTANTS.C1_t, ms_buff.data); - - let numinliers = 0; - let nmodels = 0; - - const err = err_buff.f32; - let min_median = 1000000000.0, - sigma = 0.0, - median = 0.0; - - params.eps = 0.45; - niters = params.update_iters(params.eps, niters); - - // special case - if (count == model_points) { - if (kernel.run(from, to, M, count) <= 0) { - this.cache.put_buffer(m_buff); - this.cache.put_buffer(ms_buff); - this.cache.put_buffer(err_buff); - return false; - } - - M.copy_to(model); - if (mask) { - while (--count >= 0) { - mask.data[count] = 1; - } - } - this.cache.put_buffer(m_buff); - this.cache.put_buffer(ms_buff); - this.cache.put_buffer(err_buff); - return true; - } - - for (; iter < niters; ++iter) { - // generate subset - found = this.get_subset(kernel, from, to, model_points, count, subset0, subset1); - if (!found) { - if (iter == 0) { - this.cache.put_buffer(m_buff); - this.cache.put_buffer(ms_buff); - this.cache.put_buffer(err_buff); - return false; - } - break; - } - - nmodels = kernel.run(subset0, subset1, M, model_points); - if (nmodels <= 0) continue; - - // TODO handle multimodel output - - kernel.error(from, to, M, err, count); - median = _math.median(err, 0, count - 1); - - if (median < min_median) { - min_median = median; - M.copy_to(model); - result = true; - } - } - - if (result) { - sigma = 2.5 * 1.4826 * (1 + 5.0 / (count - model_points)) * Math.sqrt(min_median); - sigma = Math.max(sigma, 0.001); - - numinliers = this.find_inliers(kernel, model, from, to, count, sigma, err, curr_mask.data); - if (mask) curr_mask.copy_to(mask); - - result = numinliers >= model_points; - } - - this.cache.put_buffer(m_buff); - this.cache.put_buffer(ms_buff); - this.cache.put_buffer(err_buff); - - return result; - } -}; +jsfeatNext.motion_estimator = motion_estimator; jsfeatNext.ransac_params_t = ransac_params_t; diff --git a/src/motion_estimator/motion_estimator.ts b/src/motion_estimator/motion_estimator.ts index d249c03..40ed428 100644 --- a/src/motion_estimator/motion_estimator.ts +++ b/src/motion_estimator/motion_estimator.ts @@ -1,10 +1,25 @@ +import jsfeatNext from "../core/core"; import { IHomography2d } from "../homography2d/homography2d"; import { matrix_t } from "../matrix_t/matrix_t"; import { point_t } from "../point_t/point_t"; import { ransac_params_t } from "./ransac_params_t"; -export class motion_estimator { +import { JSFEAT_CONSTANTS } from "../constants/constants"; +import { homography2d } from "../motion_model/motion_model"; +import { math } from "../math/math"; + +/** + * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). + * This file previously held a type-only stub — the implementation below is the + * inline code from the monolith, verbatim (the only change: lmeds instantiates + * the math module directly instead of via the jsfeatNext.math static slot). + */ +export class motion_estimator extends jsfeatNext { + constructor() { + super(); + } + get_subset( - kernel: IHomography2d, + kernel: homography2d, from: point_t[], to: point_t[], need_cnt: number, @@ -12,10 +27,44 @@ export class motion_estimator { from_sub: point_t[], to_sub: point_t[] ): boolean { - throw new Error("Method not implemented."); + const max_try = 1000; + const indices = []; + let i = 0, + j = 0, + ssiter = 0, + idx_i = 0, + ok = false; + for (; ssiter < max_try; ++ssiter) { + i = 0; + for (; i < need_cnt && ssiter < max_try; ) { + ok = false; + idx_i = 0; + while (!ok) { + ok = true; + idx_i = indices[i] = Math.floor(Math.random() * max_cnt) | 0; + for (j = 0; j < i; ++j) { + if (idx_i == indices[j]) { + ok = false; + break; + } + } + } + from_sub[i] = from[idx_i]; + to_sub[i] = to[idx_i]; + if (!kernel.check_subset(from_sub, to_sub, i + 1)) { + ssiter++; + continue; + } + ++i; + } + break; + } + + return i == need_cnt && ssiter < max_try; } + find_inliers( - kernel: IHomography2d, + kernel: homography2d, model: matrix_t, from: point_t[], to: point_t[], @@ -24,8 +73,21 @@ export class motion_estimator { err: Int32Array | Float32Array, mask: number[] ): number { - throw new Error("Method not implemented."); + let numinliers: number = 0, + i = 0, + f = 0; + const t = thresh * thresh; + + kernel.error(from, to, model, err, count); + + for (; i < count; ++i) { + f = ((err[i] <= t)); + mask[i] = f; + numinliers += f; + } + return numinliers; } + ransac( params: ransac_params_t, kernel: any, @@ -36,8 +98,94 @@ export class motion_estimator { mask: matrix_t, max_iters: number ): boolean { - throw new Error("Method not implemented."); + if (typeof max_iters === "undefined") { + max_iters = 1000; + } + + if (count < params.size) return false; + + const model_points = params.size; + let niters = max_iters, + iter = 0; + let result: boolean = false; + + const subset0: any = []; + const subset1: any = []; + let found = false; + + const mc = model.cols, + mr = model.rows; + const dt = model.type | JSFEAT_CONSTANTS.C1_t; + + const m_buff = this.cache.get_buffer((mc * mr) << 3); + const ms_buff = this.cache.get_buffer(count); + const err_buff = this.cache.get_buffer(count << 2); + const M = new matrix_t(mc, mr, dt, m_buff.data); + const curr_mask = new matrix_t(count, 1, JSFEAT_CONSTANTS.U8C1_t, ms_buff.data); + + let inliers_max = -1, + numinliers = 0; + let nmodels = 0; + + const err = err_buff.f32; + + // special case + if (count == model_points) { + if (kernel.run(from, to, M, count) <= 0) { + this.cache.put_buffer(m_buff); + this.cache.put_buffer(ms_buff); + this.cache.put_buffer(err_buff); + return false; + } + + M.copy_to(model); + if (mask) { + while (--count >= 0) { + mask.data[count] = 1; + } + } + this.cache.put_buffer(m_buff); + this.cache.put_buffer(ms_buff); + this.cache.put_buffer(err_buff); + return true; + } + + for (; iter < niters; ++iter) { + // generate subset + found = this.get_subset(kernel, from, to, model_points, count, subset0, subset1); + if (!found) { + if (iter == 0) { + this.cache.put_buffer(m_buff); + this.cache.put_buffer(ms_buff); + this.cache.put_buffer(err_buff); + return false; + } + break; + } + + nmodels = kernel.run(subset0, subset1, M, model_points); + if (nmodels <= 0) continue; + + // TODO handle multimodel output + + numinliers = this.find_inliers(kernel, M, from, to, count, params.thresh, err, curr_mask.data); + + if (numinliers > Math.max(inliers_max, model_points - 1)) { + M.copy_to(model); + inliers_max = numinliers; + if (mask) curr_mask.copy_to(mask); + niters = params.update_iters((count - numinliers) / count, niters); + result = true; + } + } + + this.cache.put_buffer(m_buff); + this.cache.put_buffer(ms_buff); + this.cache.put_buffer(err_buff); + + return result; } + lmeds( params: ransac_params_t, kernel: any, @@ -48,6 +196,106 @@ export class motion_estimator { mask: matrix_t, max_iters: number ): boolean { - throw new Error("Method not implemented."); + if (typeof max_iters === "undefined") { + max_iters = 1000; + } + + if (count < params.size) return false; + + const model_points = params.size; + let niters = max_iters, + iter = 0; + let result: boolean = false; + const _math = new math(); + + const subset0: any = []; + const subset1: any = []; + let found = false; + + const mc = model.cols, + mr = model.rows; + const dt = model.type | JSFEAT_CONSTANTS.C1_t; + + const m_buff = this.cache.get_buffer((mc * mr) << 3); + const ms_buff = this.cache.get_buffer(count); + const err_buff = this.cache.get_buffer(count << 2); + const M = new matrix_t(mc, mr, dt, m_buff.data); + const curr_mask = new matrix_t(count, 1, JSFEAT_CONSTANTS.U8_t | JSFEAT_CONSTANTS.C1_t, ms_buff.data); + + let numinliers = 0; + let nmodels = 0; + + const err = err_buff.f32; + let min_median = 1000000000.0, + sigma = 0.0, + median = 0.0; + + params.eps = 0.45; + niters = params.update_iters(params.eps, niters); + + // special case + if (count == model_points) { + if (kernel.run(from, to, M, count) <= 0) { + this.cache.put_buffer(m_buff); + this.cache.put_buffer(ms_buff); + this.cache.put_buffer(err_buff); + return false; + } + + M.copy_to(model); + if (mask) { + while (--count >= 0) { + mask.data[count] = 1; + } + } + this.cache.put_buffer(m_buff); + this.cache.put_buffer(ms_buff); + this.cache.put_buffer(err_buff); + return true; + } + + for (; iter < niters; ++iter) { + // generate subset + found = this.get_subset(kernel, from, to, model_points, count, subset0, subset1); + if (!found) { + if (iter == 0) { + this.cache.put_buffer(m_buff); + this.cache.put_buffer(ms_buff); + this.cache.put_buffer(err_buff); + return false; + } + break; + } + + nmodels = kernel.run(subset0, subset1, M, model_points); + if (nmodels <= 0) continue; + + // TODO handle multimodel output + + kernel.error(from, to, M, err, count); + median = _math.median(err, 0, count - 1); + + if (median < min_median) { + min_median = median; + M.copy_to(model); + result = true; + } + } + + if (result) { + sigma = 2.5 * 1.4826 * (1 + 5.0 / (count - model_points)) * Math.sqrt(min_median); + sigma = Math.max(sigma, 0.001); + + numinliers = this.find_inliers(kernel, model, from, to, count, sigma, err, curr_mask.data); + if (mask) curr_mask.copy_to(mask); + + result = numinliers >= model_points; + } + + this.cache.put_buffer(m_buff); + this.cache.put_buffer(ms_buff); + this.cache.put_buffer(err_buff); + + return result; } } diff --git a/src/motion_model/motion_model.ts b/src/motion_model/motion_model.ts new file mode 100644 index 0000000..6bdd68e --- /dev/null +++ b/src/motion_model/motion_model.ts @@ -0,0 +1,488 @@ +import jsfeatNext from "../core/core"; +import { matrix_t } from "../matrix_t/matrix_t"; +import { point_t } from "../point_t/point_t"; +import { JSFEAT_CONSTANTS } from "../constants/constants"; +import matmath from "../matmath/matmath"; +import { linalg } from "../linalg/linalg"; + +/** + * Motion-model kernels for motion_estimator (issue #47): the motion_model + * base plus the affine2d and homography2d kernels, moved verbatim from the + * src/jsfeatNext.ts monolith (the only change: kernels instantiate linalg + * via direct module import instead of the jsfeatNext.linalg static slot). + * In original jsfeat these live under the jsfeat.motion_model namespace. + */ +export class motion_model extends jsfeatNext { + public T0: matrix_t; + public T1: matrix_t; + public AtA: matrix_t; + public AtB: matrix_t; + + constructor() { + super(); + this.T0 = new matrix_t(3, 3, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); + this.T1 = new matrix_t(3, 3, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); + this.AtA = new matrix_t(6, 6, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); + this.AtB = new matrix_t(6, 1, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); + } + + sqr(x: number): number { + return x * x; + } + + // does isotropic normalization + iso_normalize_points(from: point_t[], to: point_t[], T0: number[], T1: number[], count: number): void { + let i = 0; + let cx0 = 0.0, + cy0 = 0.0, + d0 = 0.0, + s0 = 0.0; + let cx1 = 0.0, + cy1 = 0.0, + d1 = 0.0, + s1 = 0.0; + let dx = 0.0, + dy = 0.0; + + for (; i < count; ++i) { + cx0 += from[i].x; + cy0 += from[i].y; + cx1 += to[i].x; + cy1 += to[i].y; + } + + cx0 /= count; + cy0 /= count; + cx1 /= count; + cy1 /= count; + + for (i = 0; i < count; ++i) { + dx = from[i].x - cx0; + dy = from[i].y - cy0; + d0 += Math.sqrt(dx * dx + dy * dy); + dx = to[i].x - cx1; + dy = to[i].y - cy1; + d1 += Math.sqrt(dx * dx + dy * dy); + } + + d0 /= count; + d1 /= count; + + s0 = Math.SQRT2 / d0; + s1 = Math.SQRT2 / d1; + + T0[0] = T0[4] = s0; + T0[2] = -cx0 * s0; + T0[5] = -cy0 * s0; + T0[1] = T0[3] = T0[6] = T0[7] = 0.0; + T0[8] = 1.0; + + T1[0] = T1[4] = s1; + T1[2] = -cx1 * s1; + T1[5] = -cy1 * s1; + T1[1] = T1[3] = T1[6] = T1[7] = 0.0; + T1[8] = 1.0; + } + + have_collinear_points(points: point_t[], count: number): boolean { + let j = 0, + k = 0, + i = (count - 1) | 0; + let dx1 = 0.0, + dy1 = 0.0, + dx2 = 0.0, + dy2 = 0.0; + + // check that the i-th selected point does not belong + // to a line connecting some previously selected points + for (; j < i; ++j) { + dx1 = points[j].x - points[i].x; + dy1 = points[j].y - points[i].y; + for (k = 0; k < j; ++k) { + dx2 = points[k].x - points[i].x; + dy2 = points[k].y - points[i].y; + if ( + Math.abs(dx2 * dy1 - dy2 * dx1) <= + JSFEAT_CONSTANTS.EPSILON * (Math.abs(dx1) + Math.abs(dy1) + Math.abs(dx2) + Math.abs(dy2)) + ) + return true; + } + } + return false; + } +} + +export class affine2d extends motion_model { + constructor() { + super(); + } + + run(from: point_t[], to: point_t[], model: matrix_t, count: number): number { + let i = 0, + j = 0; + const dt = model.type | JSFEAT_CONSTANTS.C1_t; + const md = model.data, + t0d = this.T0.data, + t1d = this.T1.data; + let pt0, + pt1, + px = 0.0, + py = 0.0; + const _matmath = new matmath(); + const _linalg = new linalg(); + + this.iso_normalize_points(from, to, t0d, t1d, count); + + const a_buff = this.cache.get_buffer((2 * count * 6) << 3); + const b_buff = this.cache.get_buffer((2 * count) << 3); + + const a_mt = new matrix_t(6, 2 * count, dt, a_buff.data); + const b_mt = new matrix_t(1, 2 * count, dt, b_buff.data); + const ad = a_mt.data, + bd = b_mt.data; + + for (; i < count; ++i) { + pt0 = from[i]; + pt1 = to[i]; + + px = t0d[0] * pt0.x + t0d[1] * pt0.y + t0d[2]; + py = t0d[3] * pt0.x + t0d[4] * pt0.y + t0d[5]; + + j = i * 2 * 6; + (ad[j] = px), (ad[j + 1] = py), (ad[j + 2] = 1.0), (ad[j + 3] = 0.0), (ad[j + 4] = 0.0), (ad[j + 5] = 0.0); + + j += 6; + (ad[j] = 0.0), (ad[j + 1] = 0.0), (ad[j + 2] = 0.0), (ad[j + 3] = px), (ad[j + 4] = py), (ad[j + 5] = 1.0); + + bd[i << 1] = t1d[0] * pt1.x + t1d[1] * pt1.y + t1d[2]; + bd[(i << 1) + 1] = t1d[3] * pt1.x + t1d[4] * pt1.y + t1d[5]; + } + + _matmath.multiply_AtA(this.AtA, a_mt); + _matmath.multiply_AtB(this.AtB, a_mt, b_mt); + + _linalg.lu_solve(this.AtA, this.AtB); + + (md[0] = this.AtB.data[0]), (md[1] = this.AtB.data[1]), (md[2] = this.AtB.data[2]); + (md[3] = this.AtB.data[3]), (md[4] = this.AtB.data[4]), (md[5] = this.AtB.data[5]); + (md[6] = 0.0), (md[7] = 0.0), (md[8] = 1.0); // fill last row + + // denormalize + _matmath.invert_3x3(this.T1, this.T1); + _matmath.multiply_3x3(model, this.T1, model); + _matmath.multiply_3x3(model, model, this.T0); + + // free buffer + this.cache.put_buffer(a_buff); + this.cache.put_buffer(b_buff); + + return 1; + } + + // Per-point reprojection error for the affine model. Ported from original + // jsfeat's affine2d; jsfeatNext was missing it, which made RANSAC/LMEDS + // with an affine2d kernel throw. See issue #51. + error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void { + let i = 0; + let pt0, pt1; + const m = model.data; + + for (; i < count; ++i) { + pt0 = from[i]; + pt1 = to[i]; + + err[i] = + this.sqr(pt1.x - m[0] * pt0.x - m[1] * pt0.y - m[2]) + + this.sqr(pt1.y - m[3] * pt0.x - m[4] * pt0.y - m[5]); + } + } + + check_subset(from: point_t[], to: point_t[], count: number): boolean { + return true; // all good + } +} + +export class homography2d extends motion_model { + public mLtL: matrix_t; + public Evec: matrix_t; + + constructor() { + super(); + this.mLtL = new matrix_t(9, 9, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); + this.Evec = new matrix_t(9, 9, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); + } + + run(from: point_t[], to: point_t[], model: matrix_t, count: number): number { + let i = 0, + j = 0; + const md = model.data, + t0d = this.T0.data, + t1d = this.T1.data; + const LtL = this.mLtL.data, + evd = this.Evec.data; + let x = 0.0, + y = 0.0, + X = 0.0, + Y = 0.0; + const _linalg = new linalg(); + const _matmath = new matmath(); + + // norm + let smx = 0.0, + smy = 0.0, + cmx = 0.0, + cmy = 0.0, + sMx = 0.0, + sMy = 0.0, + cMx = 0.0, + cMy = 0.0; + + for (; i < count; ++i) { + cmx += to[i].x; + cmy += to[i].y; + cMx += from[i].x; + cMy += from[i].y; + } + + cmx /= count; + cmy /= count; + cMx /= count; + cMy /= count; + + for (i = 0; i < count; ++i) { + smx += Math.abs(to[i].x - cmx); + smy += Math.abs(to[i].y - cmy); + sMx += Math.abs(from[i].x - cMx); + sMy += Math.abs(from[i].y - cMy); + } + + if ( + Math.abs(smx) < JSFEAT_CONSTANTS.EPSILON || + Math.abs(smy) < JSFEAT_CONSTANTS.EPSILON || + Math.abs(sMx) < JSFEAT_CONSTANTS.EPSILON || + Math.abs(sMy) < JSFEAT_CONSTANTS.EPSILON + ) + return 0; + + smx = count / smx; + smy = count / smy; + sMx = count / sMx; + sMy = count / sMy; + + t0d[0] = sMx; + t0d[1] = 0; + t0d[2] = -cMx * sMx; + t0d[3] = 0; + t0d[4] = sMy; + t0d[5] = -cMy * sMy; + t0d[6] = 0; + t0d[7] = 0; + t0d[8] = 1; + + t1d[0] = 1.0 / smx; + t1d[1] = 0; + t1d[2] = cmx; + t1d[3] = 0; + t1d[4] = 1.0 / smy; + t1d[5] = cmy; + t1d[6] = 0; + t1d[7] = 0; + t1d[8] = 1; + // + + // construct system + i = 81; + while (--i >= 0) { + LtL[i] = 0.0; + } + for (i = 0; i < count; ++i) { + x = (to[i].x - cmx) * smx; + y = (to[i].y - cmy) * smy; + X = (from[i].x - cMx) * sMx; + Y = (from[i].y - cMy) * sMy; + + LtL[0] += X * X; + LtL[1] += X * Y; + LtL[2] += X; + + LtL[6] += X * -x * X; + LtL[7] += X * -x * Y; + LtL[8] += X * -x; + LtL[10] += Y * Y; + LtL[11] += Y; + + LtL[15] += Y * -x * X; + LtL[16] += Y * -x * Y; + LtL[17] += Y * -x; + LtL[20] += 1.0; + + LtL[24] += -x * X; + LtL[25] += -x * Y; + LtL[26] += -x; + LtL[30] += X * X; + LtL[31] += X * Y; + LtL[32] += X; + LtL[33] += X * -y * X; + LtL[34] += X * -y * Y; + LtL[35] += X * -y; + LtL[40] += Y * Y; + LtL[41] += Y; + LtL[42] += Y * -y * X; + LtL[43] += Y * -y * Y; + LtL[44] += Y * -y; + LtL[50] += 1.0; + LtL[51] += -y * X; + LtL[52] += -y * Y; + LtL[53] += -y; + LtL[60] += -x * X * -x * X + -y * X * -y * X; + LtL[61] += -x * X * -x * Y + -y * X * -y * Y; + LtL[62] += -x * X * -x + -y * X * -y; + LtL[70] += -x * Y * -x * Y + -y * Y * -y * Y; + LtL[71] += -x * Y * -x + -y * Y * -y; + LtL[80] += -x * -x + -y * -y; + } + // + + // symmetry + for (i = 0; i < 9; ++i) { + for (j = 0; j < i; ++j) LtL[i * 9 + j] = LtL[j * 9 + i]; + } + + _linalg.eigenVV(this.mLtL, this.Evec); + + (md[0] = evd[72]), (md[1] = evd[73]), (md[2] = evd[74]); + (md[3] = evd[75]), (md[4] = evd[76]), (md[5] = evd[77]); + (md[6] = evd[78]), (md[7] = evd[79]), (md[8] = evd[80]); + + // denormalize + _matmath.multiply_3x3(model, this.T1, model); + _matmath.multiply_3x3(model, model, this.T0); + + // set bottom right to 1.0 + x = 1.0 / md[8]; + md[0] *= x; + md[1] *= x; + md[2] *= x; + md[3] *= x; + md[4] *= x; + md[5] *= x; + md[6] *= x; + md[7] *= x; + md[8] = 1.0; + + return 1; + } + + error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void { + let i = 0; + let pt0, + pt1, + ww = 0.0, + dx = 0.0, + dy = 0.0; + const m = model.data; + + for (; i < count; ++i) { + pt0 = from[i]; + pt1 = to[i]; + + ww = 1.0 / (m[6] * pt0.x + m[7] * pt0.y + 1.0); + dx = (m[0] * pt0.x + m[1] * pt0.y + m[2]) * ww - pt1.x; + dy = (m[3] * pt0.x + m[4] * pt0.y + m[5]) * ww - pt1.y; + err[i] = dx * dx + dy * dy; + } + } + + check_subset(from: point_t[], to: point_t[], count: number): boolean { + // seems to reject good subsets actually + //if( have_collinear_points(from, count) || have_collinear_points(to, count) ) { + //return false; + //} + const _matmath = new matmath(); + if (count == 4) { + let negative = 0; + + const fp0 = from[0], + fp1 = from[1], + fp2 = from[2], + fp3 = from[3]; + const tp0 = to[0], + tp1 = to[1], + tp2 = to[2], + tp3 = to[3]; + + // set1 + let A11 = fp0.x, + A12 = fp0.y, + A13 = 1.0; + let A21 = fp1.x, + A22 = fp1.y, + A23 = 1.0; + let A31 = fp2.x, + A32 = fp2.y, + A33 = 1.0; + + let B11 = tp0.x, + B12 = tp0.y, + B13 = 1.0; + let B21 = tp1.x, + B22 = tp1.y, + B23 = 1.0; + let B31 = tp2.x, + B32 = tp2.y, + B33 = 1.0; + + let detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); + let detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); + + if (detA * detB < 0) negative++; + + // set2 + (A11 = fp1.x), (A12 = fp1.y); + (A21 = fp2.x), (A22 = fp2.y); + (A31 = fp3.x), (A32 = fp3.y); + + (B11 = tp1.x), (B12 = tp1.y); + (B21 = tp2.x), (B22 = tp2.y); + (B31 = tp3.x), (B32 = tp3.y); + + detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); + detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); + + if (detA * detB < 0) negative++; + + // set3 + (A11 = fp0.x), (A12 = fp0.y); + (A21 = fp2.x), (A22 = fp2.y); + (A31 = fp3.x), (A32 = fp3.y); + + (B11 = tp0.x), (B12 = tp0.y); + (B21 = tp2.x), (B22 = tp2.y); + (B31 = tp3.x), (B32 = tp3.y); + + detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); + detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); + + if (detA * detB < 0) negative++; + + // set4 + (A11 = fp0.x), (A12 = fp0.y); + (A21 = fp1.x), (A22 = fp1.y); + (A31 = fp3.x), (A32 = fp3.y); + + (B11 = tp0.x), (B12 = tp0.y); + (B21 = tp1.x), (B22 = tp1.y); + (B31 = tp3.x), (B32 = tp3.y); + + detA = _matmath.determinant_3x3(A11, A12, A13, A21, A22, A23, A31, A32, A33); + detB = _matmath.determinant_3x3(B11, B12, B13, B21, B22, B23, B31, B32, B33); + + if (detA * detB < 0) negative++; + + if (negative != 0 && negative != 4) { + return false; + } + } + return true; // all good + } +} From 04f1be0892866819be1c7defa2afee7bc80383c3 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 21:42:44 +0200 Subject: [PATCH 08/10] refactor(optical_flow_lk): de-duplicate the last module; jsfeatNext.ts is now a thin aggregator (#47) Ninth and FINAL de-duplication step of #47. - src/optical_flow_lk/optical_flow_lk.ts: replace the type-only stub with the REAL implementation moved verbatim from the monolith (track). The constructor instantiates imgproc via direct module import instead of the jsfeatNext.imgproc static slot. - src/jsfeatNext.ts: now a 59-line THIN AGGREGATOR - it only imports the modules and attaches them to the public namespace. Unused helper imports (resample/convol kernels, linalg_base, fast_private, orb/yape06 utils, point_t, JSFEAT_CONSTANTS) pruned; they are consumed by the modules themselves now. This completes the stub/monolith de-duplication: every algorithm lives in its own real module under src//, extending the shared base from src/core/core.ts. No type-only stubs remain; the audit's section-4 target architecture (thin aggregator) is reached for the module layer. Verified behavior-preserving: tsc --noEmit clean; npm test 57/57 (the optical_flow_lk parity test pins track() vs original jsfeat across pyramid levels); UMD bundle checked - all 18 public modules attach and instanceof chains hold. Co-Authored-By: Claude Fable 5 --- src/jsfeatNext.ts | 331 +------------------------ src/optical_flow_lk/optical_flow_lk.ts | 313 ++++++++++++++++++++++- 2 files changed, 316 insertions(+), 328 deletions(-) diff --git a/src/jsfeatNext.ts b/src/jsfeatNext.ts index 518dc5a..e721bd0 100644 --- a/src/jsfeatNext.ts +++ b/src/jsfeatNext.ts @@ -1,34 +1,25 @@ import jsfeatNext from "./core/core"; import { cache } from "./cache/cache"; import { imgproc } from "./imgproc/imgproc"; -import { _resample, _resample_u8 } from "./imgproc/resample"; -import { _convol, _convol_u8 } from "./imgproc/convol"; import { linalg } from "./linalg/linalg"; -import { swap, hypot } from "./linalg/linalg_base"; import { fast_corners } from "./fast_corners/fast_corners"; -import { _cmp_score_16 } from "./fast_corners/fast_private"; import { math } from "./math/math"; import matmath from "./matmath/matmath"; import { matrix_t } from "./matrix_t/matrix_t"; import { pyramid_t } from "./pyramid_t/pyramid_t"; -import { point_t } from "./point_t/point_t"; import { transform } from "./transform/transform"; import { keypoint_t } from "./keypoint_t/keypoint_t"; import { orb } from "./orb/orb"; -import { bit_pattern_31 } from "./orb/bit_pattern_31"; -import { rectify_patch } from "./orb/rectify_patch"; import { yape } from "./yape/yape"; -import { compute_laplacian, hessian_min_eigen_value } from "./yape06/yape06_utils"; import { yape06 } from "./yape06/yape06"; import { ransac_params_t } from "./motion_estimator/ransac_params_t"; import { motion_estimator } from "./motion_estimator/motion_estimator"; -import { motion_model, affine2d, homography2d } from "./motion_model/motion_model"; +import { affine2d, homography2d } from "./motion_model/motion_model"; import { optical_flow_lk } from "./optical_flow_lk/optical_flow_lk"; -import { JSFEAT_CONSTANTS } from "./constants/constants"; -// The base class (constants, cache, data-type helpers, static module slots) -// lives in src/core/core.ts since issue #47; this file is on its -// way to becoming a thin aggregator that only attaches the modules. +// Thin aggregator (issue #47): every algorithm lives in its own module under +// src//, extending the base class from src/core/core.ts. This file +// only attaches the modules to the public jsfeatNext namespace. export default jsfeatNext; jsfeatNext.cache = cache; @@ -65,316 +56,4 @@ jsfeatNext.affine2d = affine2d; jsfeatNext.homography2d = homography2d; -jsfeatNext.optical_flow_lk = class optical_flow_lk extends jsfeatNext { - public scharr_deriv: any; - - constructor() { - super(); - const _imgproc = new jsfeatNext.imgproc(); - this.scharr_deriv = _imgproc.scharr_derivatives; - } - - track( - prev_pyr: pyramid_t, - curr_pyr: pyramid_t, - prev_xy: Float32Array, - curr_xy: Float32Array, - count: number, - win_size: number, - max_iter: number, - status: Uint8Array, - eps: number, - min_eigen_threshold: number - ): void { - if (typeof max_iter === "undefined") { - max_iter = 30; - } - if (typeof status === "undefined") { - status = new Uint8Array(count); - } - if (typeof eps === "undefined") { - eps = 0.01; - } - if (typeof min_eigen_threshold === "undefined") { - min_eigen_threshold = 0.0001; - } - - const half_win = (win_size - 1) * 0.5; - const win_area = (win_size * win_size) | 0; - const win_area2 = win_area << 1; - const prev_imgs = prev_pyr.data, - next_imgs = curr_pyr.data; - let img_prev = prev_imgs[0].data, - img_next = next_imgs[0].data; - let w0 = prev_imgs[0].cols, - h0 = prev_imgs[0].rows, - lw = 0, - lh = 0; - - const iwin_node = this.cache.get_buffer(win_area << 2); - const deriv_iwin_node = this.cache.get_buffer(win_area2 << 2); - const deriv_lev_node = this.cache.get_buffer((h0 * (w0 << 1)) << 2); - - const deriv_m = new matrix_t(w0, h0, JSFEAT_CONSTANTS.S32C2_t, deriv_lev_node.data); - - const iwin_buf = iwin_node.i32; - const deriv_iwin = deriv_iwin_node.i32; - const deriv_lev = deriv_lev_node.i32; - - let dstep = 0, - src = 0, - dsrc = 0, - iptr = 0, - diptr = 0, - jptr = 0; - let lev_sc = 0.0, - prev_x = 0.0, - prev_y = 0.0, - next_x = 0.0, - next_y = 0.0; - let prev_delta_x = 0.0, - prev_delta_y = 0.0, - delta_x = 0.0, - delta_y = 0.0; - let iprev_x = 0, - iprev_y = 0, - inext_x = 0, - inext_y = 0; - let i = 0, - j = 0, - x = 0, - y = 0, - level = 0, - ptid = 0, - iter = 0; - let brd_tl = 0, - brd_r = 0, - brd_b = 0; - let a = 0.0, - b = 0.0, - b1 = 0.0, - b2 = 0.0; - - // fixed point math - const W_BITS14 = 14; - const W_BITS4 = 14; - const W_BITS1m5 = W_BITS4 - 5; - const W_BITS1m51 = 1 << (W_BITS1m5 - 1); - const W_BITS14_ = 1 << W_BITS14; - const W_BITS41 = 1 << (W_BITS4 - 1); - const FLT_SCALE = 1.0 / (1 << 20); - let iw00 = 0, - iw01 = 0, - iw10 = 0, - iw11 = 0, - ival = 0, - ixval = 0, - iyval = 0; - let A11 = 0.0, - A12 = 0.0, - A22 = 0.0, - D = 0.0, - min_eig = 0.0; - - const FLT_EPSILON = 0.00000011920929; - eps *= eps; - - // reset status - for (; i < count; ++i) { - status[i] = 1; - } - - const max_level = (prev_pyr.levels - 1) | 0; - level = max_level; - - for (; level >= 0; --level) { - lev_sc = 1.0 / (1 << level); - lw = w0 >> level; - lh = h0 >> level; - dstep = lw << 1; - img_prev = prev_imgs[level].data; - img_next = next_imgs[level].data; - - brd_r = (lw - win_size) | 0; - brd_b = (lh - win_size) | 0; - - // calculate level derivatives - this.scharr_deriv(prev_imgs[level], deriv_m); - - // iterate through points - for (ptid = 0; ptid < count; ++ptid) { - i = ptid << 1; - j = i + 1; - prev_x = prev_xy[i] * lev_sc; - prev_y = prev_xy[j] * lev_sc; - - if (level == max_level) { - next_x = prev_x; - next_y = prev_y; - } else { - next_x = curr_xy[i] * 2.0; - next_y = curr_xy[j] * 2.0; - } - curr_xy[i] = next_x; - curr_xy[j] = next_y; - - prev_x -= half_win; - prev_y -= half_win; - iprev_x = prev_x | 0; - iprev_y = prev_y | 0; - - // border check - x = ((iprev_x <= brd_tl || iprev_x >= brd_r || iprev_y <= brd_tl || iprev_y >= brd_b)); - if (x != 0) { - if (level == 0) { - status[ptid] = 0; - } - continue; - } - - a = prev_x - iprev_x; - b = prev_y - iprev_y; - iw00 = ((1.0 - a) * (1.0 - b) * W_BITS14_ + 0.5) | 0; - iw01 = (a * (1.0 - b) * W_BITS14_ + 0.5) | 0; - iw10 = ((1.0 - a) * b * W_BITS14_ + 0.5) | 0; - iw11 = W_BITS14_ - iw00 - iw01 - iw10; - - (A11 = 0.0), (A12 = 0.0), (A22 = 0.0); - - // extract the patch from the first image, compute covariation matrix of derivatives - for (y = 0; y < win_size; ++y) { - src = ((y + iprev_y) * lw + iprev_x) | 0; - dsrc = src << 1; - - iptr = (y * win_size) | 0; - diptr = iptr << 1; - for (x = 0; x < win_size; ++x, ++src, ++iptr, dsrc += 2) { - ival = - img_prev[src] * iw00 + - img_prev[src + 1] * iw01 + - img_prev[src + lw] * iw10 + - img_prev[src + lw + 1] * iw11; - ival = (ival + W_BITS1m51) >> W_BITS1m5; - - ixval = - deriv_lev[dsrc] * iw00 + - deriv_lev[dsrc + 2] * iw01 + - deriv_lev[dsrc + dstep] * iw10 + - deriv_lev[dsrc + dstep + 2] * iw11; - ixval = (ixval + W_BITS41) >> W_BITS4; - - iyval = - deriv_lev[dsrc + 1] * iw00 + - deriv_lev[dsrc + 3] * iw01 + - deriv_lev[dsrc + dstep + 1] * iw10 + - deriv_lev[dsrc + dstep + 3] * iw11; - iyval = (iyval + W_BITS41) >> W_BITS4; - - iwin_buf[iptr] = ival; - deriv_iwin[diptr++] = ixval; - deriv_iwin[diptr++] = iyval; - - A11 += ixval * ixval; - A12 += ixval * iyval; - A22 += iyval * iyval; - } - } - - A11 *= FLT_SCALE; - A12 *= FLT_SCALE; - A22 *= FLT_SCALE; - - D = A11 * A22 - A12 * A12; - min_eig = (A22 + A11 - Math.sqrt((A11 - A22) * (A11 - A22) + 4.0 * A12 * A12)) / win_area2; - - if (min_eig < min_eigen_threshold || D < FLT_EPSILON) { - if (level == 0) { - status[ptid] = 0; - } - continue; - } - - D = 1.0 / D; - - next_x -= half_win; - next_y -= half_win; - prev_delta_x = 0.0; - prev_delta_y = 0.0; - - for (iter = 0; iter < max_iter; ++iter) { - inext_x = next_x | 0; - inext_y = next_y | 0; - - x = ( - ((inext_x <= brd_tl || inext_x >= brd_r || inext_y <= brd_tl || inext_y >= brd_b)) - ); - if (x != 0) { - if (level == 0) { - status[ptid] = 0; - } - break; - } - - a = next_x - inext_x; - b = next_y - inext_y; - iw00 = ((1.0 - a) * (1.0 - b) * W_BITS14_ + 0.5) | 0; - iw01 = (a * (1.0 - b) * W_BITS14_ + 0.5) | 0; - iw10 = ((1.0 - a) * b * W_BITS14_ + 0.5) | 0; - iw11 = W_BITS14_ - iw00 - iw01 - iw10; - (b1 = 0.0), (b2 = 0.0); - - for (y = 0; y < win_size; ++y) { - jptr = ((y + inext_y) * lw + inext_x) | 0; - - iptr = (y * win_size) | 0; - diptr = iptr << 1; - for (x = 0; x < win_size; ++x, ++jptr, ++iptr) { - ival = - img_next[jptr] * iw00 + - img_next[jptr + 1] * iw01 + - img_next[jptr + lw] * iw10 + - img_next[jptr + lw + 1] * iw11; - ival = (ival + W_BITS1m51) >> W_BITS1m5; - ival = ival - iwin_buf[iptr]; - - b1 += ival * deriv_iwin[diptr++]; - b2 += ival * deriv_iwin[diptr++]; - } - } - - b1 *= FLT_SCALE; - b2 *= FLT_SCALE; - - delta_x = (A12 * b2 - A22 * b1) * D; - delta_y = (A12 * b1 - A11 * b2) * D; - - next_x += delta_x; - next_y += delta_y; - curr_xy[i] = next_x + half_win; - curr_xy[j] = next_y + half_win; - - if (delta_x * delta_x + delta_y * delta_y <= eps) { - break; - } - - if ( - iter > 0 && - Math.abs(delta_x + prev_delta_x) < 0.01 && - Math.abs(delta_y + prev_delta_y) < 0.01 - ) { - curr_xy[i] -= delta_x * 0.5; - curr_xy[j] -= delta_y * 0.5; - break; - } - - prev_delta_x = delta_x; - prev_delta_y = delta_y; - } - } // points loop - } // levels loop - - this.cache.put_buffer(iwin_node); - this.cache.put_buffer(deriv_iwin_node); - this.cache.put_buffer(deriv_lev_node); - } -}; +jsfeatNext.optical_flow_lk = optical_flow_lk; diff --git a/src/optical_flow_lk/optical_flow_lk.ts b/src/optical_flow_lk/optical_flow_lk.ts index a742884..9606c7f 100644 --- a/src/optical_flow_lk/optical_flow_lk.ts +++ b/src/optical_flow_lk/optical_flow_lk.ts @@ -1,5 +1,25 @@ +import jsfeatNext from "../core/core"; +import { matrix_t } from "../matrix_t/matrix_t"; import { pyramid_t } from "../pyramid_t/pyramid_t"; -export class optical_flow_lk { +import { JSFEAT_CONSTANTS } from "../constants/constants"; +import { imgproc } from "../imgproc/imgproc"; + +/** + * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). + * This file previously held a type-only stub — the implementation below is the + * inline code from the monolith, verbatim (the only change: the constructor + * instantiates the imgproc module directly instead of via the + * jsfeatNext.imgproc static slot). + */ +export class optical_flow_lk extends jsfeatNext { + public scharr_deriv: any; + + constructor() { + super(); + const _imgproc = new imgproc(); + this.scharr_deriv = _imgproc.scharr_derivatives; + } + track( prev_pyr: pyramid_t, curr_pyr: pyramid_t, @@ -12,6 +32,295 @@ export class optical_flow_lk { eps: number, min_eigen_threshold: number ): void { - throw new Error("Method not implemented."); + if (typeof max_iter === "undefined") { + max_iter = 30; + } + if (typeof status === "undefined") { + status = new Uint8Array(count); + } + if (typeof eps === "undefined") { + eps = 0.01; + } + if (typeof min_eigen_threshold === "undefined") { + min_eigen_threshold = 0.0001; + } + + const half_win = (win_size - 1) * 0.5; + const win_area = (win_size * win_size) | 0; + const win_area2 = win_area << 1; + const prev_imgs = prev_pyr.data, + next_imgs = curr_pyr.data; + let img_prev = prev_imgs[0].data, + img_next = next_imgs[0].data; + let w0 = prev_imgs[0].cols, + h0 = prev_imgs[0].rows, + lw = 0, + lh = 0; + + const iwin_node = this.cache.get_buffer(win_area << 2); + const deriv_iwin_node = this.cache.get_buffer(win_area2 << 2); + const deriv_lev_node = this.cache.get_buffer((h0 * (w0 << 1)) << 2); + + const deriv_m = new matrix_t(w0, h0, JSFEAT_CONSTANTS.S32C2_t, deriv_lev_node.data); + + const iwin_buf = iwin_node.i32; + const deriv_iwin = deriv_iwin_node.i32; + const deriv_lev = deriv_lev_node.i32; + + let dstep = 0, + src = 0, + dsrc = 0, + iptr = 0, + diptr = 0, + jptr = 0; + let lev_sc = 0.0, + prev_x = 0.0, + prev_y = 0.0, + next_x = 0.0, + next_y = 0.0; + let prev_delta_x = 0.0, + prev_delta_y = 0.0, + delta_x = 0.0, + delta_y = 0.0; + let iprev_x = 0, + iprev_y = 0, + inext_x = 0, + inext_y = 0; + let i = 0, + j = 0, + x = 0, + y = 0, + level = 0, + ptid = 0, + iter = 0; + let brd_tl = 0, + brd_r = 0, + brd_b = 0; + let a = 0.0, + b = 0.0, + b1 = 0.0, + b2 = 0.0; + + // fixed point math + const W_BITS14 = 14; + const W_BITS4 = 14; + const W_BITS1m5 = W_BITS4 - 5; + const W_BITS1m51 = 1 << (W_BITS1m5 - 1); + const W_BITS14_ = 1 << W_BITS14; + const W_BITS41 = 1 << (W_BITS4 - 1); + const FLT_SCALE = 1.0 / (1 << 20); + let iw00 = 0, + iw01 = 0, + iw10 = 0, + iw11 = 0, + ival = 0, + ixval = 0, + iyval = 0; + let A11 = 0.0, + A12 = 0.0, + A22 = 0.0, + D = 0.0, + min_eig = 0.0; + + const FLT_EPSILON = 0.00000011920929; + eps *= eps; + + // reset status + for (; i < count; ++i) { + status[i] = 1; + } + + const max_level = (prev_pyr.levels - 1) | 0; + level = max_level; + + for (; level >= 0; --level) { + lev_sc = 1.0 / (1 << level); + lw = w0 >> level; + lh = h0 >> level; + dstep = lw << 1; + img_prev = prev_imgs[level].data; + img_next = next_imgs[level].data; + + brd_r = (lw - win_size) | 0; + brd_b = (lh - win_size) | 0; + + // calculate level derivatives + this.scharr_deriv(prev_imgs[level], deriv_m); + + // iterate through points + for (ptid = 0; ptid < count; ++ptid) { + i = ptid << 1; + j = i + 1; + prev_x = prev_xy[i] * lev_sc; + prev_y = prev_xy[j] * lev_sc; + + if (level == max_level) { + next_x = prev_x; + next_y = prev_y; + } else { + next_x = curr_xy[i] * 2.0; + next_y = curr_xy[j] * 2.0; + } + curr_xy[i] = next_x; + curr_xy[j] = next_y; + + prev_x -= half_win; + prev_y -= half_win; + iprev_x = prev_x | 0; + iprev_y = prev_y | 0; + + // border check + x = ((iprev_x <= brd_tl || iprev_x >= brd_r || iprev_y <= brd_tl || iprev_y >= brd_b)); + if (x != 0) { + if (level == 0) { + status[ptid] = 0; + } + continue; + } + + a = prev_x - iprev_x; + b = prev_y - iprev_y; + iw00 = ((1.0 - a) * (1.0 - b) * W_BITS14_ + 0.5) | 0; + iw01 = (a * (1.0 - b) * W_BITS14_ + 0.5) | 0; + iw10 = ((1.0 - a) * b * W_BITS14_ + 0.5) | 0; + iw11 = W_BITS14_ - iw00 - iw01 - iw10; + + (A11 = 0.0), (A12 = 0.0), (A22 = 0.0); + + // extract the patch from the first image, compute covariation matrix of derivatives + for (y = 0; y < win_size; ++y) { + src = ((y + iprev_y) * lw + iprev_x) | 0; + dsrc = src << 1; + + iptr = (y * win_size) | 0; + diptr = iptr << 1; + for (x = 0; x < win_size; ++x, ++src, ++iptr, dsrc += 2) { + ival = + img_prev[src] * iw00 + + img_prev[src + 1] * iw01 + + img_prev[src + lw] * iw10 + + img_prev[src + lw + 1] * iw11; + ival = (ival + W_BITS1m51) >> W_BITS1m5; + + ixval = + deriv_lev[dsrc] * iw00 + + deriv_lev[dsrc + 2] * iw01 + + deriv_lev[dsrc + dstep] * iw10 + + deriv_lev[dsrc + dstep + 2] * iw11; + ixval = (ixval + W_BITS41) >> W_BITS4; + + iyval = + deriv_lev[dsrc + 1] * iw00 + + deriv_lev[dsrc + 3] * iw01 + + deriv_lev[dsrc + dstep + 1] * iw10 + + deriv_lev[dsrc + dstep + 3] * iw11; + iyval = (iyval + W_BITS41) >> W_BITS4; + + iwin_buf[iptr] = ival; + deriv_iwin[diptr++] = ixval; + deriv_iwin[diptr++] = iyval; + + A11 += ixval * ixval; + A12 += ixval * iyval; + A22 += iyval * iyval; + } + } + + A11 *= FLT_SCALE; + A12 *= FLT_SCALE; + A22 *= FLT_SCALE; + + D = A11 * A22 - A12 * A12; + min_eig = (A22 + A11 - Math.sqrt((A11 - A22) * (A11 - A22) + 4.0 * A12 * A12)) / win_area2; + + if (min_eig < min_eigen_threshold || D < FLT_EPSILON) { + if (level == 0) { + status[ptid] = 0; + } + continue; + } + + D = 1.0 / D; + + next_x -= half_win; + next_y -= half_win; + prev_delta_x = 0.0; + prev_delta_y = 0.0; + + for (iter = 0; iter < max_iter; ++iter) { + inext_x = next_x | 0; + inext_y = next_y | 0; + + x = ( + ((inext_x <= brd_tl || inext_x >= brd_r || inext_y <= brd_tl || inext_y >= brd_b)) + ); + if (x != 0) { + if (level == 0) { + status[ptid] = 0; + } + break; + } + + a = next_x - inext_x; + b = next_y - inext_y; + iw00 = ((1.0 - a) * (1.0 - b) * W_BITS14_ + 0.5) | 0; + iw01 = (a * (1.0 - b) * W_BITS14_ + 0.5) | 0; + iw10 = ((1.0 - a) * b * W_BITS14_ + 0.5) | 0; + iw11 = W_BITS14_ - iw00 - iw01 - iw10; + (b1 = 0.0), (b2 = 0.0); + + for (y = 0; y < win_size; ++y) { + jptr = ((y + inext_y) * lw + inext_x) | 0; + + iptr = (y * win_size) | 0; + diptr = iptr << 1; + for (x = 0; x < win_size; ++x, ++jptr, ++iptr) { + ival = + img_next[jptr] * iw00 + + img_next[jptr + 1] * iw01 + + img_next[jptr + lw] * iw10 + + img_next[jptr + lw + 1] * iw11; + ival = (ival + W_BITS1m51) >> W_BITS1m5; + ival = ival - iwin_buf[iptr]; + + b1 += ival * deriv_iwin[diptr++]; + b2 += ival * deriv_iwin[diptr++]; + } + } + + b1 *= FLT_SCALE; + b2 *= FLT_SCALE; + + delta_x = (A12 * b2 - A22 * b1) * D; + delta_y = (A12 * b1 - A11 * b2) * D; + + next_x += delta_x; + next_y += delta_y; + curr_xy[i] = next_x + half_win; + curr_xy[j] = next_y + half_win; + + if (delta_x * delta_x + delta_y * delta_y <= eps) { + break; + } + + if ( + iter > 0 && + Math.abs(delta_x + prev_delta_x) < 0.01 && + Math.abs(delta_y + prev_delta_y) < 0.01 + ) { + curr_xy[i] -= delta_x * 0.5; + curr_xy[j] -= delta_y * 0.5; + break; + } + + prev_delta_x = delta_x; + prev_delta_y = delta_y; + } + } // points loop + } // levels loop + + this.cache.put_buffer(iwin_node); + this.cache.put_buffer(deriv_iwin_node); + this.cache.put_buffer(deriv_lev_node); } } From 2a9d23980502e91e6f67ffa979d8e021b50f83e3 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 8 Jul 2026 22:08:46 +0200 Subject: [PATCH 09/10] build: rebuild dist and types after the #47 de-duplication One-shot artifact rebuild for the integration of the stub/monolith de-duplication (#62-#70) into dev, per the agreed convention (refactor PRs ship source only; artifacts are rebuilt at integration/release points). - dist/jsfeatNext.js (UMD) + dist/jsfeatNext.mjs (ESM) rebuilt from the refactored source - types/ regenerated: now mirrors the new module layout, including types/src/core/core.d.ts and types/src/motion_model/motion_model.d.ts Verified: npm test 57/57; all 18 public modules attach on the fresh UMD bundle; VERSION 0.7.6 intact; no .d.ts files leak into dist/. Co-Authored-By: Claude Fable 5 --- dist/jsfeatNext.js | 2 +- dist/jsfeatNext.mjs | 2309 +++++++++-------- types/src/core/core.d.ts | 69 + types/src/fast_corners/fast_corners.d.ts | 10 +- types/src/imgproc/imgproc.d.ts | 6 +- types/src/jsfeatNext.d.ts | 96 +- types/src/linalg/linalg.d.ts | 6 +- types/src/math/math.d.ts | 9 +- .../motion_estimator/motion_estimator.d.ts | 10 +- types/src/motion_model/motion_model.d.ts | 27 + .../src/optical_flow_lk/optical_flow_lk.d.ts | 5 +- types/src/orb/orb.d.ts | 9 +- types/src/pyramid_t/pyramid_t.d.ts | 6 +- types/src/yape06/yape06.d.ts | 6 +- 14 files changed, 1310 insertions(+), 1260 deletions(-) create mode 100644 types/src/core/core.d.ts create mode 100644 types/src/motion_model/motion_model.d.ts diff --git a/dist/jsfeatNext.js b/dist/jsfeatNext.js index ee32a1c..51c39e6 100644 --- a/dist/jsfeatNext.js +++ b/dist/jsfeatNext.js @@ -1 +1 @@ -(function(e,t){typeof exports==`object`&&typeof module<`u`?module.exports=t():typeof define==`function`&&define.amd?define([],t):(e=typeof globalThis<`u`?globalThis:e||self,e.jsfeatNext=t())})(this,function(){var e=class{constructor(){this._data_type_size=new Int32Array([-1,1,4,-1,4,-1,-1,-1,8,-1,-1,-1,-1,-1,-1,-1,8])}_get_data_type(e){return e&65280}_get_channel(e){return e&255}_get_data_type_size(e){return this._data_type_size[(e&65280)>>8]}},t=class{constructor(e,t){this.size=(e+7|0)&-8,t===void 0?this.buffer=new ArrayBuffer(this.size):(this.buffer=t,this.size=t.length),this.u8=new Uint8Array(this.buffer),this.i32=new Int32Array(this.buffer),this.f32=new Float32Array(this.buffer),this.f64=new Float64Array(this.buffer)}},n=class{constructor(e){this.next=null,this.data=new t(e),this.size=this.data.size,this.buffer=this.data.buffer,this.u8=this.data.u8,this.i32=this.data.i32,this.f32=this.data.f32,this.f64=this.data.f64}resize(e){delete this.data,this.data=new t(e),this.size=this.data.size,this.buffer=this.data.buffer,this.u8=this.data.u8,this.i32=this.data.i32,this.f32=this.data.f32,this.f64=this.data.f64}},r=class{constructor(){this._pool_head,this._pool_tail,this._pool_size=0}allocate(e,t){this._pool_head=this._pool_tail=new n(t);for(let r=0;rt.size&&t.resize(e),t}put_buffer(e){this._pool_tail=this._pool_tail.next=e,this._pool_size++}};function i(e,t,n,r,i){let a=0,o=e.channel,s=e.cols,c=e.rows,l=e.data,u=t.data,d=s/r,f=c/i,p=d*f*65536|0,m=0,h=0,g=0,_=0,v=0,y=0,b=0,x=0,S=0,C=0,w=0,T=0,E=0,D=0,O=0,k=0,A=n.get_buffer(r*o<<2),j=n.get_buffer(r*o<<2),M=n.get_buffer(s*2*3<<2),N=A.i32,P=j.i32,F=M.i32;for(;mS&&(F[x++]=m*o|0,F[x++]=(v-1)*o|0,F[x++]=(v-S)*256|0,a++),g=v;g.001&&(a++,F[x++]=m*o|0,F[x++]=y*o|0,F[x++]=(C-y)*256|0)}for(m=0;mS&&(a++,F[x++]=(v-1)*o|0,F[x++]=m*o|0,F[x++]=(v-S)*p),g=v;g.001&&(a++,F[x++]=y*o|0,F[x++]=m*o|0,F[x++]=(C-y)*p)}for(m=0;m>8,255),n[f+l+1]=Math.min(m>>8,255),n[f+l+2]=Math.min(h>>8,255),n[f+l+3]=Math.min(g>>8,255)}for(;l>8,255)}d+=r,f+=r}for(c=0;c>8,255),n[f+r]=Math.min(m>>8,255),n[f+y]=Math.min(h>>8,255),n[f+b]=Math.min(g>>8,255)}for(;l>8,255)}}}function s(e,t,n,r,i,a,o,s){let c=0,l=0,u=0,d=0,f=0,p=0,m=0,h=0,g=0,_=a[0],v=0,y=r<<1,b=r*3,x=r<<2;for(;ct?(t/=e,e*Math.sqrt(1+t*t)):t>0?(e/=t,t*Math.sqrt(1+e*e)):0}function u(e,t,n,r,i){let a=0,o=e[t],s=i,c=0,l=0,u=0;for(;a<25;++a)r[a]=o-e[t+n[a]];for(a=0;a<16;a+=2)c=Math.min(r[a+1],r[a+2]),c=Math.min(c,r[a+3]),!(c<=s)&&(c=Math.min(c,r[a+4]),c=Math.min(c,r[a+5]),c=Math.min(c,r[a+6]),c=Math.min(c,r[a+7]),c=Math.min(c,r[a+8]),s=Math.max(s,Math.min(c,r[a])),s=Math.max(s,Math.min(c,r[a+9])));for(l=-s,a=0;a<16;a+=2)u=Math.max(r[a+1],r[a+2]),u=Math.max(u,r[a+3]),u=Math.max(u,r[a+4]),u=Math.max(u,r[a+5]),!(u>=l)&&(u=Math.max(u,r[a+6]),u=Math.max(u,r[a+7]),u=Math.max(u,r[a+8]),l=Math.min(l,Math.max(u,r[a])),l=Math.min(l,Math.max(u,r[a+9])));return-l-1}var d=class{constructor(){}identity(e,t){t===void 0&&(t=1);let n=e.data,r=e.rows,i=e.cols,a=i+1|0,o=r*i,s=o;for(;--o>=0;)n[o]=0;for(o=s,s=0;sthis.buffer.size?(this.cols=e,this.rows=t,this.channel=n,this.allocate()):(this.cols=e,this.rows=t,this.channel=n)}},m=class{constructor(){}perspective_4point_transform(e,t,n,r,i,a,o,s,c,l,u,d,f,p,m,h,g){let _=t,v=l,y=o,b=_*v*y,x=m,S=_*x,C=v*S,w=u,T=_*w,E=a,D=n,O=p,k=D*O,A=k*E,j=O*E*w,M=O*y,N=O*w,P=v*y,F=x*v,I=x*E,L=w*E,R=1/(M-N-P+F-I+L),z=_*O,B=D*E,V=y*_,ee=x*V,H=D*v,U=k*w,W=D*w*E,G=y*x*v,K=x*D,te=-(C-b+T*E-E*S-k*v+A-j+M*v)*R,q=(b-C-z*y+z*w+A-v*B+I*v-j)*R,ne=_,J=(-w*S+ee+H*y-k*y+U-W+I*w-G)*R,Y=(-ee+V*w-K*v+U-W+K*E+G-M*w)*R,X=D,Z=(-T+V+H-B+N-M-F+I)*R,Q=(-S+T+k-H+I-L-M+P)*R;_=r,v=d,y=c,b=_*v*y,x=g,S=_*x,C=v*S,w=f,T=_*w,E=s,D=i,O=h,k=D*O,A=k*E,j=O*E*w,M=O*y,N=O*w,P=v*y,F=x*v,I=x*E,L=w*E,R=1/(M-N-P+F-I+L),z=_*O,B=D*E,V=y*_,ee=x*V,H=D*v,U=k*w,W=D*w*E,G=y*x*v,K=x*D;let re=-(C-b+T*E-E*S-k*v+A-j+M*v)*R,ie=(b-C-z*y+z*w+A-v*B+I*v-j)*R,ae=_,oe=(-w*S+ee+H*y-k*y+U-W+I*w-G)*R,se=(-ee+V*w-K*v+U-W+K*E+G-M*w)*R,ce=D,le=(-T+V+H-B+N-M-F+I)*R,ue=(-S+T+k-H+I-L-M+P)*R;v=Y-Q*X,y=te*Y,b=te*X,S=J*q,C=ne*J,T=q*Z;let de=ne*Z;O=1/(y-b*Q-S+C*Q+T*X-de*Y),A=-J+X*Z;let fe=-J*Q+Y*Z;L=-q+ne*Q;let pe=te-de;B=te*Q-T,V=-q*X+ne*Y;let me=b-C,he=y-S;W=v*O;let ge=L*O,_e=V*O,$=e.data;$[0]=re*W+A*O*ie-fe*O*ae,$[1]=re*ge+pe*O*ie-B*O*ae,$[2]=-re*_e-me*O*ie+he*O*ae,$[3]=oe*W+A*O*se-fe*O*ce,$[4]=oe*ge+pe*O*se-B*O*ce,$[5]=-oe*_e-me*O*se+he*O*ce,$[6]=le*W+A*O*ue-fe*O,$[7]=le*ge+pe*O*ue-B*O,$[8]=-le*_e-me*O*ue+he*O}invert_affine_transform(e,t){let n=e.data,r=t.data,i=n[0],a=n[1],o=n[2],s=n[3],c=n[4],l=n[5],u=1/(i*c-a*s);r[0]=u*c,r[1]=u*-a,r[2]=u*(a*l-o*c),r[3]=u*-s,r[4]=u*i,r[5]=u*(o*s-i*l)}invert_perspective_transform(e,t){let n=e.data,r=t.data,i=n[0],a=n[1],o=n[2],s=n[3],c=n[4],l=n[5],u=n[6],d=n[7],f=n[8],p=1/(i*(c*f-l*d)-a*(s*f-l*u)+o*(s*d-c*u));r[0]=p*(c*f-l*d),r[1]=p*(o*d-a*f),r[2]=p*(a*l-o*c),r[3]=p*(l*u-s*f),r[4]=p*(i*f-o*u),r[5]=p*(o*s-i*l),r[6]=p*(s*d-c*u),r[7]=p*(a*u-i*d),r[8]=p*(i*c-a*s)}},h=class{constructor(e=0,t=0,n=0,r=0,i=-1){this.x=e,this.y=t,this.score=n,this.level=r,this.angle=i}},g=[8,-3,9,5,4,2,7,-12,-11,9,-8,2,7,-12,12,-13,2,-13,2,12,1,-7,1,6,-2,-10,-2,-4,-13,-13,-11,-8,-13,-3,-12,-9,10,4,11,9,-13,-8,-8,-9,-11,7,-9,12,7,7,12,6,-4,-5,-3,0,-13,2,-12,-3,-9,0,-7,5,12,-6,12,-1,-3,6,-2,12,-6,-13,-4,-8,11,-13,12,-8,4,7,5,1,5,-3,10,-3,3,-7,6,12,-8,-7,-6,-2,-2,11,-1,-10,-13,12,-8,10,-7,3,-5,-3,-4,2,-3,7,-10,-12,-6,11,5,-12,6,-7,5,-6,7,-1,1,0,4,-5,9,11,11,-13,4,7,4,12,2,-1,4,4,-4,-12,-2,7,-8,-5,-7,-10,4,11,9,12,0,-8,1,-13,-13,-2,-8,2,-3,-2,-2,3,-6,9,-4,-9,8,12,10,7,0,9,1,3,7,-5,11,-10,-13,-6,-11,0,10,7,12,1,-6,-3,-6,12,10,-9,12,-4,-13,8,-8,-12,-13,0,-8,-4,3,3,7,8,5,7,10,-7,-1,7,1,-12,3,-10,5,6,2,-4,3,-10,-13,0,-13,5,-13,-7,-12,12,-13,3,-11,8,-7,12,-4,7,6,-10,12,8,-9,-1,-7,-6,-2,-5,0,12,-12,5,-7,5,3,-10,8,-13,-7,-7,-4,5,-3,-2,-1,-7,2,9,5,-11,-11,-13,-5,-13,-1,6,0,-1,5,-3,5,2,-4,-13,-4,12,-9,-6,-9,6,-12,-10,-8,-4,10,2,12,-3,7,12,12,12,-7,-13,-6,5,-4,9,-3,4,7,-1,12,2,-7,6,-5,1,-13,11,-12,5,-3,7,-2,-6,7,-8,12,-7,-13,-7,-11,-12,1,-3,12,12,2,-6,3,0,-4,3,-2,-13,-1,-13,1,9,7,1,8,-6,1,-1,3,12,9,1,12,6,-1,-9,-1,3,-13,-13,-10,5,7,7,10,12,12,-5,12,9,6,3,7,11,5,-13,6,10,2,-12,2,3,3,8,4,-6,2,6,12,-13,9,-12,10,3,-8,4,-7,9,-11,12,-4,-6,1,12,2,-8,6,-9,7,-4,2,3,3,-2,6,3,11,0,3,-3,8,-8,7,8,9,3,-11,-5,-6,-4,-10,11,-5,10,-5,-8,-3,12,-10,5,-9,0,8,-1,12,-6,4,-6,6,-11,-10,12,-8,7,4,-2,6,7,-2,0,-2,12,-5,-8,-5,2,7,-6,10,12,-9,-13,-8,-8,-5,-13,-5,-2,8,-8,9,-13,-9,-11,-9,0,1,-8,1,-2,7,-4,9,1,-2,1,-1,-4,11,-6,12,-11,-12,-9,-6,4,3,7,7,12,5,5,10,8,0,-4,2,8,-9,12,-5,-13,0,7,2,12,-1,2,1,7,5,11,7,-9,3,5,6,-8,-13,-4,-8,9,-5,9,-3,-3,-4,-7,-3,-12,6,5,8,0,-7,6,-6,12,-13,6,-5,-2,1,-10,3,10,4,1,8,-4,-2,-2,2,-13,2,-12,12,12,-2,-13,0,-6,4,1,9,3,-6,-10,-3,-5,-3,-13,-1,1,7,5,12,-11,4,-2,5,-7,-13,9,-9,-5,7,1,8,6,7,-8,7,6,-7,-4,-7,1,-8,11,-7,-8,-13,6,-12,-8,2,4,3,9,10,-5,12,3,-6,-5,-6,7,8,-3,9,-8,2,-12,2,8,-11,-2,-10,3,-12,-13,-7,-9,-11,0,-10,-5,5,-3,11,8,-2,-13,-1,12,-1,-8,0,9,-13,-11,-12,-5,-10,-2,-10,11,-3,9,-2,-13,2,-3,3,2,-9,-13,-4,0,-4,6,-3,-10,-4,12,-2,-7,-6,-11,-4,9,6,-3,6,11,-13,11,-5,5,11,11,12,6,7,-5,12,-2,-1,12,0,7,-4,-8,-3,-2,-7,1,-6,7,-13,-12,-8,-13,-7,-2,-6,-8,-8,5,-6,-9,-5,-1,-4,5,-13,7,-8,10,1,5,5,-13,1,0,10,-13,9,12,10,-1,5,-8,10,-9,-1,11,1,-13,-9,-3,-6,2,-1,-10,1,12,-13,1,-8,-10,8,-11,10,-6,2,-13,3,-6,7,-13,12,-9,-10,-10,-5,-7,-10,-8,-8,-13,4,-6,8,5,3,12,8,-13,-4,2,-3,-3,5,-13,10,-12,4,-13,5,-1,-9,9,-4,3,0,3,3,-9,-12,1,-6,1,3,2,4,-8,-10,-10,-10,9,8,-13,12,12,-8,-12,-6,-5,2,2,3,7,10,6,11,-8,6,8,8,-12,-7,10,-6,5,-3,-9,-3,9,-1,-13,-1,5,-3,-7,-3,4,-8,-2,-8,3,4,2,12,12,2,-5,3,11,6,-9,11,-13,3,-1,7,12,11,-1,12,4,-3,0,-3,6,4,-11,4,12,2,-4,2,1,-10,-6,-8,1,-13,7,-11,1,-13,12,-11,-13,6,0,11,-13,0,-1,1,4,-13,3,-9,-2,-9,8,-6,-3,-13,-6,-8,-2,5,-9,8,10,2,7,3,-9,-1,-6,-1,-1,9,5,11,-2,11,-3,12,-8,3,0,3,5,-1,4,0,10,3,-6,4,5,-13,0,-10,5,5,8,12,11,8,9,9,-6,7,-4,8,-12,-10,4,-10,9,7,3,12,4,9,-7,10,-2,7,0,12,-2,-1,-6,0,-11];function _(e,t,n,r,i,a,o,s){let c=Math.cos(n),l=Math.sin(n);o.data[0]=c,o.data[1]=-l,o.data[2]=(-c+l)*a*.5+r,o.data[3]=l,o.data[4]=c,o.data[5]=(-l-c)*a*.5+i,s.warp_affine(e,t,o,128)}function v(e,t,n){let r=0,i,a;for(i=n,a=0;a=0;i--,r++)a=Math.sqrt(n*n-i*i)+.5|0,t[r]=i+e*a;for(;-i=0;a--,r++)i=-Math.sqrt(n*n-a*a)-.5|0,t[r]=i+e*a;for(;a>i;a--,r++)i=-Math.sqrt(n*n-a*a)-.5|0,t[r]=i+e*a;for(i++;i<=0;i++,r++)a=-Math.sqrt(n*n-i*i)-.5|0,t[r]=i+e*a;for(;i<-a;i++,r++)a=-Math.sqrt(n*n-i*i)-.5|0,t[r]=i+e*a;for(a++;a<0;a++,r++)i=Math.sqrt(n*n-a*a)+.5|0,t[r]=i+e*a;return t[r]=t[0],t[r+1]=t[1],r}function y(e,t,n){let r=0;return e[t+1]!=0&&r++,e[t-1]!=0&&r++,e[t+n]!=0&&r++,e[t+n+1]!=0&&r++,e[t+n-1]!=0&&r++,e[t-n]!=0&&r++,e[t-n+1]!=0&&r++,e[t-n-1]!=0&&r++,r}function b(e,t,n,r,i){let a,o;if(n>0)for(t-=r*i,o=-i;o<=i;++o){for(a=-i;a<=i;++a)if(e[t+a]>n)return!1;t+=r}else for(t-=r*i,o=-i;o<=i;++o){for(a=-i;a<=i;++a)if(e[t+a]=r)if(f=e[t+a[u]],f<=i)if(f>=r){n[t]=0;return}else if(u++,p=e[t+a[u]],p>i)if(u++,m=e[t+a[u]],m>i)h=3;else if(mi)h=7;else if(mi)if(u++,m=e[t+a[u]],m>i)h=3;else if(mi)h=7;else if(mi){n[t]=0;return}if(u++,p=e[t+a[u]],p>i){n[t]=0;return}if(u++,m=e[t+a[u]],m>i){n[t]=0;return}h=1}else{if(f=e[t+a[u]],fi){if(p=m,u++,m=e[t+a[u]],mi){n[t]=0;return}if(m>i){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],m>i){n[t]=0;return}c-=d+p,h=8;break}if(p<=i){n[t]=0;return}if(m<=i){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],m>i){c-=d+p,h=3;break}if(mi){n[t]=0;return}c-=d+p,h=1;break}if(d>i){if(p=r){n[t]=0;return}if(m>=r){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],mi){c-=d+p,h=7;break}n[t]=0;return;case 2:if(d>i){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],di){n[t]=0;return}c-=d+p,h=4;break}if(m>i){c-=d+p,h=7;break}if(mi){if(mi){c-=d+p,h=3;break}if(mi){n[t]=0;return}if(di){n[t]=0;return}c-=d+p,h=1;break}if(m>=r){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],mi){c-=d+p,h=7;break}n[t]=0;return;case 5:if(di){if(p=m,u++,m=e[t+a[u]],mi){c-=d+p,h=3;break}if(mi){n[t]=0;return}if(di){c-=d+p,h=3;break}if(mi){n[t]=0;return}if(di){c-=d+p,h=7;break}n[t]=0;return;case 8:if(d>i){if(mi){n[t]=0;return}c-=d+p,h=1;break}n[t]=0;return;case 9:if(di){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],m>i){n[t]=0;return}c-=d+p,h=8;break}if(d>i){if(p=m,u++,m=e[t+a[u]],m>i,t>>i,n)}detect(e,t,n=4){let r=this.level_tables[0],i=r.radius|0,a=i-1|0,o=r.dirs,s=r.dirs_count|0,c=s>>1,l=e.data,u=e.cols|0,d=e.rows|0,f=u>>1,p=r.scores,m=0,h=0,g=0,_=0,v=0,S=0,C=0,w=0,T=this.tau|0,E=0,D,O=Math.max(i+1,n)|0,k=Math.max(i+1,n)|0,A=Math.min(u-i-2,u-n)|0,j=Math.min(d-i-2,d-n)|0;for(g=k*u+O|0,h=k;h=3&&b(p,_,w,f,i)&&(D=t[E],D.x=m,D.y=h,D.score=C,++E,m+=a,_+=a);return E}};function w(e,t,n,r,i,a,o,s,c){let l=0,u=0,d=o*n+a|0,f=d;for(l=o;l=0&&f+i=0?t[f]=-4*e[f]+e[f+r]+e[f-r]+e[f+i]+e[f-i]:t[f]=0}function T(e,t,n,r,i,a,o){let s=-2*e[t]+e[t+r]+e[t-r],c=-2*e[t]+e[t+i]+e[t-i],l=e[t+a]+e[t-a]-e[t+o]-e[t-o],u=Math.sqrt((s-c)*(s-c)+4*l*l)|0;return Math.min(Math.abs(n-u),Math.abs(-(n+u)))}var E=class{constructor(e=0,t=.5,n=.5,r=.99){this.size=e,this.thresh=t,this.eps=n,this.prob=r}update_iters(e,t){let n=Math.log(1-this.prob),r=Math.log(1-Math.pow(1-e,this.size));return(r>=0||-n>=t*-r?t:Math.round(n/r))|0}},D={name:`@webarkit/jsfeat-next`,version:`0.7.6`,description:`Typescript version of jsfeat for WebARKit`,main:`dist/jsfeatNext.js`,module:`dist/jsfeatNext.mjs`,types:`types/src/index.d.ts`,unpkg:`dist/jsfeatNext.js`,jsdelivr:`dist/jsfeatNext.js`,exports:{".":{types:`./types/src/index.d.ts`,import:`./dist/jsfeatNext.mjs`,require:`./dist/jsfeatNext.js`},"./package.json":`./package.json`},files:[`dist`,`types`,`src`],scripts:{"build-ts":`vite build`,"dev-ts":`vite build --watch`,"format-check":`prettier --check .`,format:`prettier --write .`,test:`vitest run`,"test:watch":`vitest`},repository:{type:`git`,url:`git+https://github.com/webarkit/jsfeatNext.git`},keywords:[`jsfeat`,`jsfeatNext`,`WebAR`,`WebARKit`,`AugmentedReality`,`computer`,`vision`],author:`Walter Perdan @kalwalt`,license:`LGPL-3.0-or-later`,bugs:{url:`https://github.com/webarkit/jsfeatNext/issues`},homepage:`https://github.com/webarkit/jsfeatNext#readme`,devDependencies:{prettier:`~3.5.3`,typescript:`^6.0.3`,vite:`^8.1.3`,"vite-plugin-dts":`^5.0.3`,vitest:`^4.1.10`}},O,k=class{constructor(){this.dt=new e,this.cache=new r,this.cache.allocate(30,640*4)}get_data_type(e){return this.dt._get_data_type(e)}get_channel(e){return this.dt._get_channel(e)}get_data_type_size(e){return this.dt._get_data_type_size(e)}};O=k,O.VERSION=D.version,O.EPSILON=f.EPSILON,O.FLT_MIN=f.FLT_MIN,O.U8_t=f.U8_t,O.S32_t=f.S32_t,O.F32_t=f.F32_t,O.S64_t=f.S64_t,O.F64_t=f.F64_t,O.C1_t=f.C1_t,O.C2_t=f.C2_t,O.C3_t=f.C3_t,O.C4_t=f.C4_t,O.COLOR_RGBA2GRAY=f.COLOR_RGBA2GRAY,O.COLOR_RGB2GRAY=f.COLOR_RGB2GRAY,O.COLOR_BGRA2GRAY=f.COLOR_BGRA2GRAY,O.COLOR_BGR2GRAY=f.COLOR_BGR2GRAY,O.BOX_BLUR_NOSCALE=f.BOX_BLUR_NOSCALE,O.SVD_U_T=f.SVD_U_T,O.SVD_V_T=f.SVD_V_T,O.U8C1_t=O.U8_t|O.C1_t,O.U8C3_t=O.U8_t|O.C3_t,O.U8C4_t=O.U8_t|O.C4_t,O.F32C1_t=O.F32_t|O.C1_t,O.F32C2_t=O.F32_t|O.C2_t,O.S32C1_t=O.S32_t|O.C1_t,O.S32C2_t=O.S32_t|O.C2_t;var A=class extends k{constructor(){super(),this.T0=new p(3,3,f.F32_t|f.C1_t),this.T1=new p(3,3,f.F32_t|f.C1_t),this.AtA=new p(6,6,f.F32_t|f.C1_t),this.AtB=new p(6,1,f.F32_t|f.C1_t)}sqr(e){return e*e}iso_normalize_points(e,t,n,r,i){let a=0,o=0,s=0,c=0,l=0,u=0,d=0,f=0,p=0,m=0,h=0;for(;a=0;)l[i]=0;for(i=0;i=0;)this.data[r]=new p(e>>r,t>>r,n)}build(e,t){t===void 0&&(t=!0);let n=2,r=e,i=this.data[0];if(!t){let t=e.cols*e.rows;for(;--t>=0;)i.data[t]=e.data[t]}for(i=this.data[1],this.pyrdown(r,i);nthis._threshold?2:0;return this._threshold}detect(e,t,n){n===void 0&&(n=3);let r=e.data,i=e.cols,a=e.rows,o=0,s=0,c=0,l=0,d=0,f=0,p=this.cache.get_buffer(3*i),m=this.cache.get_buffer((i+1)*3<<2),h=p.u8,g=m.i32,_=this.pixel_off,v=this.score_diff,y=Math.max(3,n),b=Math.min(a-2,a-n),x=Math.max(3,n),S=Math.min(i-3,i-n),C=0,w=0,T,E=u,D=this.threshold_tab,O=this._threshold,k=0,A=0,j=0,M=0,N=0,P=0,F=0,I=0,L=0,R=0,z=0,B=0;this._cmp_offsets(_,i,16);let V=_[0],ee=_[1],H=_[2],U=_[3],W=_[4],G=_[5],K=_[6],te=_[7],q=_[8],ne=_[9],J=_[10],Y=_[11],X=_[12],Z=_[13],Q=_[14],re=_[15];for(o=0;o8){++M,g[N+M]=s,h[P+s]=E(r,F,_,v,O);break}}else C=0;if(j&2)for(l=k+O,C=0,c=0;c<25;++c)if(d=r[F+_[c]],d>l){if(++C,C>8){++M,g[N+M]=s,h[P+s]=E(r,F,_,v,O);break}}else C=0}}if(g[N+i]=M,o!=y)for(f=(o-4+3)%3,I=f*i|0,N=f*(i+1)|0,f=(o-5+3)%3,L=f*i|0,M=g[N+i],c=0;ch[I+R]&&B>h[I+z]&&B>h[L+z]&&B>h[L+s]&&B>h[L+R]&&B>h[P+z]&&B>h[P+s]&&B>h[P+R]&&(T=t[w],T.x=s,T.y=o-1,T.score=B,w++)}return this.cache.put_buffer(p),this.cache.put_buffer(m),w}_cmp_offsets(e,t,n){let r=0,i=this.offsets16;for(;r>14,v[u+1]=e[l+h]*d+e[l+h+1]*p+e[l+h+2]*m+8192>>14,v[u+2]=e[l+g]*d+e[l+g+1]*p+e[l+g+2]*m+8192>>14,v[u+3]=e[l+_]*d+e[l+_+1]*p+e[l+_+2]*m+8192>>14;for(;a>14}}resample(e,t,n,r){let o=e.rows,s=e.cols;o>r&&s>n&&(t.resize(n,r,e.channel),e.type&f.U8_t&&t.type&f.U8_t&&o*s/(r*n)<256?i(e,t,this.cache,n,r):a(e,t,this.cache,n,r))}box_blur_gray(e,t,n,r){r===void 0&&(r=0);let i=e.cols,a=e.rows,o=a<<1,s=i<<1,c=0,l=0,u=0,d=0,p=(n<<1)+1|0,m=n+1|0,h=m+1|0,g=r&f.BOX_BLUR_NOSCALE?1:1/(p*p),_=this.cache.get_buffer(i*a<<2),v=0,y=0,b=0,x=0,S=0,C=_.i32,w=e.data,T=0;for(t.resize(i,a,e.channel),u=0;u>1,c=e.cols,l=e.rows,u=e.type,d=u&f.U8_t;t.resize(c,l,e.channel);let p=e.data,m=t.data,h,g,_=n+Math.max(l,c)|0,v=this.cache.get_buffer(_<<2),y=this.cache.get_buffer(n<<2);d?(h=v.i32,g=y.i32):u&f.S32_t?(h=v.i32,g=y.f32):(h=v.f32,g=y.f32),i.get_gaussian_kernel(n,r,g,u),d?o(h,p,m,c,l,g,n,a):s(h,p,m,c,l,g,n,a),this.cache.put_buffer(v),this.cache.put_buffer(y)}hough_transform(e,t,n,r){let i,a,o=e.data,s=e.cols,c=e.rows,l=s,u=Math.round((Math.PI-0)/n),d=Math.round(((s+c)*2+1)/t),f=1/t,p=new Int32Array((u+2)*(d+2)),m=new Float32Array(u),h=new Float32Array(u),g=0,_=0;for(;gr&&p[e]>p[e-1]&&p[e]>=p[e+1]&&p[e]>p[e-d-2]&&p[e]>=p[e+d+2]&&v.push(e)}v.sort(function(e,t){return p[e]>p[t]||p[e]==p[t]&&e>1,s=a>>1,c=o-(n<<1),l=s-(r<<1),u=0,d=0,f=n+r*i,p=0,m=0,h=0;t.resize(o,s,e.channel);let g=e.data,_=t.data;for(d=0;d>2,_[h+1]=g[p+2]+g[p+3]+g[p+i+2]+g[p+i+3]+2>>2;for(;u>2;f+=i<<1,m+=o}}scharr_derivatives(e,t){let n=e.cols,r=e.rows,i=n<<1,a=0,o=0,s=0,c,l,u,d,p,m,h=0,g=0,_=0,v=0,y,b;t.resize(n,r,2);let x=e.data,S=t.data,C=this.cache.get_buffer(n+2<<2),w=this.cache.get_buffer(n+2<<2);for(e.type&f.U8_t||e.type&f.S32_t?(y=C.i32,b=w.i32):(y=C.f32,b=w.f32);o0?o-1:1)*n|0,_=(o0?o-1:1)*n|0,_=(o0;--p)for(u=p+a*s,d=u-s,f=a;f>0;--f,u-=s,d-=s)r[u]+=r[d]+r[d+1]}}equalize_histogram(e,t){let n=e.cols,r=e.rows,i=e.data;t.resize(n,r,e.channel);let a=t.data,o=n*r,s=0,c=0,l,u,d=this.cache.get_buffer(1024);for(l=d.i32;s<256;++s)l[s]=0;for(s=0;sr&&(s=n,n=r,r=s),s=3*(i+2)|0;--s>=0;)T[s]=0;for(s=(a+2)*(i+2)|0;--s>=0;)E[s]=0;for(;c>31)-(g>>31)+((_^_>>31)-(_>>31));for(s=1;s<=a;++s,l+=u){if(s==a)for(c=M+i;--c>=M;)T[c]=0;else for(c=0;c>31)-(g>>31)+((_^_>>31)-(_>>31));for(d=l-u|0,E[P-1]=0,m=0,c=0;cn){if(g=O[d],_=O[d+1],v=g^_,g=(g^g>>31)-(g>>31)|0,_=(_^_>>31)-(_>>31)|0,y=g*13573,b=y+(g+g<<15),_<<=15,_T[j+c-1]&&h>=T[j+c+1]){h>r&&!m&&E[P+c-N]!=2?(E[P+c]=2,m=1,D[F++]=P+c):E[P+c]=1;continue}}else if(_>b){if(h>T[A+c]&&h>=T[M+c]){h>r&&!m&&E[P+c-N]!=2?(E[P+c]=2,m=1,D[F++]=P+c):E[P+c]=1;continue}}else if(v=v<0?-1:1,h>T[A+c-v]&&h>T[M+c+v]){h>r&&!m&&E[P+c-N]!=2?(E[P+c]=2,m=1,D[F++]=P+c):E[P+c]=1;continue}}E[P+c]=0,m=0}E[P+i]=0,P+=N,c=A,A=j,j=M,M=c}for(c=P-N-1,s=0;s0;)P=D[--F],P-=N+1,E[P]==1&&(E[P]=2,D[F++]=P),P+=1,E[P]==1&&(E[P]=2,D[F++]=P),P+=1,E[P]==1&&(E[P]=2,D[F++]=P),P+=N,E[P]==1&&(E[P]=2,D[F++]=P),P-=2,E[P]==1&&(E[P]=2,D[F++]=P),P+=N,E[P]==1&&(E[P]=2,D[F++]=P),P+=1,E[P]==1&&(E[P]=2,D[F++]=P),P+=1,E[P]==1&&(E[P]=2,D[F++]=P);for(P=N+1,A=0,s=0;s0&&g>0&&p=0&&m>=0&&p95&&r>40&&i>20&&n>r&&n>i&&n-Math.min(r,i)>15&&Math.abs(n-r)>15?t[o]=255:t[o]=0}},k.math=class extends k{constructor(){super(),this.qsort_stack=new Int32Array(96)}get_gaussian_kernel(e,t,n,r){let i=0,a=0,o=0,s=0,c=0,l=0,u=this.cache.get_buffer(e<<2),d=u.f32;if((e&1)==1&&e<=7&&t<=0)switch(e>>1){case 0:d[0]=1,l=1;break;case 1:d[0]=.25,d[1]=.5,d[2]=.25,l=1;break;case 2:d[0]=.0625,d[1]=.25,d[2]=.375,d[3]=.25,d[4]=.0625,l=1;break;case 3:d[0]=.03125,d[1]=.109375,d[2]=.21875,d[3]=.28125,d[4]=.21875,d[5]=.109375,d[6]=.03125,l=1;break}else for(s=t>0?t:((e-1)*.5-1)*.3+.8,c=-.5/(s*s);i=0;)for(l=E[c<<1],u=E[(c<<1)+1],c--;;)if(f=u-l+1,f<=7){for(m=l+1;m<=u;m++)for(h=m;h>l&&r(e[h],e[h-1]);h--)i=e[h],e[h]=e[h-1],e[h-1]=i;break}else{for(T=0,_=l,y=u,x=l+(f>>1),f>40&&(g=f>>3,S=l,C=l+g,w=l+(g<<1),a=e[S],o=e[C],s=e[w],l=r(a,o)?r(o,s)?C:r(a,s)?w:S:r(s,o)?C:r(a,s)?S:w,S=x-g,C=x,w=x+g,a=e[S],o=e[C],s=e[w],x=r(a,o)?r(o,s)?C:r(a,s)?w:S:r(s,o)?C:r(a,s)?S:w,S=u-(g<<1),C=u-g,w=u,a=e[S],o=e[C],s=e[w],u=r(a,o)?r(o,s)?C:r(a,s)?w:S:r(s,o)?C:r(a,s)?S:w),S=l,C=x,w=u,a=e[S],o=e[C],s=e[w],x=r(a,o)?r(o,s)?C:r(a,s)?w:S:r(s,o)?C:r(a,s)?S:w,x!=_&&(i=e[x],e[x]=e[_],e[_]=i,x=_),l=v=_+1,u=b=y,a=e[x];;){for(;l<=u&&!r(a,e[l]);)r(e[l],a)||(l>v&&(i=e[v],e[v]=e[l],e[l]=i),T=1,v++),l++;for(;l<=u&&!r(e[u],a);)r(a,e[u])||(uu)break;i=e[l],e[l]=e[u],e[u]=i,T=1,l++,u--}if(T==0){for(l=_,u=y,m=l+1;m<=u;m++)for(h=m;h>l&&r(e[h],e[h-1]);h--)i=e[h],e[h]=e[h-1],e[h-1]=i;break}for(f=Math.min(v-_,l-v),p=l-f|0,d=0;d1)p>1?f>p?(++c,E[c<<1]=_,E[(c<<1)+1]=_+f-1,l=y-p+1,u=y):(++c,E[c<<1]=y-p+1,E[(c<<1)+1]=y,l=_,u=_+f-1):(l=_,u=_+f-1);else if(p>1)l=y-p+1,u=y;else break}}median(e,t,n){let r,i=0,a=0,o=0,s=t+n>>1;for(;;){if(n<=t)return e[s];if(n==t+1)return e[t]>e[n]&&(r=e[t],e[t]=e[n],e[n]=r),e[s];for(i=t+n>>1,e[i]>e[n]&&(r=e[i],e[i]=e[n],e[n]=r),e[t]>e[n]&&(r=e[t],e[t]=e[n],e[n]=r),e[i]>e[t]&&(r=e[i],e[i]=e[t],e[t]=r),a=t+1,r=e[i],e[i]=e[a],e[a]=r,o=n;;){do++a;while(e[t]>e[a]);do--o;while(e[o]>e[t]);if(o=s&&(n=o-1)}return 0}},k.matmath=d,k.linalg=class extends k{constructor(){super(),this.matmath=new d}JacobiImpl(e,t,n,r,i,a){let o=f.EPSILON,s=0,u=0,d=0,p=0,m=0,h=0,g=0,_=0,v=0,y=a*a*30,b=0,x=0,S=0,C=0,w=0,T=0,E=0,D=0,O=0,k=this.cache.get_buffer(a<<2),A=this.cache.get_buffer(a<<2),j=k.i32,M=A.i32;if(r)for(;s0){for(p=0,b=Math.abs(e[d]),s=1;s1)for(;v0){for(p=0,b=Math.abs(e[h]),s=1;s>16&256?L:-L,e[p*t+h]=I;for(g=0;g<2;g++)for(m=0;mMath.abs(s[i*o+n])&&(i=r);if(Math.abs(s[i*o+n])=0;n--){for(p=l[n],i=n+1;i=0;s--){for(f=d[s],c=s+1,a=c*l;c=0;)n.data[o]=_.data[o];else n&&this.matmath.transpose(n,_);if(r&&i&f.SVD_V_T)for(o=u*u;--o>=0;)r.data[o]=y.data[o];else r&&this.matmath.transpose(r,y)}else{if(n&&i&f.SVD_U_T)for(o=u*u;--o>=0;)n.data[o]=y.data[o];else n&&this.matmath.transpose(n,y);if(r&&i&f.SVD_V_T)for(o=l*l;--o>=0;)r.data[o]=_.data[o];else r&&this.matmath.transpose(r,_)}this.cache.put_buffer(m),this.cache.put_buffer(h),this.cache.put_buffer(g)}svd_solve(e,t,n){let r=0,i=0,a=0,o=0,s=0,c=e.rows,l=e.cols,u=0,d=0,m=0,h=e.type|f.C1_t,g=this.cache.get_buffer(c*c<<3),_=this.cache.get_buffer(l<<3),v=this.cache.get_buffer(l*l<<3),y=new p(c,c,h,g.data),b=new p(1,l,h,_.data),x=new p(l,l,h,v.data),S=n.data,C=y.data,w=b.data,T=x.data;for(this.svd_decompose(e,b,y,x,0),m=f.EPSILON*w[0]*l;rm){for(a=0,u=0,o=0;ad&&(u+=w[o+i]*S[a]/C[i]);x[s]=u}this.cache.put_buffer(h),this.cache.put_buffer(g),this.cache.put_buffer(_)}eigenVV(e,t,n){let r=e.cols,i=r*r,a=e.type|f.C1_t,o=this.cache.get_buffer(r*r<<3),s=this.cache.get_buffer(r<<3),c=new p(r,r,a,o.data),l=new p(1,r,a,s.data);for(;--i>=0;)c.data[i]=e.data[i];if(this.JacobiImpl(c.data,r,l.data,t?t.data:null,r,r),n)for(;--r>=0;)n.data[r]=l.data[r];this.cache.put_buffer(o),this.cache.put_buffer(s)}},k.orb=class extends k{constructor(){super(),this.bit_pattern_31_=new Int32Array(g),this.H=new p(3,3,f.F32_t|f.C1_t),this.patch_img=new p(32,32,f.U8_t|f.C1_t),this.imgproc=new k.imgproc}describe(e,t,n,r){let i=0,a=0,o=0,s=0,c=0,l=0,u=0,d=0,p=this.patch_img.data,m=0;r.type&f.U8_t?r.resize(32,n,1):(r.type=f.U8_t,r.cols=32,r.rows=n,r.channel=1,r.allocate());let h=r.data,g=0;for(i=0;i=0;)f[r]=0;for(w(s,f,a,5,c,x,S,C,E),m=S*a+x|0,i=S;iy&&p>f[h-1]&&p>f[h+1]&&p>f[h-a]&&p>f[h+a]&&p>f[h-a-1]&&p>f[h+a-1]&&p>f[h-a+1]&&p>f[h+a+1])&&(g=T(s,h,p,5,c,l,u),g>b&&(_=t[v],_.x=r,_.y=i,_.score=g,++v,++r,++h));return this.cache.put_buffer(d),v}},k.motion_estimator=class extends k{constructor(){super()}get_subset(e,t,n,r,i,a,o){let s=1e3,c=[],l=0,u=0,d=0,f=0,p=!1;for(;d=0;)o.data[i]=1;return this.cache.put_buffer(b),this.cache.put_buffer(x),this.cache.put_buffer(S),!0}for(;uMath.max(T,c-1)&&(C.copy_to(a),T=E,o&&w.copy_to(o),l=e.update_iters((i-E)/i,l),d=!0))}return this.cache.put_buffer(b),this.cache.put_buffer(x),this.cache.put_buffer(S),d}lmeds(e,t,n,r,i,a,o,s){if(s===void 0&&(s=1e3),i=0;)o.data[i]=1;return this.cache.put_buffer(x),this.cache.put_buffer(S),this.cache.put_buffer(C),!0}for(;u=c),this.cache.put_buffer(x),this.cache.put_buffer(S),this.cache.put_buffer(C),d}},k.ransac_params_t=E,k.affine2d=j,k.homography2d=M,k.optical_flow_lk=class extends k{constructor(){super();let e=new k.imgproc;this.scharr_deriv=e.scharr_derivatives}track(e,t,n,r,i,a,o,s,c,l){o===void 0&&(o=30),s===void 0&&(s=new Uint8Array(i)),c===void 0&&(c=.01),l===void 0&&(l=1e-4);let u=(a-1)*.5,d=a*a|0,m=d<<1,h=e.data,g=t.data,_=h[0].data,v=g[0].data,y=h[0].cols,b=h[0].rows,x=0,S=0,C=this.cache.get_buffer(d<<2),w=this.cache.get_buffer(m<<2),T=this.cache.get_buffer(b*(y<<1)<<2),E=new p(y,b,f.S32C2_t,T.data),D=C.i32,O=w.i32,k=T.i32,A=0,j=0,M=0,N=0,P=0,F=0,I=0,L=0,R=0,z=0,B=0,V=0,ee=0,H=0,U=0,W=0,G=0,K=0,te=0,q=0,ne=0,J=0,Y=0,X=0,Z=0,Q=0,re=0,ie=0,ae=0,oe=0,se=0,ce=0,le=16384,ue=8192,de=1/(1<<20),fe=0,pe=0,me=0,he=0,ge=0,_e=0,$=0,ve=0,ye=0,be=0,xe=0,Se=0;for(c*=c;q=0;--X)for(I=1/(1<>X,S=b>>X,A=x<<1,_=h[X].data,v=g[X].data,re=x-a|0,ie=S-a|0,this.scharr_deriv(h[X],E),Z=0;Z=re||G<=0||G>=ie,J!=0){X==0&&(s[Z]=0);continue}for(ae=L-W,oe=R-G,fe=(1-ae)*(1-oe)*le+.5|0,pe=ae*(1-oe)*le+.5|0,me=(1-ae)*oe*le+.5|0,he=le-fe-pe-me,ve=0,ye=0,be=0,Y=0;Y>9,_e=k[M]*fe+k[M+2]*pe+k[M+A]*me+k[M+A+2]*he,_e=_e+ue>>14,$=k[M+1]*fe+k[M+3]*pe+k[M+A+1]*me+k[M+A+3]*he,$=$+ue>>14,D[N]=ge,O[P++]=_e,O[P++]=$,ve+=_e*_e,ye+=_e*$,be+=$*$;if(ve*=de,ye*=de,be*=de,xe=ve*be-ye*ye,Se=(be+ve-Math.sqrt((ve-be)*(ve-be)+4*ye*ye))/m,Se=re||te<=0||te>=ie,J!=0){X==0&&(s[Z]=0);break}for(ae=z-K,oe=B-te,fe=(1-ae)*(1-oe)*le+.5|0,pe=ae*(1-oe)*le+.5|0,me=(1-ae)*oe*le+.5|0,he=le-fe-pe-me,se=0,ce=0,Y=0;Y>9,ge-=D[N],se+=ge*O[P++],ce+=ge*O[P++];if(se*=de,ce*=de,H=(ye*ce-be*se)*xe,U=(ye*se-ve*ce)*xe,z+=H,B+=U,r[q]=z+u,r[ne]=B+u,H*H+U*U<=c)break;if(Q>0&&Math.abs(H+V)<.01&&Math.abs(U+ee)<.01){r[q]-=H*.5,r[ne]-=U*.5;break}V=H,ee=U}}this.cache.put_buffer(C),this.cache.put_buffer(w),this.cache.put_buffer(T)}},{jsfeatNext:k}}); \ No newline at end of file +(function(e,t){typeof exports==`object`&&typeof module<`u`?module.exports=t():typeof define==`function`&&define.amd?define([],t):(e=typeof globalThis<`u`?globalThis:e||self,e.jsfeatNext=t())})(this,function(){var e=class{constructor(){this._data_type_size=new Int32Array([-1,1,4,-1,4,-1,-1,-1,8,-1,-1,-1,-1,-1,-1,-1,8])}_get_data_type(e){return e&65280}_get_channel(e){return e&255}_get_data_type_size(e){return this._data_type_size[(e&65280)>>8]}},t=class{constructor(e,t){this.size=(e+7|0)&-8,t===void 0?this.buffer=new ArrayBuffer(this.size):(this.buffer=t,this.size=t.length),this.u8=new Uint8Array(this.buffer),this.i32=new Int32Array(this.buffer),this.f32=new Float32Array(this.buffer),this.f64=new Float64Array(this.buffer)}},n=class{constructor(e){this.next=null,this.data=new t(e),this.size=this.data.size,this.buffer=this.data.buffer,this.u8=this.data.u8,this.i32=this.data.i32,this.f32=this.data.f32,this.f64=this.data.f64}resize(e){delete this.data,this.data=new t(e),this.size=this.data.size,this.buffer=this.data.buffer,this.u8=this.data.u8,this.i32=this.data.i32,this.f32=this.data.f32,this.f64=this.data.f64}},r=class{constructor(){this._pool_head,this._pool_tail,this._pool_size=0}allocate(e,t){this._pool_head=this._pool_tail=new n(t);for(let r=0;rt.size&&t.resize(e),t}put_buffer(e){this._pool_tail=this._pool_tail.next=e,this._pool_size++}},i={EPSILON:1.192092896e-7,FLT_MIN:1e-37,U8_t:256,S32_t:512,F32_t:1024,S64_t:2048,F64_t:4096,C1_t:1,C2_t:2,C3_t:3,C4_t:4,COLOR_RGBA2GRAY:0,COLOR_RGB2GRAY:1,COLOR_BGRA2GRAY:2,COLOR_BGR2GRAY:3,BOX_BLUR_NOSCALE:1,SVD_U_T:1,SVD_V_T:2,U8C1_t:257,U8C3_t:259,U8C4_t:260,F32C1_t:1025,F32C2_t:1026,S32C1_t:513,S32C2_t:514},a={name:`@webarkit/jsfeat-next`,version:`0.7.6`,description:`Typescript version of jsfeat for WebARKit`,main:`dist/jsfeatNext.js`,module:`dist/jsfeatNext.mjs`,types:`types/src/index.d.ts`,unpkg:`dist/jsfeatNext.js`,jsdelivr:`dist/jsfeatNext.js`,exports:{".":{types:`./types/src/index.d.ts`,import:`./dist/jsfeatNext.mjs`,require:`./dist/jsfeatNext.js`},"./package.json":`./package.json`},files:[`dist`,`types`,`src`],scripts:{"build-ts":`vite build`,"dev-ts":`vite build --watch`,"format-check":`prettier --check .`,format:`prettier --write .`,test:`vitest run`,"test:watch":`vitest`},repository:{type:`git`,url:`git+https://github.com/webarkit/jsfeatNext.git`},keywords:[`jsfeat`,`jsfeatNext`,`WebAR`,`WebARKit`,`AugmentedReality`,`computer`,`vision`],author:`Walter Perdan @kalwalt`,license:`LGPL-3.0-or-later`,bugs:{url:`https://github.com/webarkit/jsfeatNext/issues`},homepage:`https://github.com/webarkit/jsfeatNext#readme`,devDependencies:{prettier:`~3.5.3`,typescript:`^6.0.3`,vite:`^8.1.3`,"vite-plugin-dts":`^5.0.3`,vitest:`^4.1.10`}},o,s=class{constructor(){this.dt=new e,this.cache=new r,this.cache.allocate(30,640*4)}get_data_type(e){return this.dt._get_data_type(e)}get_channel(e){return this.dt._get_channel(e)}get_data_type_size(e){return this.dt._get_data_type_size(e)}};o=s,o.VERSION=a.version,o.EPSILON=i.EPSILON,o.FLT_MIN=i.FLT_MIN,o.U8_t=i.U8_t,o.S32_t=i.S32_t,o.F32_t=i.F32_t,o.S64_t=i.S64_t,o.F64_t=i.F64_t,o.C1_t=i.C1_t,o.C2_t=i.C2_t,o.C3_t=i.C3_t,o.C4_t=i.C4_t,o.COLOR_RGBA2GRAY=i.COLOR_RGBA2GRAY,o.COLOR_RGB2GRAY=i.COLOR_RGB2GRAY,o.COLOR_BGRA2GRAY=i.COLOR_BGRA2GRAY,o.COLOR_BGR2GRAY=i.COLOR_BGR2GRAY,o.BOX_BLUR_NOSCALE=i.BOX_BLUR_NOSCALE,o.SVD_U_T=i.SVD_U_T,o.SVD_V_T=i.SVD_V_T,o.U8C1_t=o.U8_t|o.C1_t,o.U8C3_t=o.U8_t|o.C3_t,o.U8C4_t=o.U8_t|o.C4_t,o.F32C1_t=o.F32_t|o.C1_t,o.F32C2_t=o.F32_t|o.C2_t,o.S32C1_t=o.S32_t|o.C1_t,o.S32C2_t=o.S32_t|o.C2_t;var c=class{constructor(t,n,r,a){this.dt=new e,this.type=this.dt._get_data_type(r)|0,this.channel=this.dt._get_channel(r)|0,this.cols=t|0,this.rows=n|0,a===void 0?this.allocate():(this.buffer=a,this.data=this.type&i.U8_t?this.buffer.u8:this.type&i.S32_t?this.buffer.i32:this.type&i.F32_t?this.buffer.f32:this.buffer.f64)}allocate(){delete this.data,delete this.buffer,this.buffer=new t(this.cols*this.dt._get_data_type_size(this.type)*this.channel*this.rows),this.data=this.type&i.U8_t?this.buffer.u8:this.type&i.S32_t?this.buffer.i32:this.type&i.F32_t?this.buffer.f32:this.buffer.f64}copy_to(e){let t=e.data,n=this.data,r=0,i=this.cols*this.rows*this.channel|0;for(;rthis.buffer.size?(this.cols=e,this.rows=t,this.channel=n,this.allocate()):(this.cols=e,this.rows=t,this.channel=n)}};function l(e,t,n,r,i){let a=0,o=e.channel,s=e.cols,c=e.rows,l=e.data,u=t.data,d=s/r,f=c/i,p=d*f*65536|0,m=0,h=0,g=0,_=0,v=0,y=0,b=0,x=0,S=0,C=0,w=0,T=0,E=0,D=0,O=0,k=0,A=n.get_buffer(r*o<<2),j=n.get_buffer(r*o<<2),M=n.get_buffer(s*2*3<<2),N=A.i32,P=j.i32,F=M.i32;for(;mS&&(F[x++]=m*o|0,F[x++]=(v-1)*o|0,F[x++]=(v-S)*256|0,a++),g=v;g.001&&(a++,F[x++]=m*o|0,F[x++]=y*o|0,F[x++]=(C-y)*256|0)}for(m=0;mS&&(a++,F[x++]=(v-1)*o|0,F[x++]=m*o|0,F[x++]=(v-S)*p),g=v;g.001&&(a++,F[x++]=y*o|0,F[x++]=m*o|0,F[x++]=(C-y)*p)}for(m=0;m>8,255),n[f+l+1]=Math.min(m>>8,255),n[f+l+2]=Math.min(h>>8,255),n[f+l+3]=Math.min(g>>8,255)}for(;l>8,255)}d+=r,f+=r}for(c=0;c>8,255),n[f+r]=Math.min(m>>8,255),n[f+y]=Math.min(h>>8,255),n[f+b]=Math.min(g>>8,255)}for(;l>8,255)}}}function f(e,t,n,r,i,a,o,s){let c=0,l=0,u=0,d=0,f=0,p=0,m=0,h=0,g=0,_=a[0],v=0,y=r<<1,b=r*3,x=r<<2;for(;c>1){case 0:f[0]=1,u=1;break;case 1:f[0]=.25,f[1]=.5,f[2]=.25,u=1;break;case 2:f[0]=.0625,f[1]=.25,f[2]=.375,f[3]=.25,f[4]=.0625,u=1;break;case 3:f[0]=.03125,f[1]=.109375,f[2]=.21875,f[3]=.28125,f[4]=.21875,f[5]=.109375,f[6]=.03125,u=1;break}else for(c=t>0?t:((e-1)*.5-1)*.3+.8,l=-.5/(c*c);a=0;)for(l=E[c<<1],u=E[(c<<1)+1],c--;;)if(f=u-l+1,f<=7){for(m=l+1;m<=u;m++)for(h=m;h>l&&r(e[h],e[h-1]);h--)i=e[h],e[h]=e[h-1],e[h-1]=i;break}else{for(T=0,_=l,y=u,x=l+(f>>1),f>40&&(g=f>>3,S=l,C=l+g,w=l+(g<<1),a=e[S],o=e[C],s=e[w],l=r(a,o)?r(o,s)?C:r(a,s)?w:S:r(s,o)?C:r(a,s)?S:w,S=x-g,C=x,w=x+g,a=e[S],o=e[C],s=e[w],x=r(a,o)?r(o,s)?C:r(a,s)?w:S:r(s,o)?C:r(a,s)?S:w,S=u-(g<<1),C=u-g,w=u,a=e[S],o=e[C],s=e[w],u=r(a,o)?r(o,s)?C:r(a,s)?w:S:r(s,o)?C:r(a,s)?S:w),S=l,C=x,w=u,a=e[S],o=e[C],s=e[w],x=r(a,o)?r(o,s)?C:r(a,s)?w:S:r(s,o)?C:r(a,s)?S:w,x!=_&&(i=e[x],e[x]=e[_],e[_]=i,x=_),l=v=_+1,u=b=y,a=e[x];;){for(;l<=u&&!r(a,e[l]);)r(e[l],a)||(l>v&&(i=e[v],e[v]=e[l],e[l]=i),T=1,v++),l++;for(;l<=u&&!r(e[u],a);)r(a,e[u])||(uu)break;i=e[l],e[l]=e[u],e[u]=i,T=1,l++,u--}if(T==0){for(l=_,u=y,m=l+1;m<=u;m++)for(h=m;h>l&&r(e[h],e[h-1]);h--)i=e[h],e[h]=e[h-1],e[h-1]=i;break}for(f=Math.min(v-_,l-v),p=l-f|0,d=0;d1)p>1?f>p?(++c,E[c<<1]=_,E[(c<<1)+1]=_+f-1,l=y-p+1,u=y):(++c,E[c<<1]=y-p+1,E[(c<<1)+1]=y,l=_,u=_+f-1):(l=_,u=_+f-1);else if(p>1)l=y-p+1,u=y;else break}}median(e,t,n){let r,i=0,a=0,o=0,s=t+n>>1;for(;;){if(n<=t)return e[s];if(n==t+1)return e[t]>e[n]&&(r=e[t],e[t]=e[n],e[n]=r),e[s];for(i=t+n>>1,e[i]>e[n]&&(r=e[i],e[i]=e[n],e[n]=r),e[t]>e[n]&&(r=e[t],e[t]=e[n],e[n]=r),e[i]>e[t]&&(r=e[i],e[i]=e[t],e[t]=r),a=t+1,r=e[i],e[i]=e[a],e[a]=r,o=n;;){do++a;while(e[t]>e[a]);do--o;while(e[o]>e[t]);if(o=s&&(n=o-1)}return 0}},m=class extends s{constructor(){super()}grayscale(e,t,n,r,a){a===void 0&&(a=i.COLOR_RGBA2GRAY);let o=0,s=0,c=0,l=0,u=0,d=0,f=4899,p=9617,m=1868,h=4;(a==i.COLOR_BGRA2GRAY||a==i.COLOR_BGR2GRAY)&&(f=1868,m=4899),(a==i.COLOR_RGB2GRAY||a==i.COLOR_BGR2GRAY)&&(h=3);let g=h<<1,_=h*3|0;r.resize(t,n,1);let v=r.data;for(s=0;s>14,v[d+1]=e[u+h]*f+e[u+h+1]*p+e[u+h+2]*m+8192>>14,v[d+2]=e[u+g]*f+e[u+g+1]*p+e[u+g+2]*m+8192>>14,v[d+3]=e[u+_]*f+e[u+_+1]*p+e[u+_+2]*m+8192>>14;for(;o>14}}resample(e,t,n,r){let a=e.rows,o=e.cols;a>r&&o>n&&(t.resize(n,r,e.channel),e.type&i.U8_t&&t.type&i.U8_t&&a*o/(r*n)<256?l(e,t,this.cache,n,r):u(e,t,this.cache,n,r))}box_blur_gray(e,t,n,r){r===void 0&&(r=0);let a=e.cols,o=e.rows,s=o<<1,c=a<<1,l=0,u=0,d=0,f=0,p=(n<<1)+1|0,m=n+1|0,h=m+1|0,g=r&i.BOX_BLUR_NOSCALE?1:1/(p*p),_=this.cache.get_buffer(a*o<<2),v=0,y=0,b=0,x=0,S=0,C=_.i32,w=e.data,T=0;for(t.resize(a,o,e.channel),d=0;d>1,s=e.cols,c=e.rows,l=e.type,u=l&i.U8_t;t.resize(s,c,e.channel);let m=e.data,h=t.data,g,_,v=n+Math.max(c,s)|0,y=this.cache.get_buffer(v<<2),b=this.cache.get_buffer(n<<2);u?(g=y.i32,_=b.i32):l&i.S32_t?(g=y.i32,_=b.f32):(g=y.f32,_=b.f32),a.get_gaussian_kernel(n,r,_,l),u?d(g,m,h,s,c,_,n,o):f(g,m,h,s,c,_,n,o),this.cache.put_buffer(y),this.cache.put_buffer(b)}hough_transform(e,t,n,r){let i,a,o=e.data,s=e.cols,c=e.rows,l=s,u=Math.round((Math.PI-0)/n),d=Math.round(((s+c)*2+1)/t),f=1/t,p=new Int32Array((u+2)*(d+2)),m=new Float32Array(u),h=new Float32Array(u),g=0,_=0;for(;gr&&p[e]>p[e-1]&&p[e]>=p[e+1]&&p[e]>p[e-d-2]&&p[e]>=p[e+d+2]&&v.push(e)}v.sort(function(e,t){return p[e]>p[t]||p[e]==p[t]&&e>1,s=a>>1,c=o-(n<<1),l=s-(r<<1),u=0,d=0,f=n+r*i,p=0,m=0,h=0;t.resize(o,s,e.channel);let g=e.data,_=t.data;for(d=0;d>2,_[h+1]=g[p+2]+g[p+3]+g[p+i+2]+g[p+i+3]+2>>2;for(;u>2;f+=i<<1,m+=o}}scharr_derivatives(e,t){let n=e.cols,r=e.rows,a=n<<1,o=0,s=0,c=0,l,u,d,f,p,m,h=0,g=0,_=0,v=0,y,b;t.resize(n,r,2);let x=e.data,S=t.data,C=this.cache.get_buffer(n+2<<2),w=this.cache.get_buffer(n+2<<2);for(e.type&i.U8_t||e.type&i.S32_t?(y=C.i32,b=w.i32):(y=C.f32,b=w.f32);s0?s-1:1)*n|0,_=(s0?s-1:1)*n|0,_=(s0;--p)for(u=p+a*s,d=u-s,f=a;f>0;--f,u-=s,d-=s)r[u]+=r[d]+r[d+1]}}equalize_histogram(e,t){let n=e.cols,r=e.rows,i=e.data;t.resize(n,r,e.channel);let a=t.data,o=n*r,s=0,c=0,l,u,d=this.cache.get_buffer(1024);for(l=d.i32;s<256;++s)l[s]=0;for(s=0;sr&&(l=n,n=r,r=l),l=3*(a+2)|0;--l>=0;)T[l]=0;for(l=(o+2)*(a+2)|0;--l>=0;)E[l]=0;for(;u>31)-(g>>31)+((_^_>>31)-(_>>31));for(l=1;l<=o;++l,d+=f){if(l==o)for(u=M+a;--u>=M;)T[u]=0;else for(u=0;u>31)-(g>>31)+((_^_>>31)-(_>>31));for(p=d-f|0,E[P-1]=0,m=0,u=0;un){if(g=O[p],_=O[p+1],v=g^_,g=(g^g>>31)-(g>>31)|0,_=(_^_>>31)-(_>>31)|0,y=g*13573,b=y+(g+g<<15),_<<=15,_T[j+u-1]&&h>=T[j+u+1]){h>r&&!m&&E[P+u-N]!=2?(E[P+u]=2,m=1,D[F++]=P+u):E[P+u]=1;continue}}else if(_>b){if(h>T[A+u]&&h>=T[M+u]){h>r&&!m&&E[P+u-N]!=2?(E[P+u]=2,m=1,D[F++]=P+u):E[P+u]=1;continue}}else if(v=v<0?-1:1,h>T[A+u-v]&&h>T[M+u+v]){h>r&&!m&&E[P+u-N]!=2?(E[P+u]=2,m=1,D[F++]=P+u):E[P+u]=1;continue}}E[P+u]=0,m=0}E[P+a]=0,P+=N,u=A,A=j,j=M,M=u}for(u=P-N-1,l=0;l0;)P=D[--F],P-=N+1,E[P]==1&&(E[P]=2,D[F++]=P),P+=1,E[P]==1&&(E[P]=2,D[F++]=P),P+=1,E[P]==1&&(E[P]=2,D[F++]=P),P+=N,E[P]==1&&(E[P]=2,D[F++]=P),P-=2,E[P]==1&&(E[P]=2,D[F++]=P),P+=N,E[P]==1&&(E[P]=2,D[F++]=P),P+=1,E[P]==1&&(E[P]=2,D[F++]=P),P+=1,E[P]==1&&(E[P]=2,D[F++]=P);for(P=N+1,A=0,l=0;l0&&g>0&&p=0&&m>=0&&p95&&r>40&&i>20&&n>r&&n>i&&n-Math.min(r,i)>15&&Math.abs(n-r)>15?t[o]=255:t[o]=0}};function h(e,t,n,r){r=e[t],e[t]=e[n],e[n]=r}function g(e,t){return e=Math.abs(e),t=Math.abs(t),e>t?(t/=e,e*Math.sqrt(1+t*t)):t>0?(e/=t,t*Math.sqrt(1+e*e)):0}var _=class{constructor(){}identity(e,t){t===void 0&&(t=1);let n=e.data,r=e.rows,i=e.cols,a=i+1|0,o=r*i,s=o;for(;--o>=0;)n[o]=0;for(o=s,s=0;s0){for(d=0,b=Math.abs(e[u]),c=1;c1)for(;v0){for(d=0,b=Math.abs(e[p]),c=1;c>16&256?L:-L,e[d*t+p]=I;for(m=0;m<2;m++)for(f=0;fMath.abs(c[a*s+n])&&(a=r);if(Math.abs(c[a*s+n])=0;n--){for(f=l[n],a=n+1;a=0;s--){for(f=d[s],c=s+1,a=c*l;c=0;)n.data[s]=_.data[s];else n&&this.matmath.transpose(n,_);if(r&&a&i.SVD_V_T)for(s=f*f;--s>=0;)r.data[s]=y.data[s];else r&&this.matmath.transpose(r,y)}else{if(n&&a&i.SVD_U_T)for(s=f*f;--s>=0;)n.data[s]=y.data[s];else n&&this.matmath.transpose(n,y);if(r&&a&i.SVD_V_T)for(s=d*d;--s>=0;)r.data[s]=_.data[s];else r&&this.matmath.transpose(r,_)}this.cache.put_buffer(m),this.cache.put_buffer(h),this.cache.put_buffer(g)}svd_solve(e,t,n){let r=0,a=0,o=0,s=0,l=0,u=e.rows,d=e.cols,f=0,p=0,m=0,h=e.type|i.C1_t,g=this.cache.get_buffer(u*u<<3),_=this.cache.get_buffer(d<<3),v=this.cache.get_buffer(d*d<<3),y=new c(u,u,h,g.data),b=new c(1,d,h,_.data),x=new c(d,d,h,v.data),S=n.data,C=y.data,w=b.data,T=x.data;for(this.svd_decompose(e,b,y,x,0),m=i.EPSILON*w[0]*d;rm){for(o=0,f=0,s=0;op&&(f+=w[s+a]*S[o]/C[a]);x[l]=f}this.cache.put_buffer(h),this.cache.put_buffer(g),this.cache.put_buffer(_)}eigenVV(e,t,n){let r=e.cols,a=r*r,o=e.type|i.C1_t,s=this.cache.get_buffer(r*r<<3),l=this.cache.get_buffer(r<<3),u=new c(r,r,o,s.data),d=new c(1,r,o,l.data);for(;--a>=0;)u.data[a]=e.data[a];if(this.JacobiImpl(u.data,r,d.data,t?t.data:null,r,r),n)for(;--r>=0;)n.data[r]=d.data[r];this.cache.put_buffer(s),this.cache.put_buffer(l)}};function y(e,t,n,r,i){let a=0,o=e[t],s=i,c=0,l=0,u=0;for(;a<25;++a)r[a]=o-e[t+n[a]];for(a=0;a<16;a+=2)c=Math.min(r[a+1],r[a+2]),c=Math.min(c,r[a+3]),!(c<=s)&&(c=Math.min(c,r[a+4]),c=Math.min(c,r[a+5]),c=Math.min(c,r[a+6]),c=Math.min(c,r[a+7]),c=Math.min(c,r[a+8]),s=Math.max(s,Math.min(c,r[a])),s=Math.max(s,Math.min(c,r[a+9])));for(l=-s,a=0;a<16;a+=2)u=Math.max(r[a+1],r[a+2]),u=Math.max(u,r[a+3]),u=Math.max(u,r[a+4]),u=Math.max(u,r[a+5]),!(u>=l)&&(u=Math.max(u,r[a+6]),u=Math.max(u,r[a+7]),u=Math.max(u,r[a+8]),l=Math.min(l,Math.max(u,r[a])),l=Math.min(l,Math.max(u,r[a+9])));return-l-1}var b=class extends s{constructor(){super(),this.offsets16=new Int32Array([0,3,1,3,2,2,3,1,3,0,3,-1,2,-2,1,-3,0,-3,-1,-3,-2,-2,-3,-1,-3,0,-3,1,-2,2,-1,3]),this.threshold_tab=new Uint8Array(512),this._threshold=20,this.pixel_off=new Int32Array(25),this.score_diff=new Int32Array(25)}set_threshold(e){this._threshold=Math.min(Math.max(e,0),255);for(let e=-255;e<=255;++e)this.threshold_tab[e+255]=e<-this._threshold?1:e>this._threshold?2:0;return this._threshold}detect(e,t,n){n===void 0&&(n=3);let r=e.data,i=e.cols,a=e.rows,o=0,s=0,c=0,l=0,u=0,d=0,f=this.cache.get_buffer(3*i),p=this.cache.get_buffer((i+1)*3<<2),m=f.u8,h=p.i32,g=this.pixel_off,_=this.score_diff,v=Math.max(3,n),b=Math.min(a-2,a-n),x=Math.max(3,n),S=Math.min(i-3,i-n),C=0,w=0,T,E=y,D=this.threshold_tab,O=this._threshold,k=0,A=0,j=0,M=0,N=0,P=0,F=0,I=0,L=0,R=0,z=0,B=0;this._cmp_offsets(g,i,16);let V=g[0],ee=g[1],H=g[2],U=g[3],W=g[4],G=g[5],K=g[6],te=g[7],q=g[8],ne=g[9],J=g[10],Y=g[11],X=g[12],Z=g[13],Q=g[14],re=g[15];for(o=0;o8){++M,h[N+M]=s,m[P+s]=E(r,F,g,_,O);break}}else C=0;if(j&2)for(l=k+O,C=0,c=0;c<25;++c)if(u=r[F+g[c]],u>l){if(++C,C>8){++M,h[N+M]=s,m[P+s]=E(r,F,g,_,O);break}}else C=0}}if(h[N+i]=M,o!=v)for(d=(o-4+3)%3,I=d*i|0,N=d*(i+1)|0,d=(o-5+3)%3,L=d*i|0,M=h[N+i],c=0;cm[I+R]&&B>m[I+z]&&B>m[L+z]&&B>m[L+s]&&B>m[L+R]&&B>m[P+z]&&B>m[P+s]&&B>m[P+R]&&(T=t[w],T.x=s,T.y=o-1,T.score=B,w++)}return this.cache.put_buffer(f),this.cache.put_buffer(p),w}_cmp_offsets(e,t,n){let r=0,i=this.offsets16;for(;r=0;)this.data[r]=new c(e>>r,t>>r,n)}build(e,t){t===void 0&&(t=!0);let n=2,r=e,i=this.data[0];if(!t){let t=e.cols*e.rows;for(;--t>=0;)i.data[t]=e.data[t]}for(i=this.data[1],this.pyrdown(r,i);n=0;i--,r++)a=Math.sqrt(n*n-i*i)+.5|0,t[r]=i+e*a;for(;-i=0;a--,r++)i=-Math.sqrt(n*n-a*a)-.5|0,t[r]=i+e*a;for(;a>i;a--,r++)i=-Math.sqrt(n*n-a*a)-.5|0,t[r]=i+e*a;for(i++;i<=0;i++,r++)a=-Math.sqrt(n*n-i*i)-.5|0,t[r]=i+e*a;for(;i<-a;i++,r++)a=-Math.sqrt(n*n-i*i)-.5|0,t[r]=i+e*a;for(a++;a<0;a++,r++)i=Math.sqrt(n*n-a*a)+.5|0,t[r]=i+e*a;return t[r]=t[0],t[r+1]=t[1],r}function O(e,t,n){let r=0;return e[t+1]!=0&&r++,e[t-1]!=0&&r++,e[t+n]!=0&&r++,e[t+n+1]!=0&&r++,e[t+n-1]!=0&&r++,e[t-n]!=0&&r++,e[t-n+1]!=0&&r++,e[t-n-1]!=0&&r++,r}function k(e,t,n,r,i){let a,o;if(n>0)for(t-=r*i,o=-i;o<=i;++o){for(a=-i;a<=i;++a)if(e[t+a]>n)return!1;t+=r}else for(t-=r*i,o=-i;o<=i;++o){for(a=-i;a<=i;++a)if(e[t+a]=r)if(f=e[t+a[u]],f<=i)if(f>=r){n[t]=0;return}else if(u++,p=e[t+a[u]],p>i)if(u++,m=e[t+a[u]],m>i)h=3;else if(mi)h=7;else if(mi)if(u++,m=e[t+a[u]],m>i)h=3;else if(mi)h=7;else if(mi){n[t]=0;return}if(u++,p=e[t+a[u]],p>i){n[t]=0;return}if(u++,m=e[t+a[u]],m>i){n[t]=0;return}h=1}else{if(f=e[t+a[u]],fi){if(p=m,u++,m=e[t+a[u]],mi){n[t]=0;return}if(m>i){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],m>i){n[t]=0;return}c-=d+p,h=8;break}if(p<=i){n[t]=0;return}if(m<=i){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],m>i){c-=d+p,h=3;break}if(mi){n[t]=0;return}c-=d+p,h=1;break}if(d>i){if(p=r){n[t]=0;return}if(m>=r){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],mi){c-=d+p,h=7;break}n[t]=0;return;case 2:if(d>i){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],di){n[t]=0;return}c-=d+p,h=4;break}if(m>i){c-=d+p,h=7;break}if(mi){if(mi){c-=d+p,h=3;break}if(mi){n[t]=0;return}if(di){n[t]=0;return}c-=d+p,h=1;break}if(m>=r){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],mi){c-=d+p,h=7;break}n[t]=0;return;case 5:if(di){if(p=m,u++,m=e[t+a[u]],mi){c-=d+p,h=3;break}if(mi){n[t]=0;return}if(di){c-=d+p,h=3;break}if(mi){n[t]=0;return}if(di){c-=d+p,h=7;break}n[t]=0;return;case 8:if(d>i){if(mi){n[t]=0;return}c-=d+p,h=1;break}n[t]=0;return;case 9:if(di){n[t]=0;return}if(p=m,u++,m=e[t+a[u]],m>i){n[t]=0;return}c-=d+p,h=8;break}if(d>i){if(p=m,u++,m=e[t+a[u]],m>i,t>>i,n)}detect(e,t,n=4){let r=this.level_tables[0],i=r.radius|0,a=i-1|0,o=r.dirs,s=r.dirs_count|0,c=s>>1,l=e.data,u=e.cols|0,d=e.rows|0,f=u>>1,p=r.scores,m=0,h=0,g=0,_=0,v=0,y=0,b=0,x=0,S=this.tau|0,C=0,w,T=Math.max(i+1,n)|0,E=Math.max(i+1,n)|0,D=Math.min(u-i-2,u-n)|0,j=Math.min(d-i-2,d-n)|0;for(g=E*u+T|0,h=E;h=3&&k(p,_,x,f,i)&&(w=t[C],w.x=m,w.y=h,w.score=b,++C,m+=a,_+=a);return C}};function N(e,t,n,r,i,a,o,s,c){let l=0,u=0,d=o*n+a|0,f=d;for(l=o;l=0&&f+i=0?t[f]=-4*e[f]+e[f+r]+e[f-r]+e[f+i]+e[f-i]:t[f]=0}function P(e,t,n,r,i,a,o){let s=-2*e[t]+e[t+r]+e[t-r],c=-2*e[t]+e[t+i]+e[t-i],l=e[t+a]+e[t-a]-e[t+o]-e[t-o],u=Math.sqrt((s-c)*(s-c)+4*l*l)|0;return Math.min(Math.abs(n-u),Math.abs(-(n+u)))}var F=class extends s{constructor(){super(),this.laplacian_threshold=30,this.min_eigen_value_threshold=25}detect(e,t,n){n===void 0&&(n=5);let r=0,i=0,a=e.cols,o=e.rows,s=e.data,c=5*a|0,l=3+3*a|0,u=3-3*a|0,d=this.cache.get_buffer(a*o<<2),f=d.i32,p=0,m=0,h=0,g=0,_,v=0,y=this.laplacian_threshold,b=this.min_eigen_value_threshold,x=Math.max(5,n)|0,S=Math.max(3,n)|0,C=Math.min(a-5,a-n)|0,w=Math.min(o-3,o-n)|0;for(r=a*o;--r>=0;)f[r]=0;for(N(s,f,a,5,c,x,S,C,w),m=S*a+x|0,i=S;iy&&p>f[h-1]&&p>f[h+1]&&p>f[h-a]&&p>f[h+a]&&p>f[h-a-1]&&p>f[h+a-1]&&p>f[h-a+1]&&p>f[h+a+1])&&(g=P(s,h,p,5,c,l,u),g>b&&(_=t[v],_.x=r,_.y=i,_.score=g,++v,++r,++h));return this.cache.put_buffer(d),v}},I=class{constructor(e=0,t=.5,n=.5,r=.99){this.size=e,this.thresh=t,this.eps=n,this.prob=r}update_iters(e,t){let n=Math.log(1-this.prob),r=Math.log(1-Math.pow(1-e,this.size));return(r>=0||-n>=t*-r?t:Math.round(n/r))|0}},L=class extends s{constructor(){super()}get_subset(e,t,n,r,i,a,o){let s=1e3,c=[],l=0,u=0,d=0,f=0,p=!1;for(;d=0;)s.data[a]=1;return this.cache.put_buffer(b),this.cache.put_buffer(x),this.cache.put_buffer(S),!0}for(;fMath.max(T,u-1)&&(C.copy_to(o),T=E,s&&w.copy_to(s),d=e.update_iters((a-E)/a,d),p=!0))}return this.cache.put_buffer(b),this.cache.put_buffer(x),this.cache.put_buffer(S),p}lmeds(e,t,n,r,a,o,s,l){if(l===void 0&&(l=1e3),a=0;)s.data[a]=1;return this.cache.put_buffer(S),this.cache.put_buffer(C),this.cache.put_buffer(w),!0}for(;f=u),this.cache.put_buffer(S),this.cache.put_buffer(C),this.cache.put_buffer(w),m}},R=class extends s{constructor(){super(),this.T0=new c(3,3,i.F32_t|i.C1_t),this.T1=new c(3,3,i.F32_t|i.C1_t),this.AtA=new c(6,6,i.F32_t|i.C1_t),this.AtB=new c(6,1,i.F32_t|i.C1_t)}sqr(e){return e*e}iso_normalize_points(e,t,n,r,i){let a=0,o=0,s=0,c=0,l=0,u=0,d=0,f=0,p=0,m=0,h=0;for(;a=0;)u[a]=0;for(a=0;a=0;--X)for(I=1/(1<>X,S=b>>X,A=x<<1,_=h[X].data,v=g[X].data,re=x-o|0,ie=S-o|0,this.scharr_deriv(h[X],E),Z=0;Z=re||G<=0||G>=ie,J!=0){X==0&&(l[Z]=0);continue}for(ae=L-W,oe=R-G,fe=(1-ae)*(1-oe)*le+.5|0,pe=ae*(1-oe)*le+.5|0,me=(1-ae)*oe*le+.5|0,he=le-fe-pe-me,ve=0,ye=0,be=0,Y=0;Y>9,_e=k[M]*fe+k[M+2]*pe+k[M+A]*me+k[M+A+2]*he,_e=_e+ue>>14,$=k[M+1]*fe+k[M+3]*pe+k[M+A+1]*me+k[M+A+3]*he,$=$+ue>>14,D[N]=ge,O[P++]=_e,O[P++]=$,ve+=_e*_e,ye+=_e*$,be+=$*$;if(ve*=de,ye*=de,be*=de,xe=ve*be-ye*ye,Se=(be+ve-Math.sqrt((ve-be)*(ve-be)+4*ye*ye))/m,Se=re||te<=0||te>=ie,J!=0){X==0&&(l[Z]=0);break}for(ae=z-K,oe=B-te,fe=(1-ae)*(1-oe)*le+.5|0,pe=ae*(1-oe)*le+.5|0,me=(1-ae)*oe*le+.5|0,he=le-fe-pe-me,se=0,ce=0,Y=0;Y>9,ge-=D[N],se+=ge*O[P++],ce+=ge*O[P++];if(se*=de,ce*=de,H=(ye*ce-be*se)*xe,U=(ye*se-ve*ce)*xe,z+=H,B+=U,r[q]=z+f,r[ne]=B+f,H*H+U*U<=u)break;if(Q>0&&Math.abs(H+V)<.01&&Math.abs(U+ee)<.01){r[q]-=H*.5,r[ne]-=U*.5;break}V=H,ee=U}}this.cache.put_buffer(C),this.cache.put_buffer(w),this.cache.put_buffer(T)}},ee=s;return s.cache=r,s.pyramid_t=x,s.transform=S,s.matrix_t=c,s.keypoint_t=C,s.fast_corners=b,s.imgproc=m,s.math=p,s.matmath=_,s.linalg=v,s.orb=E,s.yape=M,s.yape06=F,s.motion_estimator=L,s.ransac_params_t=I,s.affine2d=z,s.homography2d=B,s.optical_flow_lk=V,{jsfeatNext:ee}}); \ No newline at end of file diff --git a/dist/jsfeatNext.mjs b/dist/jsfeatNext.mjs index 217e69a..c0858a8 100644 --- a/dist/jsfeatNext.mjs +++ b/dist/jsfeatNext.mjs @@ -59,10 +59,122 @@ var e = class { put_buffer(e) { this._pool_tail = this._pool_tail.next = e, this._pool_size++; } +}, i = { + EPSILON: 1.192092896e-7, + FLT_MIN: 1e-37, + U8_t: 256, + S32_t: 512, + F32_t: 1024, + S64_t: 2048, + F64_t: 4096, + C1_t: 1, + C2_t: 2, + C3_t: 3, + C4_t: 4, + COLOR_RGBA2GRAY: 0, + COLOR_RGB2GRAY: 1, + COLOR_BGRA2GRAY: 2, + COLOR_BGR2GRAY: 3, + BOX_BLUR_NOSCALE: 1, + SVD_U_T: 1, + SVD_V_T: 2, + U8C1_t: 257, + U8C3_t: 259, + U8C4_t: 260, + F32C1_t: 1025, + F32C2_t: 1026, + S32C1_t: 513, + S32C2_t: 514 +}, a = { + name: "@webarkit/jsfeat-next", + version: "0.7.6", + description: "Typescript version of jsfeat for WebARKit", + main: "dist/jsfeatNext.js", + module: "dist/jsfeatNext.mjs", + types: "types/src/index.d.ts", + unpkg: "dist/jsfeatNext.js", + jsdelivr: "dist/jsfeatNext.js", + exports: { + ".": { + types: "./types/src/index.d.ts", + import: "./dist/jsfeatNext.mjs", + require: "./dist/jsfeatNext.js" + }, + "./package.json": "./package.json" + }, + files: [ + "dist", + "types", + "src" + ], + scripts: { + "build-ts": "vite build", + "dev-ts": "vite build --watch", + "format-check": "prettier --check .", + format: "prettier --write .", + test: "vitest run", + "test:watch": "vitest" + }, + repository: { + type: "git", + url: "git+https://github.com/webarkit/jsfeatNext.git" + }, + keywords: [ + "jsfeat", + "jsfeatNext", + "WebAR", + "WebARKit", + "AugmentedReality", + "computer", + "vision" + ], + author: "Walter Perdan @kalwalt", + license: "LGPL-3.0-or-later", + bugs: { url: "https://github.com/webarkit/jsfeatNext/issues" }, + homepage: "https://github.com/webarkit/jsfeatNext#readme", + devDependencies: { + prettier: "~3.5.3", + typescript: "^6.0.3", + vite: "^8.1.3", + "vite-plugin-dts": "^5.0.3", + vitest: "^4.1.10" + } +}, o, s = class { + constructor() { + this.dt = new e(), this.cache = new r(), this.cache.allocate(30, 640 * 4); + } + get_data_type(e) { + return this.dt._get_data_type(e); + } + get_channel(e) { + return this.dt._get_channel(e); + } + get_data_type_size(e) { + return this.dt._get_data_type_size(e); + } +}; +o = s, o.VERSION = a.version, o.EPSILON = i.EPSILON, o.FLT_MIN = i.FLT_MIN, o.U8_t = i.U8_t, o.S32_t = i.S32_t, o.F32_t = i.F32_t, o.S64_t = i.S64_t, o.F64_t = i.F64_t, o.C1_t = i.C1_t, o.C2_t = i.C2_t, o.C3_t = i.C3_t, o.C4_t = i.C4_t, o.COLOR_RGBA2GRAY = i.COLOR_RGBA2GRAY, o.COLOR_RGB2GRAY = i.COLOR_RGB2GRAY, o.COLOR_BGRA2GRAY = i.COLOR_BGRA2GRAY, o.COLOR_BGR2GRAY = i.COLOR_BGR2GRAY, o.BOX_BLUR_NOSCALE = i.BOX_BLUR_NOSCALE, o.SVD_U_T = i.SVD_U_T, o.SVD_V_T = i.SVD_V_T, o.U8C1_t = o.U8_t | o.C1_t, o.U8C3_t = o.U8_t | o.C3_t, o.U8C4_t = o.U8_t | o.C4_t, o.F32C1_t = o.F32_t | o.C1_t, o.F32C2_t = o.F32_t | o.C2_t, o.S32C1_t = o.S32_t | o.C1_t, o.S32C2_t = o.S32_t | o.C2_t; +//#endregion +//#region src/matrix_t/matrix_t.ts +var c = class { + constructor(t, n, r, a) { + this.dt = new e(), this.type = this.dt._get_data_type(r) | 0, this.channel = this.dt._get_channel(r) | 0, this.cols = t | 0, this.rows = n | 0, a === void 0 ? this.allocate() : (this.buffer = a, this.data = this.type & i.U8_t ? this.buffer.u8 : this.type & i.S32_t ? this.buffer.i32 : this.type & i.F32_t ? this.buffer.f32 : this.buffer.f64); + } + allocate() { + delete this.data, delete this.buffer, this.buffer = new t(this.cols * this.dt._get_data_type_size(this.type) * this.channel * this.rows), this.data = this.type & i.U8_t ? this.buffer.u8 : this.type & i.S32_t ? this.buffer.i32 : this.type & i.F32_t ? this.buffer.f32 : this.buffer.f64; + } + copy_to(e) { + let t = e.data, n = this.data, r = 0, i = this.cols * this.rows * this.channel | 0; + for (; r < i - 4; r += 4) t[r] = n[r], t[r + 1] = n[r + 1], t[r + 2] = n[r + 2], t[r + 3] = n[r + 3]; + for (; r < i; ++r) t[r] = n[r]; + } + resize(e, t, n) { + n === void 0 && (n = this.channel), e * this.dt._get_data_type_size(this.type) * n * t > this.buffer.size ? (this.cols = e, this.rows = t, this.channel = n, this.allocate()) : (this.cols = e, this.rows = t, this.channel = n); + } }; //#endregion //#region src/imgproc/resample.ts -function i(e, t, n, r, i) { +function l(e, t, n, r, i) { let a = 0, o = e.channel, s = e.cols, c = e.rows, l = e.data, u = t.data, d = s / r, f = c / i, p = d * f * 65536 | 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = 0, D = 0, O = 0, k = 0, A = n.get_buffer(r * o << 2), j = n.get_buffer(r * o << 2), M = n.get_buffer(s * 2 * 3 << 2), N = A.i32, P = j.i32, F = M.i32; for (; m < r; m++) { for (S = m * d, C = S + d, v = S + 1 - 1e-6 | 0, y = C | 0, v = Math.min(v, s - 1), y = Math.min(y, s - 1), v > S && (F[x++] = m * o | 0, F[x++] = (v - 1) * o | 0, F[x++] = (v - S) * 256 | 0, a++), g = v; g < y; g++) a++, F[x++] = m * o | 0, F[x++] = g * o | 0, F[x++] = 256; @@ -79,7 +191,7 @@ function i(e, t, n, r, i) { } n.put_buffer(j), n.put_buffer(A), n.put_buffer(M); } -function a(e, t, n, r, i) { +function u(e, t, n, r, i) { let a = 0, o = e.channel, s = e.cols, c = e.rows, l = e.data, u = t.data, d = s / r, f = c / i, p = 1 / (d * f), m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = 0, D = 0, O = 0, k = 0, A = n.get_buffer(r * o << 2), j = n.get_buffer(r * o << 2), M = n.get_buffer(s * 2 * 3 << 2), N = A.f32, P = j.f32, F = M.f32; for (; m < r; m++) { for (S = m * d, C = S + d, v = S + 1 - 1e-6 | 0, y = C | 0, v = Math.min(v, s - 1), y = Math.min(y, s - 1), v > S && (a++, F[x++] = (v - 1) * o | 0, F[x++] = m * o | 0, F[x++] = (v - S) * p), g = v; g < y; g++) a++, F[x++] = g * o | 0, F[x++] = m * o | 0, F[x++] = p; @@ -98,7 +210,7 @@ function a(e, t, n, r, i) { } //#endregion //#region src/imgproc/convol.ts -function o(e, t, n, r, i, a, o, s) { +function d(e, t, n, r, i, a, o, s) { let c = 0, l = 0, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0, g = 0, _ = a[0], v = 0, y = r << 1, b = r * 3, x = r << 2; for (; c < i; ++c) { for (p = t[d], l = 0; l < s; ++l) e[l] = p; @@ -130,7 +242,7 @@ function o(e, t, n, r, i, a, o, s) { } } } -function s(e, t, n, r, i, a, o, s) { +function f(e, t, n, r, i, a, o, s) { let c = 0, l = 0, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0, g = 0, _ = a[0], v = 0, y = r << 1, b = r * 3, x = r << 2; for (; c < i; ++c) { for (p = t[d], l = 0; l < s; ++l) e[l] = p; @@ -163,135 +275,34 @@ function s(e, t, n, r, i, a, o, s) { } } //#endregion -//#region src/linalg/linalg_base.ts -function c(e, t, n, r) { - r = e[t], e[t] = e[n], e[n] = r; -} -function l(e, t) { - return e = Math.abs(e), t = Math.abs(t), e > t ? (t /= e, e * Math.sqrt(1 + t * t)) : t > 0 ? (e /= t, t * Math.sqrt(1 + e * e)) : 0; -} -//#endregion -//#region src/fast_corners/fast_private.ts -function u(e, t, n, r, i) { - let a = 0, o = e[t], s = i, c = 0, l = 0, u = 0; - for (; a < 25; ++a) r[a] = o - e[t + n[a]]; - for (a = 0; a < 16; a += 2) c = Math.min(r[a + 1], r[a + 2]), c = Math.min(c, r[a + 3]), !(c <= s) && (c = Math.min(c, r[a + 4]), c = Math.min(c, r[a + 5]), c = Math.min(c, r[a + 6]), c = Math.min(c, r[a + 7]), c = Math.min(c, r[a + 8]), s = Math.max(s, Math.min(c, r[a])), s = Math.max(s, Math.min(c, r[a + 9]))); - for (l = -s, a = 0; a < 16; a += 2) u = Math.max(r[a + 1], r[a + 2]), u = Math.max(u, r[a + 3]), u = Math.max(u, r[a + 4]), u = Math.max(u, r[a + 5]), !(u >= l) && (u = Math.max(u, r[a + 6]), u = Math.max(u, r[a + 7]), u = Math.max(u, r[a + 8]), l = Math.min(l, Math.max(u, r[a])), l = Math.min(l, Math.max(u, r[a + 9]))); - return -l - 1; -} -//#endregion -//#region src/matmath/matmath.ts -var d = class { - constructor() {} - identity(e, t) { - t === void 0 && (t = 1); - let n = e.data, r = e.rows, i = e.cols, a = i + 1 | 0, o = r * i, s = o; - for (; --o >= 0;) n[o] = 0; - for (o = s, s = 0; s < o;) n[s] = t, s += a; - } - transpose(e, t) { - let n = 0, r = 0, i = t.rows, a = t.cols, o = 0, s = 0, c = 0, l = t.data, u = e.data; - for (; n < i; s += 1, o += a, n++) for (c = s, r = 0; r < a; c += i, r++) u[c] = l[o + r]; - } - multiply(e, t, n) { - let r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = t.cols, f = t.rows, p = n.cols, m = t.data, h = n.data, g = e.data, _ = 0; - for (; r < f; o += d, r++) for (l = 0, i = 0; i < p; u++, l++, i++) { - for (c = l, s = o, _ = 0, a = 0; a < d; s++, c += p, a++) _ += m[s] * h[c]; - g[u] = _; - } - } - multiply_ABt(e, t, n) { - let r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = t.cols, d = t.rows, f = n.rows, p = t.data, m = n.data, h = e.data, g = 0; - for (; r < d; o += u, r++) for (c = 0, i = 0; i < f; l++, i++) { - for (s = o, g = 0, a = 0; a < u; s++, c++, a++) g += p[s] * m[c]; - h[l] = g; - } - } - multiply_AtB(e, t, n) { - let r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = t.cols, f = t.rows, p = n.cols, m = t.data, h = n.data, g = e.data, _ = 0; - for (; r < d; o++, r++) for (l = 0, i = 0; i < p; u++, l++, i++) { - for (c = l, s = o, _ = 0, a = 0; a < f; s += d, c += p, a++) _ += m[s] * h[c]; - g[u] = _; - } - } - multiply_AAt(e, t) { - let n = 0, r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = t.cols, f = t.rows, p = t.data, m = e.data, h = 0; - for (; n < f; a += f + 1, o = s, n++) for (l = a, u = a, c = o, r = n; r < f; l++, u += f, r++) { - for (s = o, h = 0, i = 0; i < d; i++) h += p[s++] * p[c++]; - m[l] = h, m[u] = h; - } +//#region src/math/math.ts +var p = class extends s { + constructor() { + super(), this.qsort_stack = /* @__PURE__ */ new Int32Array(96); } - multiply_AtA(e, t) { - let n = 0, r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = t.cols, f = t.rows, p = t.data, m = e.data, h = 0; - for (; n < d; c += d, n++) for (a = n, u = c + n, l = u, r = n; r < d; l++, u += d, r++) { - for (o = a, s = r, h = 0, i = 0; i < f; o += d, s += d, i++) h += p[o] * p[s]; - m[l] = h, m[u] = h; + get_gaussian_kernel(e, t, n, r) { + let a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = this.cache.get_buffer(e << 2), f = d.f32; + if ((e & 1) == 1 && e <= 7 && t <= 0) switch (e >> 1) { + case 0: + f[0] = 1, u = 1; + break; + case 1: + f[0] = .25, f[1] = .5, f[2] = .25, u = 1; + break; + case 2: + f[0] = .0625, f[1] = .25, f[2] = .375, f[3] = .25, f[4] = .0625, u = 1; + break; + case 3: + f[0] = .03125, f[1] = .109375, f[2] = .21875, f[3] = .28125, f[4] = .21875, f[5] = .109375, f[6] = .03125, u = 1; + break; } + else for (c = t > 0 ? t : ((e - 1) * .5 - 1) * .3 + .8, l = -.5 / (c * c); a < e; ++a) o = a - (e - 1) * .5, s = Math.exp(l * o * o), f[a] = s, u += s; + if (r & i.U8_t) for (u = 256 / u, a = 0; a < e; ++a) n[a] = f[a] * u + .5 | 0; + else for (u = 1 / u, a = 0; a < e; ++a) n[a] = f[a] * u; + this.cache.put_buffer(d); } - identity_3x3(e, t) { - t === void 0 && (t = 1); - let n = e.data; - n[0] = n[4] = n[8] = t, n[1] = n[2] = n[3] = 0, n[5] = n[6] = n[7] = 0; - } - invert_3x3(e, t) { - let n = e.data, r = t.data, i = n[4], a = n[8], o = n[5], s = n[7], c = n[0], l = c * i, u = c * o, d = n[3], f = n[1], p = d * f, m = n[2], h = d * m, g = n[6], _ = g * f, v = g * m, y = 1 / (l * a - u * s - p * a + h * s + _ * o - v * i); - r[0] = (i * a - o * s) * y, r[1] = -(f * a - m * s) * y, r[2] = -(-f * o + m * i) * y, r[3] = -(d * a - o * g) * y, r[4] = (c * a - v) * y, r[5] = -(u - h) * y, r[6] = -(-d * s + i * g) * y, r[7] = -(c * s - _) * y, r[8] = (l - p) * y; - } - multiply_3x3(e, t, n) { - let r = e.data, i = t.data, a = n.data, o = i[0], s = i[1], c = i[2], l = i[3], u = i[4], d = i[5], f = i[6], p = i[7], m = i[8], h = a[0], g = a[1], _ = a[2], v = a[3], y = a[4], b = a[5], x = a[6], S = a[7], C = a[8]; - r[0] = o * h + s * v + c * x, r[1] = o * g + s * y + c * S, r[2] = o * _ + s * b + c * C, r[3] = l * h + u * v + d * x, r[4] = l * g + u * y + d * S, r[5] = l * _ + u * b + d * C, r[6] = f * h + p * v + m * x, r[7] = f * g + p * y + m * S, r[8] = f * _ + p * b + m * C; - } - mat3x3_determinant(e) { - let t = e.data; - return t[0] * t[4] * t[8] - t[0] * t[5] * t[7] - t[3] * t[1] * t[8] + t[3] * t[2] * t[7] + t[6] * t[1] * t[5] - t[6] * t[2] * t[4]; - } - determinant_3x3(e, t, n, r, i, a, o, s, c) { - return e * i * c - e * a * s - r * t * c + r * n * s + o * t * a - o * n * i; - } -}, f = { - EPSILON: 1.192092896e-7, - FLT_MIN: 1e-37, - U8_t: 256, - S32_t: 512, - F32_t: 1024, - S64_t: 2048, - F64_t: 4096, - C1_t: 1, - C2_t: 2, - C3_t: 3, - C4_t: 4, - COLOR_RGBA2GRAY: 0, - COLOR_RGB2GRAY: 1, - COLOR_BGRA2GRAY: 2, - COLOR_BGR2GRAY: 3, - BOX_BLUR_NOSCALE: 1, - SVD_U_T: 1, - SVD_V_T: 2, - U8C1_t: 257, - U8C3_t: 259, - U8C4_t: 260, - F32C1_t: 1025, - F32C2_t: 1026, - S32C1_t: 513, - S32C2_t: 514 -}, p = class { - constructor(t, n, r, i) { - this.dt = new e(), this.type = this.dt._get_data_type(r) | 0, this.channel = this.dt._get_channel(r) | 0, this.cols = t | 0, this.rows = n | 0, i === void 0 ? this.allocate() : (this.buffer = i, this.data = this.type & f.U8_t ? this.buffer.u8 : this.type & f.S32_t ? this.buffer.i32 : this.type & f.F32_t ? this.buffer.f32 : this.buffer.f64); - } - allocate() { - delete this.data, delete this.buffer, this.buffer = new t(this.cols * this.dt._get_data_type_size(this.type) * this.channel * this.rows), this.data = this.type & f.U8_t ? this.buffer.u8 : this.type & f.S32_t ? this.buffer.i32 : this.type & f.F32_t ? this.buffer.f32 : this.buffer.f64; - } - copy_to(e) { - let t = e.data, n = this.data, r = 0, i = this.cols * this.rows * this.channel | 0; - for (; r < i - 4; r += 4) t[r] = n[r], t[r + 1] = n[r + 1], t[r + 2] = n[r + 2], t[r + 3] = n[r + 3]; - for (; r < i; ++r) t[r] = n[r]; - } - resize(e, t, n) { - n === void 0 && (n = this.channel), e * this.dt._get_data_type_size(this.type) * n * t > this.buffer.size ? (this.cols = e, this.rows = t, this.channel = n, this.allocate()) : (this.cols = e, this.rows = t, this.channel = n); - } -}, m = class { - constructor() {} perspective_4point_transform(e, t, n, r, i, a, o, s, c, l, u, d, f, p, m, h, g) { + console.warn("⚠️⚠️⚠️ This method is deprecated ad will be removed in the next releases, use transform.perspective_4point_transform() instead. ⚠️⚠️⚠️"); let _ = t, v = l, y = o, b = _ * v * y, x = m, S = _ * x, C = v * S, w = u, T = _ * w, E = a, D = n, O = p, k = D * O, A = k * E, j = O * E * w, M = O * y, N = O * w, P = v * y, F = x * v, I = x * E, L = w * E, R = 1 / (M - N - P + F - I + L), z = _ * O, B = D * E, V = y * _, ee = x * V, H = D * v, U = k * w, W = D * w * E, G = y * x * v, K = x * D, te = -(C - b + T * E - E * S - k * v + A - j + M * v) * R, q = (b - C - z * y + z * w + A - v * B + I * v - j) * R, ne = _, J = (-w * S + ee + H * y - k * y + U - W + I * w - G) * R, Y = (-ee + V * w - K * v + U - W + K * E + G - M * w) * R, X = D, Z = (-T + V + H - B + N - M - F + I) * R, Q = (-S + T + k - H + I - L - M + P) * R; _ = r, v = d, y = c, b = _ * v * y, x = g, S = _ * x, C = v * S, w = f, T = _ * w, E = s, D = i, O = h, k = D * O, A = k * E, j = O * E * w, M = O * y, N = O * w, P = v * y, F = x * v, I = x * E, L = w * E, R = 1 / (M - N - P + F - I + L), z = _ * O, B = D * E, V = y * _, ee = x * V, H = D * v, U = k * w, W = D * w * E, G = y * x * v, K = x * D; let re = -(C - b + T * E - E * S - k * v + A - j + M * v) * R, ie = (b - C - z * y + z * w + A - v * B + I * v - j) * R, ae = _, oe = (-w * S + ee + H * y - k * y + U - W + I * w - G) * R, se = (-ee + V * w - K * v + U - W + K * E + G - M * w) * R, ce = D, le = (-T + V + H - B + N - M - F + I) * R, ue = (-S + T + k - H + I - L - M + P) * R; @@ -307,30 +318,667 @@ var d = class { let ge = L * O, _e = V * O, $ = e.data; $[0] = re * W + A * O * ie - fe * O * ae, $[1] = re * ge + pe * O * ie - B * O * ae, $[2] = -re * _e - me * O * ie + he * O * ae, $[3] = oe * W + A * O * se - fe * O * ce, $[4] = oe * ge + pe * O * se - B * O * ce, $[5] = -oe * _e - me * O * se + he * O * ce, $[6] = le * W + A * O * ue - fe * O, $[7] = le * ge + pe * O * ue - B * O, $[8] = -le * _e - me * O * ue + he * O; } - invert_affine_transform(e, t) { - let n = e.data, r = t.data, i = n[0], a = n[1], o = n[2], s = n[3], c = n[4], l = n[5], u = 1 / (i * c - a * s); - r[0] = u * c, r[1] = u * -a, r[2] = u * (a * l - o * c), r[3] = u * -s, r[4] = u * i, r[5] = u * (o * s - i * l); - } - invert_perspective_transform(e, t) { - let n = e.data, r = t.data, i = n[0], a = n[1], o = n[2], s = n[3], c = n[4], l = n[5], u = n[6], d = n[7], f = n[8], p = 1 / (i * (c * f - l * d) - a * (s * f - l * u) + o * (s * d - c * u)); - r[0] = p * (c * f - l * d), r[1] = p * (o * d - a * f), r[2] = p * (a * l - o * c), r[3] = p * (l * u - s * f), r[4] = p * (i * f - o * u), r[5] = p * (o * s - i * l), r[6] = p * (s * d - c * u), r[7] = p * (a * u - i * d), r[8] = p * (i * c - a * s); - } -}, h = class { - constructor(e = 0, t = 0, n = 0, r = 0, i = -1) { - this.x = e, this.y = t, this.score = n, this.level = r, this.angle = i; - } -}, g = [ - 8, - -3, - 9, - 5, - 4, - 2, - 7, - -12, - -11, - 9, - -8, + qsort(e, t, n, r) { + let i, a, o, s, c = 0, l = 0, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = this.qsort_stack; + if (!(n - t + 1 <= 1)) for (E[0] = t, E[1] = n; c >= 0;) for (l = E[c << 1], u = E[(c << 1) + 1], c--;;) if (f = u - l + 1, f <= 7) { + for (m = l + 1; m <= u; m++) for (h = m; h > l && r(e[h], e[h - 1]); h--) i = e[h], e[h] = e[h - 1], e[h - 1] = i; + break; + } else { + for (T = 0, _ = l, y = u, x = l + (f >> 1), f > 40 && (g = f >> 3, S = l, C = l + g, w = l + (g << 1), a = e[S], o = e[C], s = e[w], l = r(a, o) ? r(o, s) ? C : r(a, s) ? w : S : r(s, o) ? C : r(a, s) ? S : w, S = x - g, C = x, w = x + g, a = e[S], o = e[C], s = e[w], x = r(a, o) ? r(o, s) ? C : r(a, s) ? w : S : r(s, o) ? C : r(a, s) ? S : w, S = u - (g << 1), C = u - g, w = u, a = e[S], o = e[C], s = e[w], u = r(a, o) ? r(o, s) ? C : r(a, s) ? w : S : r(s, o) ? C : r(a, s) ? S : w), S = l, C = x, w = u, a = e[S], o = e[C], s = e[w], x = r(a, o) ? r(o, s) ? C : r(a, s) ? w : S : r(s, o) ? C : r(a, s) ? S : w, x != _ && (i = e[x], e[x] = e[_], e[_] = i, x = _), l = v = _ + 1, u = b = y, a = e[x];;) { + for (; l <= u && !r(a, e[l]);) r(e[l], a) || (l > v && (i = e[v], e[v] = e[l], e[l] = i), T = 1, v++), l++; + for (; l <= u && !r(e[u], a);) r(a, e[u]) || (u < b && (i = e[b], e[b] = e[u], e[u] = i), T = 1, b--), u--; + if (l > u) break; + i = e[l], e[l] = e[u], e[u] = i, T = 1, l++, u--; + } + if (T == 0) { + for (l = _, u = y, m = l + 1; m <= u; m++) for (h = m; h > l && r(e[h], e[h - 1]); h--) i = e[h], e[h] = e[h - 1], e[h - 1] = i; + break; + } + for (f = Math.min(v - _, l - v), p = l - f | 0, d = 0; d < f; ++d, ++p) i = e[_ + d], e[_ + d] = e[p], e[p] = i; + for (f = Math.min(y - b, b - u), p = y - f + 1 | 0, d = 0; d < f; ++d, ++p) i = e[l + d], e[l + d] = e[p], e[p] = i; + if (f = l - v, p = b - u, f > 1) p > 1 ? f > p ? (++c, E[c << 1] = _, E[(c << 1) + 1] = _ + f - 1, l = y - p + 1, u = y) : (++c, E[c << 1] = y - p + 1, E[(c << 1) + 1] = y, l = _, u = _ + f - 1) : (l = _, u = _ + f - 1); + else if (p > 1) l = y - p + 1, u = y; + else break; + } + } + median(e, t, n) { + let r, i = 0, a = 0, o = 0, s = t + n >> 1; + for (;;) { + if (n <= t) return e[s]; + if (n == t + 1) return e[t] > e[n] && (r = e[t], e[t] = e[n], e[n] = r), e[s]; + for (i = t + n >> 1, e[i] > e[n] && (r = e[i], e[i] = e[n], e[n] = r), e[t] > e[n] && (r = e[t], e[t] = e[n], e[n] = r), e[i] > e[t] && (r = e[i], e[i] = e[t], e[t] = r), a = t + 1, r = e[i], e[i] = e[a], e[a] = r, o = n;;) { + do + ++a; + while (e[t] > e[a]); + do + --o; + while (e[o] > e[t]); + if (o < a) break; + r = e[a], e[a] = e[o], e[o] = r; + } + r = e[t], e[t] = e[o], e[o] = r, o <= s ? t = a : o >= s && (n = o - 1); + } + return 0; + } +}, m = class extends s { + constructor() { + super(); + } + grayscale(e, t, n, r, a) { + a === void 0 && (a = i.COLOR_RGBA2GRAY); + let o = 0, s = 0, c = 0, l = 0, u = 0, d = 0, f = 4899, p = 9617, m = 1868, h = 4; + (a == i.COLOR_BGRA2GRAY || a == i.COLOR_BGR2GRAY) && (f = 1868, m = 4899), (a == i.COLOR_RGB2GRAY || a == i.COLOR_BGR2GRAY) && (h = 3); + let g = h << 1, _ = h * 3 | 0; + r.resize(t, n, 1); + let v = r.data; + for (s = 0; s < n; ++s, l += t, c += t * h) { + for (o = 0, u = c, d = l; o <= t - 4; o += 4, u += h << 2, d += 4) v[d] = e[u] * f + e[u + 1] * p + e[u + 2] * m + 8192 >> 14, v[d + 1] = e[u + h] * f + e[u + h + 1] * p + e[u + h + 2] * m + 8192 >> 14, v[d + 2] = e[u + g] * f + e[u + g + 1] * p + e[u + g + 2] * m + 8192 >> 14, v[d + 3] = e[u + _] * f + e[u + _ + 1] * p + e[u + _ + 2] * m + 8192 >> 14; + for (; o < t; ++o, ++d, u += h) v[d] = e[u] * f + e[u + 1] * p + e[u + 2] * m + 8192 >> 14; + } + } + resample(e, t, n, r) { + let a = e.rows, o = e.cols; + a > r && o > n && (t.resize(n, r, e.channel), e.type & i.U8_t && t.type & i.U8_t && a * o / (r * n) < 256 ? l(e, t, this.cache, n, r) : u(e, t, this.cache, n, r)); + } + box_blur_gray(e, t, n, r) { + r === void 0 && (r = 0); + let a = e.cols, o = e.rows, s = o << 1, c = a << 1, l = 0, u = 0, d = 0, f = 0, p = (n << 1) + 1 | 0, m = n + 1 | 0, h = m + 1 | 0, g = r & i.BOX_BLUR_NOSCALE ? 1 : 1 / (p * p), _ = this.cache.get_buffer(a * o << 2), v = 0, y = 0, b = 0, x = 0, S = 0, C = _.i32, w = e.data, T = 0; + for (t.resize(a, o, e.channel), d = 0; d < o; ++d) { + for (y = d, v = m * w[b], l = b + 1 | 0, f = b + n | 0; l <= f; ++l) v += w[l]; + for (x = b + m | 0, S = b, T = w[S], u = 0; u < n; ++u, y += o) C[y] = v, v += w[x] - T, x++; + for (; u < a - h; u += 2, y += s) C[y] = v, v += w[x] - w[S], C[y + o] = v, v += w[x + 1] - w[S + 1], x += 2, S += 2; + for (; u < a - m; ++u, y += o) C[y] = v, v += w[x] - w[S], x++, S++; + for (T = w[x - 1]; u < a; ++u, y += o) C[y] = v, v += T - w[S], S++; + b += a; + } + if (b = 0, w = t.data, g == 1) for (d = 0; d < a; ++d) { + for (y = d, v = m * C[b], l = b + 1 | 0, f = b + n | 0; l <= f; ++l) v += C[l]; + for (x = b + m, S = b, T = C[S], u = 0; u < n; ++u, y += a) w[y] = v, v += C[x] - T, x++; + for (; u < o - h; u += 2, y += c) w[y] = v, v += C[x] - C[S], w[y + a] = v, v += C[x + 1] - C[S + 1], x += 2, S += 2; + for (; u < o - m; ++u, y += a) w[y] = v, v += C[x] - C[S], x++, S++; + for (T = C[x - 1]; u < o; ++u, y += a) w[y] = v, v += T - C[S], S++; + b += o; + } + else for (d = 0; d < a; ++d) { + for (y = d, v = m * C[b], l = b + 1 | 0, f = b + n | 0; l <= f; ++l) v += C[l]; + for (x = b + m, S = b, T = C[S], u = 0; u < n; ++u, y += a) w[y] = v * g, v += C[x] - T, x++; + for (; u < o - h; u += 2, y += c) w[y] = v * g, v += C[x] - C[S], w[y + a] = v * g, v += C[x + 1] - C[S + 1], x += 2, S += 2; + for (; u < o - m; ++u, y += a) w[y] = v * g, v += C[x] - C[S], x++, S++; + for (T = C[x - 1]; u < o; ++u, y += a) w[y] = v * g, v += T - C[S], S++; + b += o; + } + this.cache.put_buffer(_); + } + gaussian_blur(e, t, n, r) { + let a = new p(); + r === void 0 && (r = 0), n === void 0 && (n = 0), n = n == 0 ? Math.max(1, 4 * r + 1 - 1e-8) * 2 + 1 | 0 : n; + let o = n >> 1, s = e.cols, c = e.rows, l = e.type, u = l & i.U8_t; + t.resize(s, c, e.channel); + let m = e.data, h = t.data, g, _, v = n + Math.max(c, s) | 0, y = this.cache.get_buffer(v << 2), b = this.cache.get_buffer(n << 2); + u ? (g = y.i32, _ = b.i32) : l & i.S32_t ? (g = y.i32, _ = b.f32) : (g = y.f32, _ = b.f32), a.get_gaussian_kernel(n, r, _, l), u ? d(g, m, h, s, c, _, n, o) : f(g, m, h, s, c, _, n, o), this.cache.put_buffer(y), this.cache.put_buffer(b); + } + hough_transform(e, t, n, r) { + let i, a, o = e.data, s = e.cols, c = e.rows, l = s, u = Math.round((Math.PI - 0) / n), d = Math.round(((s + c) * 2 + 1) / t), f = 1 / t, p = new Int32Array((u + 2) * (d + 2)), m = new Float32Array(u), h = new Float32Array(u), g = 0, _ = 0; + for (; g < u; g++) m[g] = Math.sin(_) * f, h[g] = Math.cos(_) * f, _ += n; + for (a = 0; a < c; a++) for (let e = 0; e < s; e++) if (o[a * l + e] != 0) for (g = 0; g < u; g++) i = Math.round(e * h[g] + a * m[g]), i += (d - 1) / 2, p[(g + 1) * (d + 2) + i + 1] += 1; + let v = []; + for (i = 0; i < d; i++) for (g = 0; g < u; g++) { + let e = (g + 1) * (d + 2) + i + 1; + p[e] > r && p[e] > p[e - 1] && p[e] >= p[e + 1] && p[e] > p[e - d - 2] && p[e] >= p[e + d + 2] && v.push(e); + } + v.sort(function(e, t) { + return p[e] > p[t] || p[e] == p[t] && e < t; + }); + let y = Math.min(u * d, v.length), b = 1 / (d + 2), x = []; + for (a = 0; a < y; a++) { + let e = v[a]; + g = Math.floor(e * b) - 1, i = e - (g + 1) * (d + 2) - 1; + let r = (i - (d - 1) * .5) * t, o = g * n; + x.push([r, o]); + } + return x; + } + pyrdown(e, t, n, r) { + n === void 0 && (n = 0), r === void 0 && (r = 0); + let i = e.cols, a = e.rows, o = i >> 1, s = a >> 1, c = o - (n << 1), l = s - (r << 1), u = 0, d = 0, f = n + r * i, p = 0, m = 0, h = 0; + t.resize(o, s, e.channel); + let g = e.data, _ = t.data; + for (d = 0; d < l; ++d) { + for (p = f, h = m, u = 0; u <= c - 2; u += 2, h += 2, p += 4) _[h] = g[p] + g[p + 1] + g[p + i] + g[p + i + 1] + 2 >> 2, _[h + 1] = g[p + 2] + g[p + 3] + g[p + i + 2] + g[p + i + 3] + 2 >> 2; + for (; u < c; ++u, ++h, p += 2) _[h] = g[p] + g[p + 1] + g[p + i] + g[p + i + 1] + 2 >> 2; + f += i << 1, m += o; + } + } + scharr_derivatives(e, t) { + let n = e.cols, r = e.rows, a = n << 1, o = 0, s = 0, c = 0, l, u, d, f, p, m, h = 0, g = 0, _ = 0, v = 0, y, b; + t.resize(n, r, 2); + let x = e.data, S = t.data, C = this.cache.get_buffer(n + 2 << 2), w = this.cache.get_buffer(n + 2 << 2); + for (e.type & i.U8_t || e.type & i.S32_t ? (y = C.i32, b = w.i32) : (y = C.f32, b = w.f32); s < r; ++s, g += n) { + for (h = (s > 0 ? s - 1 : 1) * n | 0, _ = (s < r - 1 ? s + 1 : r - 2) * n | 0, v = s * a | 0, o = 0, c = 1; o <= n - 2; o += 2, c += 2) l = x[h + o], u = x[_ + o], y[c] = (l + u) * 3 + x[g + o] * 10, b[c] = u - l, l = x[h + o + 1], u = x[_ + o + 1], y[c + 1] = (l + u) * 3 + x[g + o + 1] * 10, b[c + 1] = u - l; + for (; o < n; ++o, ++c) l = x[h + o], u = x[_ + o], y[c] = (l + u) * 3 + x[g + o] * 10, b[c] = u - l; + for (o = n + 1 | 0, y[0] = y[1], y[o] = y[n], b[0] = b[1], b[o] = b[n], o = 0; o <= n - 4; o += 4) l = b[o + 2], u = b[o + 1], d = b[o + 3], f = b[o + 4], p = y[o + 2], m = y[o + 3], S[v++] = p - y[o], S[v++] = (l + b[o]) * 3 + u * 10, S[v++] = m - y[o + 1], S[v++] = (d + u) * 3 + l * 10, S[v++] = y[o + 4] - p, S[v++] = (f + l) * 3 + d * 10, S[v++] = y[o + 5] - m, S[v++] = (b[o + 5] + d) * 3 + f * 10; + for (; o < n; ++o) S[v++] = y[o + 2] - y[o], S[v++] = (b[o + 2] + b[o]) * 3 + b[o + 1] * 10; + } + this.cache.put_buffer(C), this.cache.put_buffer(w); + } + sobel_derivatives(e, t) { + let n = e.cols, r = e.rows, a = n << 1, o = 0, s = 0, c = 0, l, u, d, f, p, m, h = 0, g = 0, _ = 0, v = 0, y, b; + t.resize(n, r, 2); + let x = e.data, S = t.data, C = this.cache.get_buffer(n + 2 << 2), w = this.cache.get_buffer(n + 2 << 2); + for (e.type & i.U8_t || e.type & i.S32_t ? (y = C.i32, b = w.i32) : (y = C.f32, b = w.f32); s < r; ++s, g += n) { + for (h = (s > 0 ? s - 1 : 1) * n | 0, _ = (s < r - 1 ? s + 1 : r - 2) * n | 0, v = s * a | 0, o = 0, c = 1; o <= n - 2; o += 2, c += 2) l = x[h + o], u = x[_ + o], y[c] = l + u + x[g + o] * 2, b[c] = u - l, l = x[h + o + 1], u = x[_ + o + 1], y[c + 1] = l + u + x[g + o + 1] * 2, b[c + 1] = u - l; + for (; o < n; ++o, ++c) l = x[h + o], u = x[_ + o], y[c] = l + u + x[g + o] * 2, b[c] = u - l; + for (o = n + 1 | 0, y[0] = y[1], y[o] = y[n], b[0] = b[1], b[o] = b[n], o = 0; o <= n - 4; o += 4) l = b[o + 2], u = b[o + 1], d = b[o + 3], f = b[o + 4], p = y[o + 2], m = y[o + 3], S[v++] = p - y[o], S[v++] = l + b[o] + u * 2, S[v++] = m - y[o + 1], S[v++] = d + u + l * 2, S[v++] = y[o + 4] - p, S[v++] = f + l + d * 2, S[v++] = y[o + 5] - m, S[v++] = b[o + 5] + d + f * 2; + for (; o < n; ++o) S[v++] = y[o + 2] - y[o], S[v++] = b[o + 2] + b[o] + b[o + 1] * 2; + } + this.cache.put_buffer(C), this.cache.put_buffer(w); + } + compute_integral_image(e, t, n, r) { + let i = e.cols | 0, a = e.rows | 0, o = e.data, s = i + 1 | 0, c = 0, l = 0, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0; + if (t && n) { + for (; f < s; ++f) t[f] = 0, n[f] = 0; + for (u = s + 1 | 0, d = 1, f = 0, h = 0; f < a; ++f, ++u, ++d) { + for (c = l = 0, p = 0; p <= i - 2; p += 2, h += 2, u += 2, d += 2) m = o[h], c += m, l += m * m, t[u] = t[d] + c, n[u] = n[d] + l, m = o[h + 1], c += m, l += m * m, t[u + 1] = t[d + 1] + c, n[u + 1] = n[d + 1] + l; + for (; p < i; ++p, ++h, ++u, ++d) m = o[h], c += m, l += m * m, t[u] = t[d] + c, n[u] = n[d] + l; + } + } else if (t) { + for (; f < s; ++f) t[f] = 0; + for (u = s + 1 | 0, d = 1, f = 0, h = 0; f < a; ++f, ++u, ++d) { + for (c = 0, p = 0; p <= i - 2; p += 2, h += 2, u += 2, d += 2) c += o[h], t[u] = t[d] + c, c += o[h + 1], t[u + 1] = t[d + 1] + c; + for (; p < i; ++p, ++h, ++u, ++d) c += o[h], t[u] = t[d] + c; + } + } else if (n) { + for (; f < s; ++f) n[f] = 0; + for (u = s + 1 | 0, d = 1, f = 0, h = 0; f < a; ++f, ++u, ++d) { + for (l = 0, p = 0; p <= i - 2; p += 2, h += 2, u += 2, d += 2) m = o[h], l += m * m, n[u] = n[d] + l, m = o[h + 1], l += m * m, n[u + 1] = n[d + 1] + l; + for (; p < i; ++p, ++h, ++u, ++d) m = o[h], l += m * m, n[u] = n[d] + l; + } + } + if (r) { + for (f = 0; f < s; ++f) r[f] = 0; + for (u = s + 1 | 0, d = 0, f = 0, h = 0; f < a; ++f, ++u, ++d) { + for (p = 0; p <= i - 2; p += 2, h += 2, u += 2, d += 2) r[u] = o[h] + r[d], r[u + 1] = o[h + 1] + r[d + 1]; + for (; p < i; ++p, ++h, ++u, ++d) r[u] = o[h] + r[d]; + } + for (u = s + i | 0, d = i, f = 0; f < a; ++f, u += s, d += s) r[u] += r[d]; + for (p = i - 1; p > 0; --p) for (u = p + a * s, d = u - s, f = a; f > 0; --f, u -= s, d -= s) r[u] += r[d] + r[d + 1]; + } + } + equalize_histogram(e, t) { + let n = e.cols, r = e.rows, i = e.data; + t.resize(n, r, e.channel); + let a = t.data, o = n * r, s = 0, c = 0, l, u, d = this.cache.get_buffer(1024); + for (l = d.i32; s < 256; ++s) l[s] = 0; + for (s = 0; s < o; ++s) ++l[i[s]]; + for (c = l[0], s = 1; s < 256; ++s) c = l[s] += c; + for (u = 255 / o, s = 0; s < o; ++s) a[s] = l[i[s]] * u + .5 | 0; + this.cache.put_buffer(d); + } + canny(e, t, n, r) { + let a = e.cols, o = e.rows; + e.data, t.resize(a, o, e.channel); + let s = t.data, l = 0, u = 0, d = 0, f = a << 1, p = 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = this.cache.get_buffer(o * f << 2), S = this.cache.get_buffer(3 * (a + 2) << 2), C = this.cache.get_buffer((o + 2) * (a + 2) << 2), w = this.cache.get_buffer(o * a << 2), T = S.i32, E = C.i32, D = w.i32, O = x.i32, k = new c(a, o, i.S32C2_t, x.data), A = 1, j = a + 2 + 1 | 0, M = 2 * (a + 2) + 1 | 0, N = a + 2 | 0, P = N + 1 | 0, F = 0; + for (this.sobel_derivatives(e, k), n > r && (l = n, n = r, r = l), l = 3 * (a + 2) | 0; --l >= 0;) T[l] = 0; + for (l = (o + 2) * (a + 2) | 0; --l >= 0;) E[l] = 0; + for (; u < a; ++u, d += 2) g = O[d], _ = O[d + 1], T[j + u] = (g ^ g >> 31) - (g >> 31) + ((_ ^ _ >> 31) - (_ >> 31)); + for (l = 1; l <= o; ++l, d += f) { + if (l == o) for (u = M + a; --u >= M;) T[u] = 0; + else for (u = 0; u < a; u++) g = O[d + (u << 1)], _ = O[d + (u << 1) + 1], T[M + u] = (g ^ g >> 31) - (g >> 31) + ((_ ^ _ >> 31) - (_ >> 31)); + for (p = d - f | 0, E[P - 1] = 0, m = 0, u = 0; u < a; ++u, p += 2) { + if (h = T[j + u], h > n) { + if (g = O[p], _ = O[p + 1], v = g ^ _, g = (g ^ g >> 31) - (g >> 31) | 0, _ = (_ ^ _ >> 31) - (_ >> 31) | 0, y = g * 13573, b = y + (g + g << 15), _ <<= 15, _ < y) { + if (h > T[j + u - 1] && h >= T[j + u + 1]) { + h > r && !m && E[P + u - N] != 2 ? (E[P + u] = 2, m = 1, D[F++] = P + u) : E[P + u] = 1; + continue; + } + } else if (_ > b) { + if (h > T[A + u] && h >= T[M + u]) { + h > r && !m && E[P + u - N] != 2 ? (E[P + u] = 2, m = 1, D[F++] = P + u) : E[P + u] = 1; + continue; + } + } else if (v = v < 0 ? -1 : 1, h > T[A + u - v] && h > T[M + u + v]) { + h > r && !m && E[P + u - N] != 2 ? (E[P + u] = 2, m = 1, D[F++] = P + u) : E[P + u] = 1; + continue; + } + } + E[P + u] = 0, m = 0; + } + E[P + a] = 0, P += N, u = A, A = j, j = M, M = u; + } + for (u = P - N - 1, l = 0; l < N; ++l, ++u) E[u] = 0; + for (; F > 0;) P = D[--F], P -= N + 1, E[P] == 1 && (E[P] = 2, D[F++] = P), P += 1, E[P] == 1 && (E[P] = 2, D[F++] = P), P += 1, E[P] == 1 && (E[P] = 2, D[F++] = P), P += N, E[P] == 1 && (E[P] = 2, D[F++] = P), P -= 2, E[P] == 1 && (E[P] = 2, D[F++] = P), P += N, E[P] == 1 && (E[P] = 2, D[F++] = P), P += 1, E[P] == 1 && (E[P] = 2, D[F++] = P), P += 1, E[P] == 1 && (E[P] = 2, D[F++] = P); + for (P = N + 1, A = 0, l = 0; l < o; ++l, P += N) for (u = 0; u < a; ++u) s[A++] = Number(E[P + u] == 2) * 255; + this.cache.put_buffer(x), this.cache.put_buffer(S), this.cache.put_buffer(C), this.cache.put_buffer(w); + } + warp_perspective(e, t, n, r) { + r === void 0 && (r = 0); + let i = e.cols | 0, a = e.rows | 0, o = t.cols | 0, s = t.rows | 0, c = e.data, l = t.data, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = 0, S = 0, C = 0, w = 0, T = n.data, E = T[0], D = T[1], O = T[2], k = T[3], A = T[4], j = T[5], M = T[6], N = T[7], P = T[8]; + for (let e = 0; d < s; ++d) for (_ = D * d + O, v = A * d + j, y = N * d + P, u = 0; u < o; ++u, ++e, _ += E, v += k, y += M) b = 1 / y, h = _ * b, g = v * b, p = h | 0, m = g | 0, h > 0 && g > 0 && p < i - 1 && m < a - 1 ? (x = Math.max(h - p, 0), S = Math.max(g - m, 0), f = i * m + p | 0, C = c[f] + x * (c[f + 1] - c[f]), w = c[f + i] + x * (c[f + i + 1] - c[f + i]), l[e] = C + S * (w - C)) : l[e] = r; + } + warp_affine(e, t, n, r) { + r === void 0 && (r = 0); + let i = e.cols, a = e.rows, o = t.cols, s = t.rows, c = e.data, l = t.data, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = n.data, S = x[0], C = x[1], w = x[2], T = x[3], E = x[4], D = x[5]; + for (let e = 0; d < s; ++d) for (h = C * d + w, g = E * d + D, u = 0; u < o; ++u, ++e, h += S, g += T) p = h | 0, m = g | 0, p >= 0 && m >= 0 && p < i - 1 && m < a - 1 ? (_ = h - p, v = g - m, f = i * m + p, y = c[f] + _ * (c[f + 1] - c[f]), b = c[f + i] + _ * (c[f + i + 1] - c[f + i]), l[e] = y + v * (b - y)) : l[e] = r; + } + skindetector(e, t) { + let n, r, i, a, o = e.width * e.height; + for (; o--;) a = o * 4, n = e.data[a], r = e.data[a + 1], i = e.data[a + 2], n > 95 && r > 40 && i > 20 && n > r && n > i && n - Math.min(r, i) > 15 && Math.abs(n - r) > 15 ? t[o] = 255 : t[o] = 0; + } +}; +//#endregion +//#region src/linalg/linalg_base.ts +function h(e, t, n, r) { + r = e[t], e[t] = e[n], e[n] = r; +} +function g(e, t) { + return e = Math.abs(e), t = Math.abs(t), e > t ? (t /= e, e * Math.sqrt(1 + t * t)) : t > 0 ? (e /= t, t * Math.sqrt(1 + e * e)) : 0; +} +//#endregion +//#region src/matmath/matmath.ts +var _ = class { + constructor() {} + identity(e, t) { + t === void 0 && (t = 1); + let n = e.data, r = e.rows, i = e.cols, a = i + 1 | 0, o = r * i, s = o; + for (; --o >= 0;) n[o] = 0; + for (o = s, s = 0; s < o;) n[s] = t, s += a; + } + transpose(e, t) { + let n = 0, r = 0, i = t.rows, a = t.cols, o = 0, s = 0, c = 0, l = t.data, u = e.data; + for (; n < i; s += 1, o += a, n++) for (c = s, r = 0; r < a; c += i, r++) u[c] = l[o + r]; + } + multiply(e, t, n) { + let r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = t.cols, f = t.rows, p = n.cols, m = t.data, h = n.data, g = e.data, _ = 0; + for (; r < f; o += d, r++) for (l = 0, i = 0; i < p; u++, l++, i++) { + for (c = l, s = o, _ = 0, a = 0; a < d; s++, c += p, a++) _ += m[s] * h[c]; + g[u] = _; + } + } + multiply_ABt(e, t, n) { + let r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = t.cols, d = t.rows, f = n.rows, p = t.data, m = n.data, h = e.data, g = 0; + for (; r < d; o += u, r++) for (c = 0, i = 0; i < f; l++, i++) { + for (s = o, g = 0, a = 0; a < u; s++, c++, a++) g += p[s] * m[c]; + h[l] = g; + } + } + multiply_AtB(e, t, n) { + let r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = t.cols, f = t.rows, p = n.cols, m = t.data, h = n.data, g = e.data, _ = 0; + for (; r < d; o++, r++) for (l = 0, i = 0; i < p; u++, l++, i++) { + for (c = l, s = o, _ = 0, a = 0; a < f; s += d, c += p, a++) _ += m[s] * h[c]; + g[u] = _; + } + } + multiply_AAt(e, t) { + let n = 0, r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = t.cols, f = t.rows, p = t.data, m = e.data, h = 0; + for (; n < f; a += f + 1, o = s, n++) for (l = a, u = a, c = o, r = n; r < f; l++, u += f, r++) { + for (s = o, h = 0, i = 0; i < d; i++) h += p[s++] * p[c++]; + m[l] = h, m[u] = h; + } + } + multiply_AtA(e, t) { + let n = 0, r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = t.cols, f = t.rows, p = t.data, m = e.data, h = 0; + for (; n < d; c += d, n++) for (a = n, u = c + n, l = u, r = n; r < d; l++, u += d, r++) { + for (o = a, s = r, h = 0, i = 0; i < f; o += d, s += d, i++) h += p[o] * p[s]; + m[l] = h, m[u] = h; + } + } + identity_3x3(e, t) { + t === void 0 && (t = 1); + let n = e.data; + n[0] = n[4] = n[8] = t, n[1] = n[2] = n[3] = 0, n[5] = n[6] = n[7] = 0; + } + invert_3x3(e, t) { + let n = e.data, r = t.data, i = n[4], a = n[8], o = n[5], s = n[7], c = n[0], l = c * i, u = c * o, d = n[3], f = n[1], p = d * f, m = n[2], h = d * m, g = n[6], _ = g * f, v = g * m, y = 1 / (l * a - u * s - p * a + h * s + _ * o - v * i); + r[0] = (i * a - o * s) * y, r[1] = -(f * a - m * s) * y, r[2] = -(-f * o + m * i) * y, r[3] = -(d * a - o * g) * y, r[4] = (c * a - v) * y, r[5] = -(u - h) * y, r[6] = -(-d * s + i * g) * y, r[7] = -(c * s - _) * y, r[8] = (l - p) * y; + } + multiply_3x3(e, t, n) { + let r = e.data, i = t.data, a = n.data, o = i[0], s = i[1], c = i[2], l = i[3], u = i[4], d = i[5], f = i[6], p = i[7], m = i[8], h = a[0], g = a[1], _ = a[2], v = a[3], y = a[4], b = a[5], x = a[6], S = a[7], C = a[8]; + r[0] = o * h + s * v + c * x, r[1] = o * g + s * y + c * S, r[2] = o * _ + s * b + c * C, r[3] = l * h + u * v + d * x, r[4] = l * g + u * y + d * S, r[5] = l * _ + u * b + d * C, r[6] = f * h + p * v + m * x, r[7] = f * g + p * y + m * S, r[8] = f * _ + p * b + m * C; + } + mat3x3_determinant(e) { + let t = e.data; + return t[0] * t[4] * t[8] - t[0] * t[5] * t[7] - t[3] * t[1] * t[8] + t[3] * t[2] * t[7] + t[6] * t[1] * t[5] - t[6] * t[2] * t[4]; + } + determinant_3x3(e, t, n, r, i, a, o, s, c) { + return e * i * c - e * a * s - r * t * c + r * n * s + o * t * a - o * n * i; + } +}, v = class extends s { + constructor() { + super(), this.matmath = new _(); + } + JacobiImpl(e, t, n, r, a, o) { + let s = i.EPSILON, c = 0, l = 0, u = 0, d = 0, f = 0, p = 0, m = 0, _ = 0, v = 0, y = o * o * 30, b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = 0, D = 0, O = 0, k = this.cache.get_buffer(o << 2), A = this.cache.get_buffer(o << 2), j = k.i32, M = A.i32; + if (r) for (; c < o; c++) { + for (u = c * a, l = 0; l < o; l++) r[u + l] = 0; + r[u + c] = 1; + } + for (u = 0; u < o; u++) { + if (n[u] = e[(t + 1) * u], u < o - 1) { + for (d = u + 1, b = Math.abs(e[t * u + d]), c = u + 2; c < o; c++) x = Math.abs(e[t * u + c]), b < x && (b = x, d = c); + j[u] = d; + } + if (u > 0) { + for (d = 0, b = Math.abs(e[u]), c = 1; c < u; c++) x = Math.abs(e[t * c + u]), b < x && (b = x, d = c); + M[u] = d; + } + } + if (o > 1) for (; v < y; v++) { + for (u = 0, b = Math.abs(e[j[0]]), c = 1; c < o - 1; c++) x = Math.abs(e[t * c + j[c]]), b < x && (b = x, u = c); + for (f = j[u], c = 1; c < o; c++) x = Math.abs(e[t * M[c] + c]), b < x && (b = x, u = M[c], f = c); + if (S = e[t * u + f], Math.abs(S) <= s) break; + for (C = (n[f] - n[u]) * .5, w = Math.abs(C) + g(S, C), T = g(S, w), E = w / T, T = S / T, w = S / w * S, C < 0 && (T = -T, w = -w), e[t * u + f] = 0, n[u] -= w, n[f] += w, c = 0; c < u; c++) m = t * c + u, _ = t * c + f, D = e[m], O = e[_], e[m] = D * E - O * T, e[_] = D * T + O * E; + for (c = u + 1; c < f; c++) m = t * u + c, _ = t * c + f, D = e[m], O = e[_], e[m] = D * E - O * T, e[_] = D * T + O * E; + for (c = f + 1, m = t * u + c, _ = t * f + c; c < o; c++, m++, _++) D = e[m], O = e[_], e[m] = D * E - O * T, e[_] = D * T + O * E; + if (r) for (m = a * u, _ = a * f, c = 0; c < o; c++, m++, _++) D = r[m], O = r[_], r[m] = D * E - O * T, r[_] = D * T + O * E; + for (l = 0; l < 2; l++) { + if (p = l == 0 ? u : f, p < o - 1) { + for (d = p + 1, b = Math.abs(e[t * p + d]), c = p + 2; c < o; c++) x = Math.abs(e[t * p + c]), b < x && (b = x, d = c); + j[p] = d; + } + if (p > 0) { + for (d = 0, b = Math.abs(e[p]), c = 1; c < p; c++) x = Math.abs(e[t * c + p]), b < x && (b = x, d = c); + M[p] = d; + } + } + } + for (u = 0; u < o - 1; u++) { + for (d = u, c = u + 1; c < o; c++) n[d] < n[c] && (d = c); + if (u != d && (h(n, d, u, b), r)) for (c = 0; c < o; c++) h(r, a * d + c, a * u + c, b); + } + this.cache.put_buffer(k), this.cache.put_buffer(A); + } + JacobiSVDImpl(e, t, n, r, a, o, s, c) { + let l = i.EPSILON * 2, u = i.FLT_MIN, d = 0, f = 0, p = 0, m = 0, _ = Math.max(o, 30), v = 0, y = 0, b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = 0, D = 0, O = 0, k = 0, A = 0, j = 0, M = 0, N = 0, P = 0, F = 4660, I = 0, L = 0, R = 0, z = this.cache.get_buffer(s << 3), B = z.f64; + for (; d < s; d++) { + for (p = 0, O = 0; p < o; p++) T = e[d * t + p], O += T * T; + if (B[d] = O, r) { + for (p = 0; p < s; p++) r[d * a + p] = 0; + r[d * a + d] = 1; + } + } + for (; m < _; m++) { + for (S = 0, d = 0; d < s - 1; d++) for (f = d + 1; f < s; f++) { + for (v = d * t | 0, y = f * t | 0, M = B[d], N = 0, P = B[f], p = 2, N += e[v] * e[y], N += e[v + 1] * e[y + 1]; p < o; p++) N += e[v + p] * e[y + p]; + if (!(Math.abs(N) <= l * Math.sqrt(M * P))) { + for (N *= 2, k = M - P, A = g(N, k), k < 0 ? (j = (A - k) * .5, w = Math.sqrt(j / A), C = N / (A * w * 2)) : (C = Math.sqrt((A + k) / (A * 2)), w = N / (A * C * 2)), M = 0, P = 0, p = 2, E = C * e[v] + w * e[y], D = -w * e[v] + C * e[y], e[v] = E, e[y] = D, M += E * E, P += D * D, E = C * e[v + 1] + w * e[y + 1], D = -w * e[v + 1] + C * e[y + 1], e[v + 1] = E, e[y + 1] = D, M += E * E, P += D * D; p < o; p++) E = C * e[v + p] + w * e[y + p], D = -w * e[v + p] + C * e[y + p], e[v + p] = E, e[y + p] = D, M += E * E, P += D * D; + if (B[d] = M, B[f] = P, S = 1, r) for (b = d * a | 0, x = f * a | 0, p = 2, E = C * r[b] + w * r[x], D = -w * r[b] + C * r[x], r[b] = E, r[x] = D, E = C * r[b + 1] + w * r[x + 1], D = -w * r[b + 1] + C * r[x + 1], r[b + 1] = E, r[x + 1] = D; p < s; p++) E = C * r[b + p] + w * r[x + p], D = -w * r[b + p] + C * r[x + p], r[b + p] = E, r[x + p] = D; + } + } + if (S == 0) break; + } + for (d = 0; d < s; d++) { + for (p = 0, O = 0; p < o; p++) T = e[d * t + p], O += T * T; + B[d] = Math.sqrt(O); + } + for (d = 0; d < s - 1; d++) { + for (f = d, p = d + 1; p < s; p++) B[f] < B[p] && (f = p); + if (d != f && (h(B, d, f, O), r)) { + for (p = 0; p < o; p++) h(e, d * t + p, f * t + p, T); + for (p = 0; p < s; p++) h(r, d * a + p, f * a + p, T); + } + } + for (d = 0; d < s; d++) n[d] = B[d]; + if (!r) { + this.cache.put_buffer(z); + return; + } + for (d = 0; d < c; d++) { + for (O = d < s ? B[d] : 0; O <= u;) { + for (L = 1 / o, p = 0; p < o; p++) F = F * 214013 + 2531011, I = F >> 16 & 256 ? L : -L, e[d * t + p] = I; + for (m = 0; m < 2; m++) for (f = 0; f < d; f++) { + for (O = 0, p = 0; p < o; p++) O += e[d * t + p] * e[f * t + p]; + for (R = 0, p = 0; p < o; p++) T = e[d * t + p] - O * e[f * t + p], e[d * t + p] = T, R += Math.abs(T); + for (R = R ? 1 / R : 0, p = 0; p < o; p++) e[d * t + p] *= R; + } + for (O = 0, p = 0; p < o; p++) T = e[d * t + p], O += T * T; + O = Math.sqrt(O); + } + for (w = 1 / O, p = 0; p < o; p++) e[d * t + p] *= w; + } + this.cache.put_buffer(z); + } + lu_solve(e, t) { + let n = 0, r = 0, a = 0, o = 1, s = e.cols, c = e.data, l = t.data, u, d, f; + for (n = 0; n < s; n++) { + for (a = n, r = n + 1; r < s; r++) Math.abs(c[r * s + n]) > Math.abs(c[a * s + n]) && (a = r); + if (Math.abs(c[a * s + n]) < i.EPSILON) return 0; + if (a != n) { + for (r = n; r < s; r++) h(c, n * s + r, a * s + r, void 0); + h(l, n, a, void 0), o = -o; + } + for (d = -1 / c[n * s + n], r = n + 1; r < s; r++) { + for (u = c[r * s + n] * d, a = n + 1; a < s; a++) c[r * s + a] += u * c[n * s + a]; + l[r] += u * l[n]; + } + c[n * s + n] = -d; + } + for (n = s - 1; n >= 0; n--) { + for (f = l[n], a = n + 1; a < s; a++) f -= c[n * s + a] * l[a]; + l[n] = f * c[n * s + n]; + } + return 1; + } + cholesky_solve(e, t) { + let n = 0, r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = e.cols, u = e.data, d = t.data, f, p; + for (n = 0; n < l; n++) for (p = 1, a = n * l, o = a, r = n; r < l; r++) { + for (f = u[o + n], i = 0; i < n; i++) f -= u[i * l + n] * u[o + i]; + if (r == n) { + if (u[o + n] = f, f == 0) return 0; + p = 1 / f; + } else u[a + r] = f, u[o + n] = f * p; + o += l; + } + for (a = 0, s = 0; s < l; s++) { + for (f = d[s], c = 0; c < s; c++) f -= u[a + c] * d[c]; + d[s] = f, a += l; + } + for (a = 0, s = 0; s < l; s++) d[s] /= u[a + s], a += l; + for (s = l - 1; s >= 0; s--) { + for (f = d[s], c = s + 1, a = c * l; c < l; c++) f -= u[a + s] * d[c], a += l; + d[s] = f; + } + return 1; + } + svd_decompose(e, t, n, r, a) { + a === void 0 && (a = 0); + let o = 0, s = 0, l = e.rows, u = e.cols, d = l, f = u, p = e.type | i.C1_t; + d < f && (o = 1, s = d, d = f, f = s); + let m = this.cache.get_buffer(d * d << 3), h = this.cache.get_buffer(f << 3), g = this.cache.get_buffer(f * f << 3), _ = new c(d, d, p, m.data), v = new c(1, f, p, h.data), y = new c(f, f, p, g.data); + if (o == 0) this.matmath.transpose(_, e); + else { + for (s = 0; s < u * l; s++) _.data[s] = e.data[s]; + for (; s < f * d; s++) _.data[s] = 0; + } + if (this.JacobiSVDImpl(_.data, d, v.data, y.data, f, d, f, d), t) { + for (s = 0; s < f; s++) t.data[s] = v.data[s]; + for (; s < u; s++) t.data[s] = 0; + } + if (o == 0) { + if (n && a & i.SVD_U_T) for (s = d * d; --s >= 0;) n.data[s] = _.data[s]; + else n && this.matmath.transpose(n, _); + if (r && a & i.SVD_V_T) for (s = f * f; --s >= 0;) r.data[s] = y.data[s]; + else r && this.matmath.transpose(r, y); + } else { + if (n && a & i.SVD_U_T) for (s = f * f; --s >= 0;) n.data[s] = y.data[s]; + else n && this.matmath.transpose(n, y); + if (r && a & i.SVD_V_T) for (s = d * d; --s >= 0;) r.data[s] = _.data[s]; + else r && this.matmath.transpose(r, _); + } + this.cache.put_buffer(m), this.cache.put_buffer(h), this.cache.put_buffer(g); + } + svd_solve(e, t, n) { + let r = 0, a = 0, o = 0, s = 0, l = 0, u = e.rows, d = e.cols, f = 0, p = 0, m = 0, h = e.type | i.C1_t, g = this.cache.get_buffer(u * u << 3), _ = this.cache.get_buffer(d << 3), v = this.cache.get_buffer(d * d << 3), y = new c(u, u, h, g.data), b = new c(1, d, h, _.data), x = new c(d, d, h, v.data), S = n.data, C = y.data, w = b.data, T = x.data; + for (this.svd_decompose(e, b, y, x, 0), m = i.EPSILON * w[0] * d; r < d; r++, l += d) { + for (p = 0, a = 0; a < d; a++) if (w[a] > m) { + for (o = 0, f = 0, s = 0; o < u; o++, s += d) f += C[s + a] * S[o]; + p += f * T[l + a] / w[a]; + } + t.data[r] = p; + } + this.cache.put_buffer(g), this.cache.put_buffer(_), this.cache.put_buffer(v); + } + svd_invert(e, t) { + let n = 0, r = 0, a = 0, o = 0, s = 0, l = 0, u = t.rows, d = t.cols, f = 0, p = 0, m = t.type | i.C1_t, h = this.cache.get_buffer(u * u << 3), g = this.cache.get_buffer(d << 3), _ = this.cache.get_buffer(d * d << 3), v = new c(u, u, m, h.data), y = new c(1, d, m, g.data), b = new c(d, d, m, _.data), x = e.data, S = v.data, C = y.data, w = b.data; + for (this.svd_decompose(t, y, v, b, 0), p = i.EPSILON * C[0] * d; n < d; n++, s += d) for (r = 0, o = 0; r < u; r++, l++) { + for (a = 0, f = 0; a < d; a++, o++) C[a] > p && (f += w[s + a] * S[o] / C[a]); + x[l] = f; + } + this.cache.put_buffer(h), this.cache.put_buffer(g), this.cache.put_buffer(_); + } + eigenVV(e, t, n) { + let r = e.cols, a = r * r, o = e.type | i.C1_t, s = this.cache.get_buffer(r * r << 3), l = this.cache.get_buffer(r << 3), u = new c(r, r, o, s.data), d = new c(1, r, o, l.data); + for (; --a >= 0;) u.data[a] = e.data[a]; + if (this.JacobiImpl(u.data, r, d.data, t ? t.data : null, r, r), n) for (; --r >= 0;) n.data[r] = d.data[r]; + this.cache.put_buffer(s), this.cache.put_buffer(l); + } +}; +//#endregion +//#region src/fast_corners/fast_private.ts +function y(e, t, n, r, i) { + let a = 0, o = e[t], s = i, c = 0, l = 0, u = 0; + for (; a < 25; ++a) r[a] = o - e[t + n[a]]; + for (a = 0; a < 16; a += 2) c = Math.min(r[a + 1], r[a + 2]), c = Math.min(c, r[a + 3]), !(c <= s) && (c = Math.min(c, r[a + 4]), c = Math.min(c, r[a + 5]), c = Math.min(c, r[a + 6]), c = Math.min(c, r[a + 7]), c = Math.min(c, r[a + 8]), s = Math.max(s, Math.min(c, r[a])), s = Math.max(s, Math.min(c, r[a + 9]))); + for (l = -s, a = 0; a < 16; a += 2) u = Math.max(r[a + 1], r[a + 2]), u = Math.max(u, r[a + 3]), u = Math.max(u, r[a + 4]), u = Math.max(u, r[a + 5]), !(u >= l) && (u = Math.max(u, r[a + 6]), u = Math.max(u, r[a + 7]), u = Math.max(u, r[a + 8]), l = Math.min(l, Math.max(u, r[a])), l = Math.min(l, Math.max(u, r[a + 9]))); + return -l - 1; +} +//#endregion +//#region src/fast_corners/fast_corners.ts +var b = class extends s { + constructor() { + super(), this.offsets16 = new Int32Array([ + 0, + 3, + 1, + 3, + 2, + 2, + 3, + 1, + 3, + 0, + 3, + -1, + 2, + -2, + 1, + -3, + 0, + -3, + -1, + -3, + -2, + -2, + -3, + -1, + -3, + 0, + -3, + 1, + -2, + 2, + -1, + 3 + ]), this.threshold_tab = /* @__PURE__ */ new Uint8Array(512), this._threshold = 20, this.pixel_off = /* @__PURE__ */ new Int32Array(25), this.score_diff = /* @__PURE__ */ new Int32Array(25); + } + set_threshold(e) { + this._threshold = Math.min(Math.max(e, 0), 255); + for (let e = -255; e <= 255; ++e) this.threshold_tab[e + 255] = e < -this._threshold ? 1 : e > this._threshold ? 2 : 0; + return this._threshold; + } + detect(e, t, n) { + n === void 0 && (n = 3); + let r = e.data, i = e.cols, a = e.rows, o = 0, s = 0, c = 0, l = 0, u = 0, d = 0, f = this.cache.get_buffer(3 * i), p = this.cache.get_buffer((i + 1) * 3 << 2), m = f.u8, h = p.i32, g = this.pixel_off, _ = this.score_diff, v = Math.max(3, n), b = Math.min(a - 2, a - n), x = Math.max(3, n), S = Math.min(i - 3, i - n), C = 0, w = 0, T, E = y, D = this.threshold_tab, O = this._threshold, k = 0, A = 0, j = 0, M = 0, N = 0, P = 0, F = 0, I = 0, L = 0, R = 0, z = 0, B = 0; + this._cmp_offsets(g, i, 16); + let V = g[0], ee = g[1], H = g[2], U = g[3], W = g[4], G = g[5], K = g[6], te = g[7], q = g[8], ne = g[9], J = g[10], Y = g[11], X = g[12], Z = g[13], Q = g[14], re = g[15]; + for (o = 0; o < i * 3; ++o) m[o] = 0; + for (o = v; o < b; ++o) { + for (F = o * i + x | 0, d = (o - 3) % 3, P = d * i | 0, N = d * (i + 1) | 0, s = 0; s < i; ++s) m[P + s] = 0; + if (M = 0, o < b - 1) { + for (s = x; s < S; ++s, ++F) if (k = r[F], A = -k + 255, j = D[A + r[F + V]] | D[A + r[F + q]], j != 0 && (j &= D[A + r[F + H]] | D[A + r[F + J]], j &= D[A + r[F + W]] | D[A + r[F + X]], j &= D[A + r[F + K]] | D[A + r[F + Q]], j != 0)) { + if (j &= D[A + r[F + ee]] | D[A + r[F + ne]], j &= D[A + r[F + U]] | D[A + r[F + Y]], j &= D[A + r[F + G]] | D[A + r[F + Z]], j &= D[A + r[F + te]] | D[A + r[F + re]], j & 1) for (l = k - O, C = 0, c = 0; c < 25; ++c) if (u = r[F + g[c]], u < l) { + if (++C, C > 8) { + ++M, h[N + M] = s, m[P + s] = E(r, F, g, _, O); + break; + } + } else C = 0; + if (j & 2) for (l = k + O, C = 0, c = 0; c < 25; ++c) if (u = r[F + g[c]], u > l) { + if (++C, C > 8) { + ++M, h[N + M] = s, m[P + s] = E(r, F, g, _, O); + break; + } + } else C = 0; + } + } + if (h[N + i] = M, o != v) for (d = (o - 4 + 3) % 3, I = d * i | 0, N = d * (i + 1) | 0, d = (o - 5 + 3) % 3, L = d * i | 0, M = h[N + i], c = 0; c < M; ++c) s = h[N + c], R = s + 1 | 0, z = s - 1 | 0, B = m[I + s], B > m[I + R] && B > m[I + z] && B > m[L + z] && B > m[L + s] && B > m[L + R] && B > m[P + z] && B > m[P + s] && B > m[P + R] && (T = t[w], T.x = s, T.y = o - 1, T.score = B, w++); + } + return this.cache.put_buffer(f), this.cache.put_buffer(p), w; + } + _cmp_offsets(e, t, n) { + let r = 0, i = this.offsets16; + for (; r < n; ++r) e[r] = i[r << 1] + i[(r << 1) + 1] * t; + for (; r < 25; ++r) e[r] = e[r - n]; + } +}, x = class extends s { + constructor(e) { + super(), this.levels = e | 0, this.data = Array(e); + let t = new m(); + this.pyrdown = t.pyrdown; + } + allocate(e, t, n) { + let r = this.levels; + for (; --r >= 0;) this.data[r] = new c(e >> r, t >> r, n); + } + build(e, t) { + t === void 0 && (t = !0); + let n = 2, r = e, i = this.data[0]; + if (!t) { + let t = e.cols * e.rows; + for (; --t >= 0;) i.data[t] = e.data[t]; + } + for (i = this.data[1], this.pyrdown(r, i); n < this.levels; ++n) r = i, i = this.data[n], this.pyrdown(r, i); + } +}, S = class { + constructor() {} + perspective_4point_transform(e, t, n, r, i, a, o, s, c, l, u, d, f, p, m, h, g) { + let _ = t, v = l, y = o, b = _ * v * y, x = m, S = _ * x, C = v * S, w = u, T = _ * w, E = a, D = n, O = p, k = D * O, A = k * E, j = O * E * w, M = O * y, N = O * w, P = v * y, F = x * v, I = x * E, L = w * E, R = 1 / (M - N - P + F - I + L), z = _ * O, B = D * E, V = y * _, ee = x * V, H = D * v, U = k * w, W = D * w * E, G = y * x * v, K = x * D, te = -(C - b + T * E - E * S - k * v + A - j + M * v) * R, q = (b - C - z * y + z * w + A - v * B + I * v - j) * R, ne = _, J = (-w * S + ee + H * y - k * y + U - W + I * w - G) * R, Y = (-ee + V * w - K * v + U - W + K * E + G - M * w) * R, X = D, Z = (-T + V + H - B + N - M - F + I) * R, Q = (-S + T + k - H + I - L - M + P) * R; + _ = r, v = d, y = c, b = _ * v * y, x = g, S = _ * x, C = v * S, w = f, T = _ * w, E = s, D = i, O = h, k = D * O, A = k * E, j = O * E * w, M = O * y, N = O * w, P = v * y, F = x * v, I = x * E, L = w * E, R = 1 / (M - N - P + F - I + L), z = _ * O, B = D * E, V = y * _, ee = x * V, H = D * v, U = k * w, W = D * w * E, G = y * x * v, K = x * D; + let re = -(C - b + T * E - E * S - k * v + A - j + M * v) * R, ie = (b - C - z * y + z * w + A - v * B + I * v - j) * R, ae = _, oe = (-w * S + ee + H * y - k * y + U - W + I * w - G) * R, se = (-ee + V * w - K * v + U - W + K * E + G - M * w) * R, ce = D, le = (-T + V + H - B + N - M - F + I) * R, ue = (-S + T + k - H + I - L - M + P) * R; + v = Y - Q * X, y = te * Y, b = te * X, S = J * q, C = ne * J, T = q * Z; + let de = ne * Z; + O = 1 / (y - b * Q - S + C * Q + T * X - de * Y), A = -J + X * Z; + let fe = -J * Q + Y * Z; + L = -q + ne * Q; + let pe = te - de; + B = te * Q - T, V = -q * X + ne * Y; + let me = b - C, he = y - S; + W = v * O; + let ge = L * O, _e = V * O, $ = e.data; + $[0] = re * W + A * O * ie - fe * O * ae, $[1] = re * ge + pe * O * ie - B * O * ae, $[2] = -re * _e - me * O * ie + he * O * ae, $[3] = oe * W + A * O * se - fe * O * ce, $[4] = oe * ge + pe * O * se - B * O * ce, $[5] = -oe * _e - me * O * se + he * O * ce, $[6] = le * W + A * O * ue - fe * O, $[7] = le * ge + pe * O * ue - B * O, $[8] = -le * _e - me * O * ue + he * O; + } + invert_affine_transform(e, t) { + let n = e.data, r = t.data, i = n[0], a = n[1], o = n[2], s = n[3], c = n[4], l = n[5], u = 1 / (i * c - a * s); + r[0] = u * c, r[1] = u * -a, r[2] = u * (a * l - o * c), r[3] = u * -s, r[4] = u * i, r[5] = u * (o * s - i * l); + } + invert_perspective_transform(e, t) { + let n = e.data, r = t.data, i = n[0], a = n[1], o = n[2], s = n[3], c = n[4], l = n[5], u = n[6], d = n[7], f = n[8], p = 1 / (i * (c * f - l * d) - a * (s * f - l * u) + o * (s * d - c * u)); + r[0] = p * (c * f - l * d), r[1] = p * (o * d - a * f), r[2] = p * (a * l - o * c), r[3] = p * (l * u - s * f), r[4] = p * (i * f - o * u), r[5] = p * (o * s - i * l), r[6] = p * (s * d - c * u), r[7] = p * (a * u - i * d), r[8] = p * (i * c - a * s); + } +}, C = class { + constructor(e = 0, t = 0, n = 0, r = 0, i = -1) { + this.x = e, this.y = t, this.score = n, this.level = r, this.angle = i; + } +}, w = [ + 8, + -3, + 9, + 5, + 4, + 2, + 7, + -12, + -11, + 9, + -8, 2, 7, -12, @@ -1347,13 +1995,29 @@ var d = class { ]; //#endregion //#region src/orb/rectify_patch.ts -function _(e, t, n, r, i, a, o, s) { +function T(e, t, n, r, i, a, o, s) { let c = Math.cos(n), l = Math.sin(n); o.data[0] = c, o.data[1] = -l, o.data[2] = (-c + l) * a * .5 + r, o.data[3] = l, o.data[4] = c, o.data[5] = (-l - c) * a * .5 + i, s.warp_affine(e, t, o, 128); } //#endregion +//#region src/orb/orb.ts +var E = class extends s { + constructor() { + super(), this.bit_pattern_31_ = new Int32Array(w), this.H = new c(3, 3, i.F32_t | i.C1_t), this.patch_img = new c(32, 32, i.U8_t | i.C1_t), this.imgproc = new m(); + } + describe(e, t, n, r) { + let a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = 0, f = 0, p = this.patch_img.data, m = 0; + r.type & i.U8_t ? r.resize(32, n, 1) : (r.type = i.U8_t, r.cols = 32, r.rows = n, r.channel = 1, r.allocate()); + let h = r.data, g = 0; + for (a = 0; a < n; ++a) { + for (s = t[a].x, c = t[a].y, l = t[a].angle, T(e, this.patch_img, l, s, c, 32, this.H, this.imgproc), m = 0, o = 0; o < 32; ++o) u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, f = u < d | 0, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, f |= (u < d) << 1, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, f |= (u < d) << 2, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, f |= (u < d) << 3, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, f |= (u < d) << 4, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, f |= (u < d) << 5, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, f |= (u < d) << 6, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, f |= (u < d) << 7, h[g + o] = f; + g += 32; + } + } +}; +//#endregion //#region src/yape/yape_utils.ts -function v(e, t, n) { +function D(e, t, n) { let r = 0, i, a; for (i = n, a = 0; a < i; a++, r++) i = Math.sqrt(n * n - a * a) + .5 | 0, t[r] = i + e * a; for (i--; i < a && i >= 0; i--, r++) a = Math.sqrt(n * n - i * i) + .5 | 0, t[r] = i + e * a; @@ -1365,11 +2029,11 @@ function v(e, t, n) { for (a++; a < 0; a++, r++) i = Math.sqrt(n * n - a * a) + .5 | 0, t[r] = i + e * a; return t[r] = t[0], t[r + 1] = t[1], r; } -function y(e, t, n) { +function O(e, t, n) { let r = 0; return e[t + 1] != 0 && r++, e[t - 1] != 0 && r++, e[t + n] != 0 && r++, e[t + n + 1] != 0 && r++, e[t + n - 1] != 0 && r++, e[t - n] != 0 && r++, e[t - n + 1] != 0 && r++, e[t - n - 1] != 0 && r++, r; } -function b(e, t, n, r, i) { +function k(e, t, n, r, i) { let a, o; if (n > 0) for (t -= r * i, o = -i; o <= i; ++o) { for (a = -i; a <= i; ++a) if (e[t + a] > n) return !1; @@ -1381,7 +2045,7 @@ function b(e, t, n, r, i) { } return !0; } -function x(e, t, n, r, i, a, o, s) { +function A(e, t, n, r, i, a, o, s) { let c = 0, l = 0, u = o - 1 | 0, d = 0, f = 0, p = 0, m = 0, h = 0; if (d = e[t + a[l]], d <= i) if (d >= r) if (f = e[t + a[u]], f <= i) if (f >= r) { n[t] = 0; @@ -1467,221 +2131,28 @@ function x(e, t, n, r, i, a, o, s) { n[t] = 0; return; } - c -= d + p, h = 8; - break; - } - if (p <= i) { - n[t] = 0; - return; - } - if (m <= i) { - n[t] = 0; - return; - } - if (p = m, u++, m = e[t + a[u]], m > i) { - c -= d + p, h = 3; - break; - } - if (m < r) { - c -= d + p, h = 6; - break; - } - n[t] = 0; - return; - case 1: - if (d < r) { - if (p = m, u++, m = e[t + a[u]], m > i) { - n[t] = 0; - return; - } - c -= d + p, h = 1; - break; - } - if (d > i) { - if (p < r) { - n[t] = 0; - return; - } - if (m < r) { - n[t] = 0; - return; - } - if (p = m, u++, m = e[t + a[u]], m < r) { - n[t] = 0; - return; - } - c -= d + p, h = 9; - break; - } - if (p >= r) { - n[t] = 0; - return; - } - if (m >= r) { - n[t] = 0; - return; - } - if (p = m, u++, m = e[t + a[u]], m < r) { - c -= d + p, h = 2; - break; - } - if (m > i) { - c -= d + p, h = 7; - break; - } - n[t] = 0; - return; - case 2: - if (d > i) { - n[t] = 0; - return; - } - if (p = m, u++, m = e[t + a[u]], d < r) { - if (m > i) { - n[t] = 0; - return; - } - c -= d + p, h = 4; - break; - } - if (m > i) { - c -= d + p, h = 7; - break; - } - if (m < r) { - c -= d + p, h = 2; - break; - } - n[t] = 0; - return; - case 3: - if (d < r) { - n[t] = 0; - return; - } - if (p = m, u++, m = e[t + a[u]], d > i) { - if (m < r) { - n[t] = 0; - return; - } - c -= d + p, h = 5; - break; - } - if (m > i) { - c -= d + p, h = 3; - break; - } - if (m < r) { - c -= d + p, h = 6; - break; - } - n[t] = 0; - return; - case 4: - if (d > i) { - n[t] = 0; - return; - } - if (d < r) { - if (p = m, u++, m = e[t + a[u]], m > i) { - n[t] = 0; - return; - } - c -= d + p, h = 1; - break; - } - if (m >= r) { - n[t] = 0; - return; - } - if (p = m, u++, m = e[t + a[u]], m < r) { - c -= d + p, h = 2; - break; - } - if (m > i) { - c -= d + p, h = 7; - break; - } - n[t] = 0; - return; - case 5: - if (d < r) { - n[t] = 0; - return; - } - if (d > i) { - if (p = m, u++, m = e[t + a[u]], m < r) { - n[t] = 0; - return; - } - c -= d + p, h = 0; - break; - } - if (m <= i) { - n[t] = 0; - return; - } - if (p = m, u++, m = e[t + a[u]], m > i) { - c -= d + p, h = 3; - break; - } - if (m < r) { - c -= d + p, h = 6; - break; - } - n[t] = 0; - return; - case 7: - if (d > i) { - n[t] = 0; - return; - } - if (d < r) { - n[t] = 0; - return; - } - if (p = m, u++, m = e[t + a[u]], m > i) { - c -= d + p, h = 3; - break; - } - if (m < r) { - c -= d + p, h = 6; - break; - } - n[t] = 0; - return; - case 6: - if (d > i) { + c -= d + p, h = 8; + break; + } + if (p <= i) { n[t] = 0; return; } - if (d < r) { + if (m <= i) { n[t] = 0; return; } - if (p = m, u++, m = e[t + a[u]], m < r) { - c -= d + p, h = 2; + if (p = m, u++, m = e[t + a[u]], m > i) { + c -= d + p, h = 3; break; } - if (m > i) { - c -= d + p, h = 7; + if (m < r) { + c -= d + p, h = 6; break; } n[t] = 0; return; - case 8: - if (d > i) { - if (m < r) { - n[t] = 0; - return; - } - if (p = m, u++, m = e[t + a[u]], m < r) { - n[t] = 0; - return; - } - c -= d + p, h = 9; - break; - } + case 1: if (d < r) { if (p = m, u++, m = e[t + a[u]], m > i) { n[t] = 0; @@ -1690,802 +2161,279 @@ function x(e, t, n, r, i, a, o, s) { c -= d + p, h = 1; break; } - n[t] = 0; - return; - case 9: - if (d < r) { - if (m > i) { + if (d > i) { + if (p < r) { n[t] = 0; return; } - if (p = m, u++, m = e[t + a[u]], m > i) { + if (m < r) { n[t] = 0; return; } - c -= d + p, h = 8; - break; - } - if (d > i) { if (p = m, u++, m = e[t + a[u]], m < r) { n[t] = 0; return; } - c -= d + p, h = 0; + c -= d + p, h = 9; break; } - n[t] = 0; - return; - default: break; - } - n[t] = c + s * e[t]; -} -var S = class { - constructor(e, t, n) { - this.dirs = /* @__PURE__ */ new Int32Array(1024), this.dirs_count = v(e, this.dirs, n) | 0, this.scores = new Int32Array(e * t), this.radius = n | 0; - } -}, C = class { - constructor() { - this.level_tables = [], this.tau = 7; - } - init(e, t, n, r = 1) { - n = Math.min(n, 7), n = Math.max(n, 3); - for (let i = 0; i < r; ++i) this.level_tables[i] = new S(e >> i, t >> i, n); - } - detect(e, t, n = 4) { - let r = this.level_tables[0], i = r.radius | 0, a = i - 1 | 0, o = r.dirs, s = r.dirs_count | 0, c = s >> 1, l = e.data, u = e.cols | 0, d = e.rows | 0, f = u >> 1, p = r.scores, m = 0, h = 0, g = 0, _ = 0, v = 0, S = 0, C = 0, w = 0, T = this.tau | 0, E = 0, D, O = Math.max(i + 1, n) | 0, k = Math.max(i + 1, n) | 0, A = Math.min(u - i - 2, u - n) | 0, j = Math.min(d - i - 2, d - n) | 0; - for (g = k * u + O | 0, h = k; h < j; ++h, g += u) for (m = O, _ = g; m < A; ++m, ++_) v = l[_] + T, S = l[_] - T, S < l[_ + i] && l[_ + i] < v && S < l[_ - i] && l[_ - i] < v ? p[_] = 0 : x(l, _, p, S, v, o, c, s); - for (g = k * u + O | 0, h = k; h < j; ++h, g += u) for (m = O, _ = g; m < A; ++m, ++_) w = p[_], C = Math.abs(w), C < 5 ? (++m, ++_) : y(p, _, u) >= 3 && b(p, _, w, f, i) && (D = t[E], D.x = m, D.y = h, D.score = C, ++E, m += a, _ += a); - return E; - } -}; -//#endregion -//#region src/yape06/yape06_utils.ts -function w(e, t, n, r, i, a, o, s, c) { - let l = 0, u = 0, d = o * n + a | 0, f = d; - for (l = o; l < c; ++l, d += n, f = d) for (u = a; u < s; ++u, ++f) f + r < e.length && f - r >= 0 && f + i < e.length && f - i >= 0 ? t[f] = -4 * e[f] + e[f + r] + e[f - r] + e[f + i] + e[f - i] : t[f] = 0; -} -function T(e, t, n, r, i, a, o) { - let s = -2 * e[t] + e[t + r] + e[t - r], c = -2 * e[t] + e[t + i] + e[t - i], l = e[t + a] + e[t - a] - e[t + o] - e[t - o], u = Math.sqrt((s - c) * (s - c) + 4 * l * l) | 0; - return Math.min(Math.abs(n - u), Math.abs(-(n + u))); -} -//#endregion -//#region src/motion_estimator/ransac_params_t.ts -var E = class { - constructor(e = 0, t = .5, n = .5, r = .99) { - this.size = e, this.thresh = t, this.eps = n, this.prob = r; - } - update_iters(e, t) { - let n = Math.log(1 - this.prob), r = Math.log(1 - Math.pow(1 - e, this.size)); - return (r >= 0 || -n >= t * -r ? t : Math.round(n / r)) | 0; - } -}, D = { - name: "@webarkit/jsfeat-next", - version: "0.7.6", - description: "Typescript version of jsfeat for WebARKit", - main: "dist/jsfeatNext.js", - module: "dist/jsfeatNext.mjs", - types: "types/src/index.d.ts", - unpkg: "dist/jsfeatNext.js", - jsdelivr: "dist/jsfeatNext.js", - exports: { - ".": { - types: "./types/src/index.d.ts", - import: "./dist/jsfeatNext.mjs", - require: "./dist/jsfeatNext.js" - }, - "./package.json": "./package.json" - }, - files: [ - "dist", - "types", - "src" - ], - scripts: { - "build-ts": "vite build", - "dev-ts": "vite build --watch", - "format-check": "prettier --check .", - format: "prettier --write .", - test: "vitest run", - "test:watch": "vitest" - }, - repository: { - type: "git", - url: "git+https://github.com/webarkit/jsfeatNext.git" - }, - keywords: [ - "jsfeat", - "jsfeatNext", - "WebAR", - "WebARKit", - "AugmentedReality", - "computer", - "vision" - ], - author: "Walter Perdan @kalwalt", - license: "LGPL-3.0-or-later", - bugs: { url: "https://github.com/webarkit/jsfeatNext/issues" }, - homepage: "https://github.com/webarkit/jsfeatNext#readme", - devDependencies: { - prettier: "~3.5.3", - typescript: "^6.0.3", - vite: "^8.1.3", - "vite-plugin-dts": "^5.0.3", - vitest: "^4.1.10" - } -}, O, k = class { - constructor() { - this.dt = new e(), this.cache = new r(), this.cache.allocate(30, 640 * 4); - } - get_data_type(e) { - return this.dt._get_data_type(e); - } - get_channel(e) { - return this.dt._get_channel(e); - } - get_data_type_size(e) { - return this.dt._get_data_type_size(e); - } -}; -O = k, O.VERSION = D.version, O.EPSILON = f.EPSILON, O.FLT_MIN = f.FLT_MIN, O.U8_t = f.U8_t, O.S32_t = f.S32_t, O.F32_t = f.F32_t, O.S64_t = f.S64_t, O.F64_t = f.F64_t, O.C1_t = f.C1_t, O.C2_t = f.C2_t, O.C3_t = f.C3_t, O.C4_t = f.C4_t, O.COLOR_RGBA2GRAY = f.COLOR_RGBA2GRAY, O.COLOR_RGB2GRAY = f.COLOR_RGB2GRAY, O.COLOR_BGRA2GRAY = f.COLOR_BGRA2GRAY, O.COLOR_BGR2GRAY = f.COLOR_BGR2GRAY, O.BOX_BLUR_NOSCALE = f.BOX_BLUR_NOSCALE, O.SVD_U_T = f.SVD_U_T, O.SVD_V_T = f.SVD_V_T, O.U8C1_t = O.U8_t | O.C1_t, O.U8C3_t = O.U8_t | O.C3_t, O.U8C4_t = O.U8_t | O.C4_t, O.F32C1_t = O.F32_t | O.C1_t, O.F32C2_t = O.F32_t | O.C2_t, O.S32C1_t = O.S32_t | O.C1_t, O.S32C2_t = O.S32_t | O.C2_t; -var A = class extends k { - constructor() { - super(), this.T0 = new p(3, 3, f.F32_t | f.C1_t), this.T1 = new p(3, 3, f.F32_t | f.C1_t), this.AtA = new p(6, 6, f.F32_t | f.C1_t), this.AtB = new p(6, 1, f.F32_t | f.C1_t); - } - sqr(e) { - return e * e; - } - iso_normalize_points(e, t, n, r, i) { - let a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0; - for (; a < i; ++a) o += e[a].x, s += e[a].y, u += t[a].x, d += t[a].y; - for (o /= i, s /= i, u /= i, d /= i, a = 0; a < i; ++a) m = e[a].x - o, h = e[a].y - s, c += Math.sqrt(m * m + h * h), m = t[a].x - u, h = t[a].y - d, f += Math.sqrt(m * m + h * h); - c /= i, f /= i, l = Math.SQRT2 / c, p = Math.SQRT2 / f, n[0] = n[4] = l, n[2] = -o * l, n[5] = -s * l, n[1] = n[3] = n[6] = n[7] = 0, n[8] = 1, r[0] = r[4] = p, r[2] = -u * p, r[5] = -d * p, r[1] = r[3] = r[6] = r[7] = 0, r[8] = 1; - } - have_collinear_points(e, t) { - let n = 0, r = 0, i = t - 1 | 0, a = 0, o = 0, s = 0, c = 0; - for (; n < i; ++n) for (a = e[n].x - e[i].x, o = e[n].y - e[i].y, r = 0; r < n; ++r) if (s = e[r].x - e[i].x, c = e[r].y - e[i].y, Math.abs(s * o - c * a) <= f.EPSILON * (Math.abs(a) + Math.abs(o) + Math.abs(s) + Math.abs(c))) return !0; - return !1; - } -}, j = class extends A { - constructor() { - super(); - } - run(e, t, n, r) { - let i = 0, a = 0, o = n.type | f.C1_t, s = n.data, c = this.T0.data, l = this.T1.data, u, m, h = 0, g = 0, _ = new d(), v = new k.linalg(); - this.iso_normalize_points(e, t, c, l, r); - let y = this.cache.get_buffer(2 * r * 6 << 3), b = this.cache.get_buffer(2 * r << 3), x = new p(6, 2 * r, o, y.data), S = new p(1, 2 * r, o, b.data), C = x.data, w = S.data; - for (; i < r; ++i) u = e[i], m = t[i], h = c[0] * u.x + c[1] * u.y + c[2], g = c[3] * u.x + c[4] * u.y + c[5], a = i * 2 * 6, C[a] = h, C[a + 1] = g, C[a + 2] = 1, C[a + 3] = 0, C[a + 4] = 0, C[a + 5] = 0, a += 6, C[a] = 0, C[a + 1] = 0, C[a + 2] = 0, C[a + 3] = h, C[a + 4] = g, C[a + 5] = 1, w[i << 1] = l[0] * m.x + l[1] * m.y + l[2], w[(i << 1) + 1] = l[3] * m.x + l[4] * m.y + l[5]; - return _.multiply_AtA(this.AtA, x), _.multiply_AtB(this.AtB, x, S), v.lu_solve(this.AtA, this.AtB), s[0] = this.AtB.data[0], s[1] = this.AtB.data[1], s[2] = this.AtB.data[2], s[3] = this.AtB.data[3], s[4] = this.AtB.data[4], s[5] = this.AtB.data[5], s[6] = 0, s[7] = 0, s[8] = 1, _.invert_3x3(this.T1, this.T1), _.multiply_3x3(n, this.T1, n), _.multiply_3x3(n, n, this.T0), this.cache.put_buffer(y), this.cache.put_buffer(b), 1; - } - error(e, t, n, r, i) { - let a = 0, o, s, c = n.data; - for (; a < i; ++a) o = e[a], s = t[a], r[a] = this.sqr(s.x - c[0] * o.x - c[1] * o.y - c[2]) + this.sqr(s.y - c[3] * o.x - c[4] * o.y - c[5]); - } - check_subset(e, t, n) { - return !0; - } -}, M = class extends A { - constructor() { - super(), this.mLtL = new p(9, 9, f.F32_t | f.C1_t), this.Evec = new p(9, 9, f.F32_t | f.C1_t); - } - run(e, t, n, r) { - let i = 0, a = 0, o = n.data, s = this.T0.data, c = this.T1.data, l = this.mLtL.data, u = this.Evec.data, p = 0, m = 0, h = 0, g = 0, _ = new k.linalg(), v = new d(), y = 0, b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = 0; - for (; i < r; ++i) x += t[i].x, S += t[i].y, T += e[i].x, E += e[i].y; - for (x /= r, S /= r, T /= r, E /= r, i = 0; i < r; ++i) y += Math.abs(t[i].x - x), b += Math.abs(t[i].y - S), C += Math.abs(e[i].x - T), w += Math.abs(e[i].y - E); - if (Math.abs(y) < f.EPSILON || Math.abs(b) < f.EPSILON || Math.abs(C) < f.EPSILON || Math.abs(w) < f.EPSILON) return 0; - for (y = r / y, b = r / b, C = r / C, w = r / w, s[0] = C, s[1] = 0, s[2] = -T * C, s[3] = 0, s[4] = w, s[5] = -E * w, s[6] = 0, s[7] = 0, s[8] = 1, c[0] = 1 / y, c[1] = 0, c[2] = x, c[3] = 0, c[4] = 1 / b, c[5] = S, c[6] = 0, c[7] = 0, c[8] = 1, i = 81; --i >= 0;) l[i] = 0; - for (i = 0; i < r; ++i) p = (t[i].x - x) * y, m = (t[i].y - S) * b, h = (e[i].x - T) * C, g = (e[i].y - E) * w, l[0] += h * h, l[1] += h * g, l[2] += h, l[6] += h * -p * h, l[7] += h * -p * g, l[8] += h * -p, l[10] += g * g, l[11] += g, l[15] += g * -p * h, l[16] += g * -p * g, l[17] += g * -p, l[20] += 1, l[24] += -p * h, l[25] += -p * g, l[26] += -p, l[30] += h * h, l[31] += h * g, l[32] += h, l[33] += h * -m * h, l[34] += h * -m * g, l[35] += h * -m, l[40] += g * g, l[41] += g, l[42] += g * -m * h, l[43] += g * -m * g, l[44] += g * -m, l[50] += 1, l[51] += -m * h, l[52] += -m * g, l[53] += -m, l[60] += -p * h * -p * h + -m * h * -m * h, l[61] += -p * h * -p * g + -m * h * -m * g, l[62] += -p * h * -p + -m * h * -m, l[70] += -p * g * -p * g + -m * g * -m * g, l[71] += -p * g * -p + -m * g * -m, l[80] += -p * -p + -m * -m; - for (i = 0; i < 9; ++i) for (a = 0; a < i; ++a) l[i * 9 + a] = l[a * 9 + i]; - return _.eigenVV(this.mLtL, this.Evec), o[0] = u[72], o[1] = u[73], o[2] = u[74], o[3] = u[75], o[4] = u[76], o[5] = u[77], o[6] = u[78], o[7] = u[79], o[8] = u[80], v.multiply_3x3(n, this.T1, n), v.multiply_3x3(n, n, this.T0), p = 1 / o[8], o[0] *= p, o[1] *= p, o[2] *= p, o[3] *= p, o[4] *= p, o[5] *= p, o[6] *= p, o[7] *= p, o[8] = 1, 1; - } - error(e, t, n, r, i) { - let a = 0, o, s, c = 0, l = 0, u = 0, d = n.data; - for (; a < i; ++a) o = e[a], s = t[a], c = 1 / (d[6] * o.x + d[7] * o.y + 1), l = (d[0] * o.x + d[1] * o.y + d[2]) * c - s.x, u = (d[3] * o.x + d[4] * o.y + d[5]) * c - s.y, r[a] = l * l + u * u; - } - check_subset(e, t, n) { - let r = new d(); - if (n == 4) { - let n = 0, i = e[0], a = e[1], o = e[2], s = e[3], c = t[0], l = t[1], u = t[2], d = t[3], f = i.x, p = i.y, m = a.x, h = a.y, g = o.x, _ = o.y, v = c.x, y = c.y, b = l.x, x = l.y, S = u.x, C = u.y, w = r.determinant_3x3(f, p, 1, m, h, 1, g, _, 1), T = r.determinant_3x3(v, y, 1, b, x, 1, S, C, 1); - if (w * T < 0 && n++, f = a.x, p = a.y, m = o.x, h = o.y, g = s.x, _ = s.y, v = l.x, y = l.y, b = u.x, x = u.y, S = d.x, C = d.y, w = r.determinant_3x3(f, p, 1, m, h, 1, g, _, 1), T = r.determinant_3x3(v, y, 1, b, x, 1, S, C, 1), w * T < 0 && n++, f = i.x, p = i.y, m = o.x, h = o.y, g = s.x, _ = s.y, v = c.x, y = c.y, b = u.x, x = u.y, S = d.x, C = d.y, w = r.determinant_3x3(f, p, 1, m, h, 1, g, _, 1), T = r.determinant_3x3(v, y, 1, b, x, 1, S, C, 1), w * T < 0 && n++, f = i.x, p = i.y, m = a.x, h = a.y, g = s.x, _ = s.y, v = c.x, y = c.y, b = l.x, x = l.y, S = d.x, C = d.y, w = r.determinant_3x3(f, p, 1, m, h, 1, g, _, 1), T = r.determinant_3x3(v, y, 1, b, x, 1, S, C, 1), w * T < 0 && n++, n != 0 && n != 4) return !1; - } - return !0; - } -}; -k.cache = r, k.pyramid_t = class extends k { - constructor(e) { - super(), this.levels = e | 0, this.data = Array(e); - let t = new k.imgproc(); - this.pyrdown = t.pyrdown; - } - allocate(e, t, n) { - let r = this.levels; - for (; --r >= 0;) this.data[r] = new p(e >> r, t >> r, n); - } - build(e, t) { - t === void 0 && (t = !0); - let n = 2, r = e, i = this.data[0]; - if (!t) { - let t = e.cols * e.rows; - for (; --t >= 0;) i.data[t] = e.data[t]; - } - for (i = this.data[1], this.pyrdown(r, i); n < this.levels; ++n) r = i, i = this.data[n], this.pyrdown(r, i); - } -}, k.transform = m, k.matrix_t = p, k.keypoint_t = h, k.fast_corners = class extends k { - constructor() { - super(), this.offsets16 = new Int32Array([ - 0, - 3, - 1, - 3, - 2, - 2, - 3, - 1, - 3, - 0, - 3, - -1, - 2, - -2, - 1, - -3, - 0, - -3, - -1, - -3, - -2, - -2, - -3, - -1, - -3, - 0, - -3, - 1, - -2, - 2, - -1, - 3 - ]), this.threshold_tab = /* @__PURE__ */ new Uint8Array(512), this._threshold = 20, this.pixel_off = /* @__PURE__ */ new Int32Array(25), this.score_diff = /* @__PURE__ */ new Int32Array(25); - } - set_threshold(e) { - this._threshold = Math.min(Math.max(e, 0), 255); - for (let e = -255; e <= 255; ++e) this.threshold_tab[e + 255] = e < -this._threshold ? 1 : e > this._threshold ? 2 : 0; - return this._threshold; - } - detect(e, t, n) { - n === void 0 && (n = 3); - let r = e.data, i = e.cols, a = e.rows, o = 0, s = 0, c = 0, l = 0, d = 0, f = 0, p = this.cache.get_buffer(3 * i), m = this.cache.get_buffer((i + 1) * 3 << 2), h = p.u8, g = m.i32, _ = this.pixel_off, v = this.score_diff, y = Math.max(3, n), b = Math.min(a - 2, a - n), x = Math.max(3, n), S = Math.min(i - 3, i - n), C = 0, w = 0, T, E = u, D = this.threshold_tab, O = this._threshold, k = 0, A = 0, j = 0, M = 0, N = 0, P = 0, F = 0, I = 0, L = 0, R = 0, z = 0, B = 0; - this._cmp_offsets(_, i, 16); - let V = _[0], ee = _[1], H = _[2], U = _[3], W = _[4], G = _[5], K = _[6], te = _[7], q = _[8], ne = _[9], J = _[10], Y = _[11], X = _[12], Z = _[13], Q = _[14], re = _[15]; - for (o = 0; o < i * 3; ++o) h[o] = 0; - for (o = y; o < b; ++o) { - for (F = o * i + x | 0, f = (o - 3) % 3, P = f * i | 0, N = f * (i + 1) | 0, s = 0; s < i; ++s) h[P + s] = 0; - if (M = 0, o < b - 1) { - for (s = x; s < S; ++s, ++F) if (k = r[F], A = -k + 255, j = D[A + r[F + V]] | D[A + r[F + q]], j != 0 && (j &= D[A + r[F + H]] | D[A + r[F + J]], j &= D[A + r[F + W]] | D[A + r[F + X]], j &= D[A + r[F + K]] | D[A + r[F + Q]], j != 0)) { - if (j &= D[A + r[F + ee]] | D[A + r[F + ne]], j &= D[A + r[F + U]] | D[A + r[F + Y]], j &= D[A + r[F + G]] | D[A + r[F + Z]], j &= D[A + r[F + te]] | D[A + r[F + re]], j & 1) for (l = k - O, C = 0, c = 0; c < 25; ++c) if (d = r[F + _[c]], d < l) { - if (++C, C > 8) { - ++M, g[N + M] = s, h[P + s] = E(r, F, _, v, O); - break; - } - } else C = 0; - if (j & 2) for (l = k + O, C = 0, c = 0; c < 25; ++c) if (d = r[F + _[c]], d > l) { - if (++C, C > 8) { - ++M, g[N + M] = s, h[P + s] = E(r, F, _, v, O); - break; - } - } else C = 0; + if (p >= r) { + n[t] = 0; + return; + } + if (m >= r) { + n[t] = 0; + return; + } + if (p = m, u++, m = e[t + a[u]], m < r) { + c -= d + p, h = 2; + break; + } + if (m > i) { + c -= d + p, h = 7; + break; + } + n[t] = 0; + return; + case 2: + if (d > i) { + n[t] = 0; + return; + } + if (p = m, u++, m = e[t + a[u]], d < r) { + if (m > i) { + n[t] = 0; + return; } + c -= d + p, h = 4; + break; } - if (g[N + i] = M, o != y) for (f = (o - 4 + 3) % 3, I = f * i | 0, N = f * (i + 1) | 0, f = (o - 5 + 3) % 3, L = f * i | 0, M = g[N + i], c = 0; c < M; ++c) s = g[N + c], R = s + 1 | 0, z = s - 1 | 0, B = h[I + s], B > h[I + R] && B > h[I + z] && B > h[L + z] && B > h[L + s] && B > h[L + R] && B > h[P + z] && B > h[P + s] && B > h[P + R] && (T = t[w], T.x = s, T.y = o - 1, T.score = B, w++); - } - return this.cache.put_buffer(p), this.cache.put_buffer(m), w; - } - _cmp_offsets(e, t, n) { - let r = 0, i = this.offsets16; - for (; r < n; ++r) e[r] = i[r << 1] + i[(r << 1) + 1] * t; - for (; r < 25; ++r) e[r] = e[r - n]; - } -}, k.imgproc = class extends k { - constructor() { - super(); - } - grayscale(e, t, n, r, i) { - i === void 0 && (i = f.COLOR_RGBA2GRAY); - let a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = 4899, p = 9617, m = 1868, h = 4; - (i == f.COLOR_BGRA2GRAY || i == f.COLOR_BGR2GRAY) && (d = 1868, m = 4899), (i == f.COLOR_RGB2GRAY || i == f.COLOR_BGR2GRAY) && (h = 3); - let g = h << 1, _ = h * 3 | 0; - r.resize(t, n, 1); - let v = r.data; - for (o = 0; o < n; ++o, c += t, s += t * h) { - for (a = 0, l = s, u = c; a <= t - 4; a += 4, l += h << 2, u += 4) v[u] = e[l] * d + e[l + 1] * p + e[l + 2] * m + 8192 >> 14, v[u + 1] = e[l + h] * d + e[l + h + 1] * p + e[l + h + 2] * m + 8192 >> 14, v[u + 2] = e[l + g] * d + e[l + g + 1] * p + e[l + g + 2] * m + 8192 >> 14, v[u + 3] = e[l + _] * d + e[l + _ + 1] * p + e[l + _ + 2] * m + 8192 >> 14; - for (; a < t; ++a, ++u, l += h) v[u] = e[l] * d + e[l + 1] * p + e[l + 2] * m + 8192 >> 14; - } - } - resample(e, t, n, r) { - let o = e.rows, s = e.cols; - o > r && s > n && (t.resize(n, r, e.channel), e.type & f.U8_t && t.type & f.U8_t && o * s / (r * n) < 256 ? i(e, t, this.cache, n, r) : a(e, t, this.cache, n, r)); - } - box_blur_gray(e, t, n, r) { - r === void 0 && (r = 0); - let i = e.cols, a = e.rows, o = a << 1, s = i << 1, c = 0, l = 0, u = 0, d = 0, p = (n << 1) + 1 | 0, m = n + 1 | 0, h = m + 1 | 0, g = r & f.BOX_BLUR_NOSCALE ? 1 : 1 / (p * p), _ = this.cache.get_buffer(i * a << 2), v = 0, y = 0, b = 0, x = 0, S = 0, C = _.i32, w = e.data, T = 0; - for (t.resize(i, a, e.channel), u = 0; u < a; ++u) { - for (y = u, v = m * w[b], c = b + 1 | 0, d = b + n | 0; c <= d; ++c) v += w[c]; - for (x = b + m | 0, S = b, T = w[S], l = 0; l < n; ++l, y += a) C[y] = v, v += w[x] - T, x++; - for (; l < i - h; l += 2, y += o) C[y] = v, v += w[x] - w[S], C[y + a] = v, v += w[x + 1] - w[S + 1], x += 2, S += 2; - for (; l < i - m; ++l, y += a) C[y] = v, v += w[x] - w[S], x++, S++; - for (T = w[x - 1]; l < i; ++l, y += a) C[y] = v, v += T - w[S], S++; - b += i; - } - if (b = 0, w = t.data, g == 1) for (u = 0; u < i; ++u) { - for (y = u, v = m * C[b], c = b + 1 | 0, d = b + n | 0; c <= d; ++c) v += C[c]; - for (x = b + m, S = b, T = C[S], l = 0; l < n; ++l, y += i) w[y] = v, v += C[x] - T, x++; - for (; l < a - h; l += 2, y += s) w[y] = v, v += C[x] - C[S], w[y + i] = v, v += C[x + 1] - C[S + 1], x += 2, S += 2; - for (; l < a - m; ++l, y += i) w[y] = v, v += C[x] - C[S], x++, S++; - for (T = C[x - 1]; l < a; ++l, y += i) w[y] = v, v += T - C[S], S++; - b += a; - } - else for (u = 0; u < i; ++u) { - for (y = u, v = m * C[b], c = b + 1 | 0, d = b + n | 0; c <= d; ++c) v += C[c]; - for (x = b + m, S = b, T = C[S], l = 0; l < n; ++l, y += i) w[y] = v * g, v += C[x] - T, x++; - for (; l < a - h; l += 2, y += s) w[y] = v * g, v += C[x] - C[S], w[y + i] = v * g, v += C[x + 1] - C[S + 1], x += 2, S += 2; - for (; l < a - m; ++l, y += i) w[y] = v * g, v += C[x] - C[S], x++, S++; - for (T = C[x - 1]; l < a; ++l, y += i) w[y] = v * g, v += T - C[S], S++; - b += a; - } - this.cache.put_buffer(_); - } - gaussian_blur(e, t, n, r) { - let i = new k.math(); - r === void 0 && (r = 0), n === void 0 && (n = 0), n = n == 0 ? Math.max(1, 4 * r + 1 - 1e-8) * 2 + 1 | 0 : n; - let a = n >> 1, c = e.cols, l = e.rows, u = e.type, d = u & f.U8_t; - t.resize(c, l, e.channel); - let p = e.data, m = t.data, h, g, _ = n + Math.max(l, c) | 0, v = this.cache.get_buffer(_ << 2), y = this.cache.get_buffer(n << 2); - d ? (h = v.i32, g = y.i32) : u & f.S32_t ? (h = v.i32, g = y.f32) : (h = v.f32, g = y.f32), i.get_gaussian_kernel(n, r, g, u), d ? o(h, p, m, c, l, g, n, a) : s(h, p, m, c, l, g, n, a), this.cache.put_buffer(v), this.cache.put_buffer(y); - } - hough_transform(e, t, n, r) { - let i, a, o = e.data, s = e.cols, c = e.rows, l = s, u = Math.round((Math.PI - 0) / n), d = Math.round(((s + c) * 2 + 1) / t), f = 1 / t, p = new Int32Array((u + 2) * (d + 2)), m = new Float32Array(u), h = new Float32Array(u), g = 0, _ = 0; - for (; g < u; g++) m[g] = Math.sin(_) * f, h[g] = Math.cos(_) * f, _ += n; - for (a = 0; a < c; a++) for (let e = 0; e < s; e++) if (o[a * l + e] != 0) for (g = 0; g < u; g++) i = Math.round(e * h[g] + a * m[g]), i += (d - 1) / 2, p[(g + 1) * (d + 2) + i + 1] += 1; - let v = []; - for (i = 0; i < d; i++) for (g = 0; g < u; g++) { - let e = (g + 1) * (d + 2) + i + 1; - p[e] > r && p[e] > p[e - 1] && p[e] >= p[e + 1] && p[e] > p[e - d - 2] && p[e] >= p[e + d + 2] && v.push(e); - } - v.sort(function(e, t) { - return p[e] > p[t] || p[e] == p[t] && e < t; - }); - let y = Math.min(u * d, v.length), b = 1 / (d + 2), x = []; - for (a = 0; a < y; a++) { - let e = v[a]; - g = Math.floor(e * b) - 1, i = e - (g + 1) * (d + 2) - 1; - let r = (i - (d - 1) * .5) * t, o = g * n; - x.push([r, o]); - } - return x; - } - pyrdown(e, t, n, r) { - n === void 0 && (n = 0), r === void 0 && (r = 0); - let i = e.cols, a = e.rows, o = i >> 1, s = a >> 1, c = o - (n << 1), l = s - (r << 1), u = 0, d = 0, f = n + r * i, p = 0, m = 0, h = 0; - t.resize(o, s, e.channel); - let g = e.data, _ = t.data; - for (d = 0; d < l; ++d) { - for (p = f, h = m, u = 0; u <= c - 2; u += 2, h += 2, p += 4) _[h] = g[p] + g[p + 1] + g[p + i] + g[p + i + 1] + 2 >> 2, _[h + 1] = g[p + 2] + g[p + 3] + g[p + i + 2] + g[p + i + 3] + 2 >> 2; - for (; u < c; ++u, ++h, p += 2) _[h] = g[p] + g[p + 1] + g[p + i] + g[p + i + 1] + 2 >> 2; - f += i << 1, m += o; - } - } - scharr_derivatives(e, t) { - let n = e.cols, r = e.rows, i = n << 1, a = 0, o = 0, s = 0, c, l, u, d, p, m, h = 0, g = 0, _ = 0, v = 0, y, b; - t.resize(n, r, 2); - let x = e.data, S = t.data, C = this.cache.get_buffer(n + 2 << 2), w = this.cache.get_buffer(n + 2 << 2); - for (e.type & f.U8_t || e.type & f.S32_t ? (y = C.i32, b = w.i32) : (y = C.f32, b = w.f32); o < r; ++o, g += n) { - for (h = (o > 0 ? o - 1 : 1) * n | 0, _ = (o < r - 1 ? o + 1 : r - 2) * n | 0, v = o * i | 0, a = 0, s = 1; a <= n - 2; a += 2, s += 2) c = x[h + a], l = x[_ + a], y[s] = (c + l) * 3 + x[g + a] * 10, b[s] = l - c, c = x[h + a + 1], l = x[_ + a + 1], y[s + 1] = (c + l) * 3 + x[g + a + 1] * 10, b[s + 1] = l - c; - for (; a < n; ++a, ++s) c = x[h + a], l = x[_ + a], y[s] = (c + l) * 3 + x[g + a] * 10, b[s] = l - c; - for (a = n + 1 | 0, y[0] = y[1], y[a] = y[n], b[0] = b[1], b[a] = b[n], a = 0; a <= n - 4; a += 4) c = b[a + 2], l = b[a + 1], u = b[a + 3], d = b[a + 4], p = y[a + 2], m = y[a + 3], S[v++] = p - y[a], S[v++] = (c + b[a]) * 3 + l * 10, S[v++] = m - y[a + 1], S[v++] = (u + l) * 3 + c * 10, S[v++] = y[a + 4] - p, S[v++] = (d + c) * 3 + u * 10, S[v++] = y[a + 5] - m, S[v++] = (b[a + 5] + u) * 3 + d * 10; - for (; a < n; ++a) S[v++] = y[a + 2] - y[a], S[v++] = (b[a + 2] + b[a]) * 3 + b[a + 1] * 10; - } - this.cache.put_buffer(C), this.cache.put_buffer(w); - } - sobel_derivatives(e, t) { - let n = e.cols, r = e.rows, i = n << 1, a = 0, o = 0, s = 0, c, l, u, d, p, m, h = 0, g = 0, _ = 0, v = 0, y, b; - t.resize(n, r, 2); - let x = e.data, S = t.data, C = this.cache.get_buffer(n + 2 << 2), w = this.cache.get_buffer(n + 2 << 2); - for (e.type & f.U8_t || e.type & f.S32_t ? (y = C.i32, b = w.i32) : (y = C.f32, b = w.f32); o < r; ++o, g += n) { - for (h = (o > 0 ? o - 1 : 1) * n | 0, _ = (o < r - 1 ? o + 1 : r - 2) * n | 0, v = o * i | 0, a = 0, s = 1; a <= n - 2; a += 2, s += 2) c = x[h + a], l = x[_ + a], y[s] = c + l + x[g + a] * 2, b[s] = l - c, c = x[h + a + 1], l = x[_ + a + 1], y[s + 1] = c + l + x[g + a + 1] * 2, b[s + 1] = l - c; - for (; a < n; ++a, ++s) c = x[h + a], l = x[_ + a], y[s] = c + l + x[g + a] * 2, b[s] = l - c; - for (a = n + 1 | 0, y[0] = y[1], y[a] = y[n], b[0] = b[1], b[a] = b[n], a = 0; a <= n - 4; a += 4) c = b[a + 2], l = b[a + 1], u = b[a + 3], d = b[a + 4], p = y[a + 2], m = y[a + 3], S[v++] = p - y[a], S[v++] = c + b[a] + l * 2, S[v++] = m - y[a + 1], S[v++] = u + l + c * 2, S[v++] = y[a + 4] - p, S[v++] = d + c + u * 2, S[v++] = y[a + 5] - m, S[v++] = b[a + 5] + u + d * 2; - for (; a < n; ++a) S[v++] = y[a + 2] - y[a], S[v++] = b[a + 2] + b[a] + b[a + 1] * 2; - } - this.cache.put_buffer(C), this.cache.put_buffer(w); - } - compute_integral_image(e, t, n, r) { - let i = e.cols | 0, a = e.rows | 0, o = e.data, s = i + 1 | 0, c = 0, l = 0, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0; - if (t && n) { - for (; f < s; ++f) t[f] = 0, n[f] = 0; - for (u = s + 1 | 0, d = 1, f = 0, h = 0; f < a; ++f, ++u, ++d) { - for (c = l = 0, p = 0; p <= i - 2; p += 2, h += 2, u += 2, d += 2) m = o[h], c += m, l += m * m, t[u] = t[d] + c, n[u] = n[d] + l, m = o[h + 1], c += m, l += m * m, t[u + 1] = t[d + 1] + c, n[u + 1] = n[d + 1] + l; - for (; p < i; ++p, ++h, ++u, ++d) m = o[h], c += m, l += m * m, t[u] = t[d] + c, n[u] = n[d] + l; + if (m > i) { + c -= d + p, h = 7; + break; } - } else if (t) { - for (; f < s; ++f) t[f] = 0; - for (u = s + 1 | 0, d = 1, f = 0, h = 0; f < a; ++f, ++u, ++d) { - for (c = 0, p = 0; p <= i - 2; p += 2, h += 2, u += 2, d += 2) c += o[h], t[u] = t[d] + c, c += o[h + 1], t[u + 1] = t[d + 1] + c; - for (; p < i; ++p, ++h, ++u, ++d) c += o[h], t[u] = t[d] + c; + if (m < r) { + c -= d + p, h = 2; + break; } - } else if (n) { - for (; f < s; ++f) n[f] = 0; - for (u = s + 1 | 0, d = 1, f = 0, h = 0; f < a; ++f, ++u, ++d) { - for (l = 0, p = 0; p <= i - 2; p += 2, h += 2, u += 2, d += 2) m = o[h], l += m * m, n[u] = n[d] + l, m = o[h + 1], l += m * m, n[u + 1] = n[d + 1] + l; - for (; p < i; ++p, ++h, ++u, ++d) m = o[h], l += m * m, n[u] = n[d] + l; + n[t] = 0; + return; + case 3: + if (d < r) { + n[t] = 0; + return; } - } - if (r) { - for (f = 0; f < s; ++f) r[f] = 0; - for (u = s + 1 | 0, d = 0, f = 0, h = 0; f < a; ++f, ++u, ++d) { - for (p = 0; p <= i - 2; p += 2, h += 2, u += 2, d += 2) r[u] = o[h] + r[d], r[u + 1] = o[h + 1] + r[d + 1]; - for (; p < i; ++p, ++h, ++u, ++d) r[u] = o[h] + r[d]; + if (p = m, u++, m = e[t + a[u]], d > i) { + if (m < r) { + n[t] = 0; + return; + } + c -= d + p, h = 5; + break; } - for (u = s + i | 0, d = i, f = 0; f < a; ++f, u += s, d += s) r[u] += r[d]; - for (p = i - 1; p > 0; --p) for (u = p + a * s, d = u - s, f = a; f > 0; --f, u -= s, d -= s) r[u] += r[d] + r[d + 1]; - } - } - equalize_histogram(e, t) { - let n = e.cols, r = e.rows, i = e.data; - t.resize(n, r, e.channel); - let a = t.data, o = n * r, s = 0, c = 0, l, u, d = this.cache.get_buffer(1024); - for (l = d.i32; s < 256; ++s) l[s] = 0; - for (s = 0; s < o; ++s) ++l[i[s]]; - for (c = l[0], s = 1; s < 256; ++s) c = l[s] += c; - for (u = 255 / o, s = 0; s < o; ++s) a[s] = l[i[s]] * u + .5 | 0; - this.cache.put_buffer(d); - } - canny(e, t, n, r) { - let i = e.cols, a = e.rows; - e.data, t.resize(i, a, e.channel); - let o = t.data, s = 0, c = 0, l = 0, u = i << 1, d = 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = this.cache.get_buffer(a * u << 2), S = this.cache.get_buffer(3 * (i + 2) << 2), C = this.cache.get_buffer((a + 2) * (i + 2) << 2), w = this.cache.get_buffer(a * i << 2), T = S.i32, E = C.i32, D = w.i32, O = x.i32, k = new p(i, a, f.S32C2_t, x.data), A = 1, j = i + 2 + 1 | 0, M = 2 * (i + 2) + 1 | 0, N = i + 2 | 0, P = N + 1 | 0, F = 0; - for (this.sobel_derivatives(e, k), n > r && (s = n, n = r, r = s), s = 3 * (i + 2) | 0; --s >= 0;) T[s] = 0; - for (s = (a + 2) * (i + 2) | 0; --s >= 0;) E[s] = 0; - for (; c < i; ++c, l += 2) g = O[l], _ = O[l + 1], T[j + c] = (g ^ g >> 31) - (g >> 31) + ((_ ^ _ >> 31) - (_ >> 31)); - for (s = 1; s <= a; ++s, l += u) { - if (s == a) for (c = M + i; --c >= M;) T[c] = 0; - else for (c = 0; c < i; c++) g = O[l + (c << 1)], _ = O[l + (c << 1) + 1], T[M + c] = (g ^ g >> 31) - (g >> 31) + ((_ ^ _ >> 31) - (_ >> 31)); - for (d = l - u | 0, E[P - 1] = 0, m = 0, c = 0; c < i; ++c, d += 2) { - if (h = T[j + c], h > n) { - if (g = O[d], _ = O[d + 1], v = g ^ _, g = (g ^ g >> 31) - (g >> 31) | 0, _ = (_ ^ _ >> 31) - (_ >> 31) | 0, y = g * 13573, b = y + (g + g << 15), _ <<= 15, _ < y) { - if (h > T[j + c - 1] && h >= T[j + c + 1]) { - h > r && !m && E[P + c - N] != 2 ? (E[P + c] = 2, m = 1, D[F++] = P + c) : E[P + c] = 1; - continue; - } - } else if (_ > b) { - if (h > T[A + c] && h >= T[M + c]) { - h > r && !m && E[P + c - N] != 2 ? (E[P + c] = 2, m = 1, D[F++] = P + c) : E[P + c] = 1; - continue; - } - } else if (v = v < 0 ? -1 : 1, h > T[A + c - v] && h > T[M + c + v]) { - h > r && !m && E[P + c - N] != 2 ? (E[P + c] = 2, m = 1, D[F++] = P + c) : E[P + c] = 1; - continue; - } + if (m > i) { + c -= d + p, h = 3; + break; + } + if (m < r) { + c -= d + p, h = 6; + break; + } + n[t] = 0; + return; + case 4: + if (d > i) { + n[t] = 0; + return; + } + if (d < r) { + if (p = m, u++, m = e[t + a[u]], m > i) { + n[t] = 0; + return; } - E[P + c] = 0, m = 0; + c -= d + p, h = 1; + break; } - E[P + i] = 0, P += N, c = A, A = j, j = M, M = c; - } - for (c = P - N - 1, s = 0; s < N; ++s, ++c) E[c] = 0; - for (; F > 0;) P = D[--F], P -= N + 1, E[P] == 1 && (E[P] = 2, D[F++] = P), P += 1, E[P] == 1 && (E[P] = 2, D[F++] = P), P += 1, E[P] == 1 && (E[P] = 2, D[F++] = P), P += N, E[P] == 1 && (E[P] = 2, D[F++] = P), P -= 2, E[P] == 1 && (E[P] = 2, D[F++] = P), P += N, E[P] == 1 && (E[P] = 2, D[F++] = P), P += 1, E[P] == 1 && (E[P] = 2, D[F++] = P), P += 1, E[P] == 1 && (E[P] = 2, D[F++] = P); - for (P = N + 1, A = 0, s = 0; s < a; ++s, P += N) for (c = 0; c < i; ++c) o[A++] = Number(E[P + c] == 2) * 255; - this.cache.put_buffer(x), this.cache.put_buffer(S), this.cache.put_buffer(C), this.cache.put_buffer(w); - } - warp_perspective(e, t, n, r) { - r === void 0 && (r = 0); - let i = e.cols | 0, a = e.rows | 0, o = t.cols | 0, s = t.rows | 0, c = e.data, l = t.data, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = 0, S = 0, C = 0, w = 0, T = n.data, E = T[0], D = T[1], O = T[2], k = T[3], A = T[4], j = T[5], M = T[6], N = T[7], P = T[8]; - for (let e = 0; d < s; ++d) for (_ = D * d + O, v = A * d + j, y = N * d + P, u = 0; u < o; ++u, ++e, _ += E, v += k, y += M) b = 1 / y, h = _ * b, g = v * b, p = h | 0, m = g | 0, h > 0 && g > 0 && p < i - 1 && m < a - 1 ? (x = Math.max(h - p, 0), S = Math.max(g - m, 0), f = i * m + p | 0, C = c[f] + x * (c[f + 1] - c[f]), w = c[f + i] + x * (c[f + i + 1] - c[f + i]), l[e] = C + S * (w - C)) : l[e] = r; - } - warp_affine(e, t, n, r) { - r === void 0 && (r = 0); - let i = e.cols, a = e.rows, o = t.cols, s = t.rows, c = e.data, l = t.data, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = n.data, S = x[0], C = x[1], w = x[2], T = x[3], E = x[4], D = x[5]; - for (let e = 0; d < s; ++d) for (h = C * d + w, g = E * d + D, u = 0; u < o; ++u, ++e, h += S, g += T) p = h | 0, m = g | 0, p >= 0 && m >= 0 && p < i - 1 && m < a - 1 ? (_ = h - p, v = g - m, f = i * m + p, y = c[f] + _ * (c[f + 1] - c[f]), b = c[f + i] + _ * (c[f + i + 1] - c[f + i]), l[e] = y + v * (b - y)) : l[e] = r; - } - skindetector(e, t) { - let n, r, i, a, o = e.width * e.height; - for (; o--;) a = o * 4, n = e.data[a], r = e.data[a + 1], i = e.data[a + 2], n > 95 && r > 40 && i > 20 && n > r && n > i && n - Math.min(r, i) > 15 && Math.abs(n - r) > 15 ? t[o] = 255 : t[o] = 0; - } -}, k.math = class extends k { - constructor() { - super(), this.qsort_stack = /* @__PURE__ */ new Int32Array(96); - } - get_gaussian_kernel(e, t, n, r) { - let i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = this.cache.get_buffer(e << 2), d = u.f32; - if ((e & 1) == 1 && e <= 7 && t <= 0) switch (e >> 1) { - case 0: - d[0] = 1, l = 1; + if (m >= r) { + n[t] = 0; + return; + } + if (p = m, u++, m = e[t + a[u]], m < r) { + c -= d + p, h = 2; break; - case 1: - d[0] = .25, d[1] = .5, d[2] = .25, l = 1; + } + if (m > i) { + c -= d + p, h = 7; break; - case 2: - d[0] = .0625, d[1] = .25, d[2] = .375, d[3] = .25, d[4] = .0625, l = 1; + } + n[t] = 0; + return; + case 5: + if (d < r) { + n[t] = 0; + return; + } + if (d > i) { + if (p = m, u++, m = e[t + a[u]], m < r) { + n[t] = 0; + return; + } + c -= d + p, h = 0; + break; + } + if (m <= i) { + n[t] = 0; + return; + } + if (p = m, u++, m = e[t + a[u]], m > i) { + c -= d + p, h = 3; + break; + } + if (m < r) { + c -= d + p, h = 6; + break; + } + n[t] = 0; + return; + case 7: + if (d > i) { + n[t] = 0; + return; + } + if (d < r) { + n[t] = 0; + return; + } + if (p = m, u++, m = e[t + a[u]], m > i) { + c -= d + p, h = 3; break; - case 3: - d[0] = .03125, d[1] = .109375, d[2] = .21875, d[3] = .28125, d[4] = .21875, d[5] = .109375, d[6] = .03125, l = 1; + } + if (m < r) { + c -= d + p, h = 6; break; - } - else for (s = t > 0 ? t : ((e - 1) * .5 - 1) * .3 + .8, c = -.5 / (s * s); i < e; ++i) a = i - (e - 1) * .5, o = Math.exp(c * a * a), d[i] = o, l += o; - if (r & f.U8_t) for (l = 256 / l, i = 0; i < e; ++i) n[i] = d[i] * l + .5 | 0; - else for (l = 1 / l, i = 0; i < e; ++i) n[i] = d[i] * l; - this.cache.put_buffer(u); - } - perspective_4point_transform(e, t, n, r, i, a, o, s, c, l, u, d, f, p, m, h, g) { - console.warn("⚠️⚠️⚠️ This method is deprecated ad will be removed in the next releases, use transform.perspective_4point_transform() instead. ⚠️⚠️⚠️"); - let _ = t, v = l, y = o, b = _ * v * y, x = m, S = _ * x, C = v * S, w = u, T = _ * w, E = a, D = n, O = p, k = D * O, A = k * E, j = O * E * w, M = O * y, N = O * w, P = v * y, F = x * v, I = x * E, L = w * E, R = 1 / (M - N - P + F - I + L), z = _ * O, B = D * E, V = y * _, ee = x * V, H = D * v, U = k * w, W = D * w * E, G = y * x * v, K = x * D, te = -(C - b + T * E - E * S - k * v + A - j + M * v) * R, q = (b - C - z * y + z * w + A - v * B + I * v - j) * R, ne = _, J = (-w * S + ee + H * y - k * y + U - W + I * w - G) * R, Y = (-ee + V * w - K * v + U - W + K * E + G - M * w) * R, X = D, Z = (-T + V + H - B + N - M - F + I) * R, Q = (-S + T + k - H + I - L - M + P) * R; - _ = r, v = d, y = c, b = _ * v * y, x = g, S = _ * x, C = v * S, w = f, T = _ * w, E = s, D = i, O = h, k = D * O, A = k * E, j = O * E * w, M = O * y, N = O * w, P = v * y, F = x * v, I = x * E, L = w * E, R = 1 / (M - N - P + F - I + L), z = _ * O, B = D * E, V = y * _, ee = x * V, H = D * v, U = k * w, W = D * w * E, G = y * x * v, K = x * D; - let re = -(C - b + T * E - E * S - k * v + A - j + M * v) * R, ie = (b - C - z * y + z * w + A - v * B + I * v - j) * R, ae = _, oe = (-w * S + ee + H * y - k * y + U - W + I * w - G) * R, se = (-ee + V * w - K * v + U - W + K * E + G - M * w) * R, ce = D, le = (-T + V + H - B + N - M - F + I) * R, ue = (-S + T + k - H + I - L - M + P) * R; - v = Y - Q * X, y = te * Y, b = te * X, S = J * q, C = ne * J, T = q * Z; - let de = ne * Z; - O = 1 / (y - b * Q - S + C * Q + T * X - de * Y), A = -J + X * Z; - let fe = -J * Q + Y * Z; - L = -q + ne * Q; - let pe = te - de; - B = te * Q - T, V = -q * X + ne * Y; - let me = b - C, he = y - S; - W = v * O; - let ge = L * O, _e = V * O, $ = e.data; - $[0] = re * W + A * O * ie - fe * O * ae, $[1] = re * ge + pe * O * ie - B * O * ae, $[2] = -re * _e - me * O * ie + he * O * ae, $[3] = oe * W + A * O * se - fe * O * ce, $[4] = oe * ge + pe * O * se - B * O * ce, $[5] = -oe * _e - me * O * se + he * O * ce, $[6] = le * W + A * O * ue - fe * O, $[7] = le * ge + pe * O * ue - B * O, $[8] = -le * _e - me * O * ue + he * O; - } - qsort(e, t, n, r) { - let i, a, o, s, c = 0, l = 0, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = this.qsort_stack; - if (!(n - t + 1 <= 1)) for (E[0] = t, E[1] = n; c >= 0;) for (l = E[c << 1], u = E[(c << 1) + 1], c--;;) if (f = u - l + 1, f <= 7) { - for (m = l + 1; m <= u; m++) for (h = m; h > l && r(e[h], e[h - 1]); h--) i = e[h], e[h] = e[h - 1], e[h - 1] = i; - break; - } else { - for (T = 0, _ = l, y = u, x = l + (f >> 1), f > 40 && (g = f >> 3, S = l, C = l + g, w = l + (g << 1), a = e[S], o = e[C], s = e[w], l = r(a, o) ? r(o, s) ? C : r(a, s) ? w : S : r(s, o) ? C : r(a, s) ? S : w, S = x - g, C = x, w = x + g, a = e[S], o = e[C], s = e[w], x = r(a, o) ? r(o, s) ? C : r(a, s) ? w : S : r(s, o) ? C : r(a, s) ? S : w, S = u - (g << 1), C = u - g, w = u, a = e[S], o = e[C], s = e[w], u = r(a, o) ? r(o, s) ? C : r(a, s) ? w : S : r(s, o) ? C : r(a, s) ? S : w), S = l, C = x, w = u, a = e[S], o = e[C], s = e[w], x = r(a, o) ? r(o, s) ? C : r(a, s) ? w : S : r(s, o) ? C : r(a, s) ? S : w, x != _ && (i = e[x], e[x] = e[_], e[_] = i, x = _), l = v = _ + 1, u = b = y, a = e[x];;) { - for (; l <= u && !r(a, e[l]);) r(e[l], a) || (l > v && (i = e[v], e[v] = e[l], e[l] = i), T = 1, v++), l++; - for (; l <= u && !r(e[u], a);) r(a, e[u]) || (u < b && (i = e[b], e[b] = e[u], e[u] = i), T = 1, b--), u--; - if (l > u) break; - i = e[l], e[l] = e[u], e[u] = i, T = 1, l++, u--; } - if (T == 0) { - for (l = _, u = y, m = l + 1; m <= u; m++) for (h = m; h > l && r(e[h], e[h - 1]); h--) i = e[h], e[h] = e[h - 1], e[h - 1] = i; + n[t] = 0; + return; + case 6: + if (d > i) { + n[t] = 0; + return; + } + if (d < r) { + n[t] = 0; + return; + } + if (p = m, u++, m = e[t + a[u]], m < r) { + c -= d + p, h = 2; break; } - for (f = Math.min(v - _, l - v), p = l - f | 0, d = 0; d < f; ++d, ++p) i = e[_ + d], e[_ + d] = e[p], e[p] = i; - for (f = Math.min(y - b, b - u), p = y - f + 1 | 0, d = 0; d < f; ++d, ++p) i = e[l + d], e[l + d] = e[p], e[p] = i; - if (f = l - v, p = b - u, f > 1) p > 1 ? f > p ? (++c, E[c << 1] = _, E[(c << 1) + 1] = _ + f - 1, l = y - p + 1, u = y) : (++c, E[c << 1] = y - p + 1, E[(c << 1) + 1] = y, l = _, u = _ + f - 1) : (l = _, u = _ + f - 1); - else if (p > 1) l = y - p + 1, u = y; - else break; - } - } - median(e, t, n) { - let r, i = 0, a = 0, o = 0, s = t + n >> 1; - for (;;) { - if (n <= t) return e[s]; - if (n == t + 1) return e[t] > e[n] && (r = e[t], e[t] = e[n], e[n] = r), e[s]; - for (i = t + n >> 1, e[i] > e[n] && (r = e[i], e[i] = e[n], e[n] = r), e[t] > e[n] && (r = e[t], e[t] = e[n], e[n] = r), e[i] > e[t] && (r = e[i], e[i] = e[t], e[t] = r), a = t + 1, r = e[i], e[i] = e[a], e[a] = r, o = n;;) { - do - ++a; - while (e[t] > e[a]); - do - --o; - while (e[o] > e[t]); - if (o < a) break; - r = e[a], e[a] = e[o], e[o] = r; + if (m > i) { + c -= d + p, h = 7; + break; } - r = e[t], e[t] = e[o], e[o] = r, o <= s ? t = a : o >= s && (n = o - 1); - } - return 0; - } -}, k.matmath = d, k.linalg = class extends k { - constructor() { - super(), this.matmath = new d(); - } - JacobiImpl(e, t, n, r, i, a) { - let o = f.EPSILON, s = 0, u = 0, d = 0, p = 0, m = 0, h = 0, g = 0, _ = 0, v = 0, y = a * a * 30, b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = 0, D = 0, O = 0, k = this.cache.get_buffer(a << 2), A = this.cache.get_buffer(a << 2), j = k.i32, M = A.i32; - if (r) for (; s < a; s++) { - for (d = s * i, u = 0; u < a; u++) r[d + u] = 0; - r[d + s] = 1; - } - for (d = 0; d < a; d++) { - if (n[d] = e[(t + 1) * d], d < a - 1) { - for (p = d + 1, b = Math.abs(e[t * d + p]), s = d + 2; s < a; s++) x = Math.abs(e[t * d + s]), b < x && (b = x, p = s); - j[d] = p; - } - if (d > 0) { - for (p = 0, b = Math.abs(e[d]), s = 1; s < d; s++) x = Math.abs(e[t * s + d]), b < x && (b = x, p = s); - M[d] = p; - } - } - if (a > 1) for (; v < y; v++) { - for (d = 0, b = Math.abs(e[j[0]]), s = 1; s < a - 1; s++) x = Math.abs(e[t * s + j[s]]), b < x && (b = x, d = s); - for (m = j[d], s = 1; s < a; s++) x = Math.abs(e[t * M[s] + s]), b < x && (b = x, d = M[s], m = s); - if (S = e[t * d + m], Math.abs(S) <= o) break; - for (C = (n[m] - n[d]) * .5, w = Math.abs(C) + l(S, C), T = l(S, w), E = w / T, T = S / T, w = S / w * S, C < 0 && (T = -T, w = -w), e[t * d + m] = 0, n[d] -= w, n[m] += w, s = 0; s < d; s++) g = t * s + d, _ = t * s + m, D = e[g], O = e[_], e[g] = D * E - O * T, e[_] = D * T + O * E; - for (s = d + 1; s < m; s++) g = t * d + s, _ = t * s + m, D = e[g], O = e[_], e[g] = D * E - O * T, e[_] = D * T + O * E; - for (s = m + 1, g = t * d + s, _ = t * m + s; s < a; s++, g++, _++) D = e[g], O = e[_], e[g] = D * E - O * T, e[_] = D * T + O * E; - if (r) for (g = i * d, _ = i * m, s = 0; s < a; s++, g++, _++) D = r[g], O = r[_], r[g] = D * E - O * T, r[_] = D * T + O * E; - for (u = 0; u < 2; u++) { - if (h = u == 0 ? d : m, h < a - 1) { - for (p = h + 1, b = Math.abs(e[t * h + p]), s = h + 2; s < a; s++) x = Math.abs(e[t * h + s]), b < x && (b = x, p = s); - j[h] = p; + n[t] = 0; + return; + case 8: + if (d > i) { + if (m < r) { + n[t] = 0; + return; } - if (h > 0) { - for (p = 0, b = Math.abs(e[h]), s = 1; s < h; s++) x = Math.abs(e[t * s + h]), b < x && (b = x, p = s); - M[h] = p; + if (p = m, u++, m = e[t + a[u]], m < r) { + n[t] = 0; + return; } + c -= d + p, h = 9; + break; } - } - for (d = 0; d < a - 1; d++) { - for (p = d, s = d + 1; s < a; s++) n[p] < n[s] && (p = s); - if (d != p && (c(n, p, d, b), r)) for (s = 0; s < a; s++) c(r, i * p + s, i * d + s, b); - } - this.cache.put_buffer(k), this.cache.put_buffer(A); - } - JacobiSVDImpl(e, t, n, r, i, a, o, s) { - let u = f.EPSILON * 2, d = f.FLT_MIN, p = 0, m = 0, h = 0, g = 0, _ = Math.max(a, 30), v = 0, y = 0, b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = 0, D = 0, O = 0, k = 0, A = 0, j = 0, M = 0, N = 0, P = 0, F = 4660, I = 0, L = 0, R = 0, z = this.cache.get_buffer(o << 3), B = z.f64; - for (; p < o; p++) { - for (h = 0, O = 0; h < a; h++) T = e[p * t + h], O += T * T; - if (B[p] = O, r) { - for (h = 0; h < o; h++) r[p * i + h] = 0; - r[p * i + p] = 1; - } - } - for (; g < _; g++) { - for (S = 0, p = 0; p < o - 1; p++) for (m = p + 1; m < o; m++) { - for (v = p * t | 0, y = m * t | 0, M = B[p], N = 0, P = B[m], h = 2, N += e[v] * e[y], N += e[v + 1] * e[y + 1]; h < a; h++) N += e[v + h] * e[y + h]; - if (!(Math.abs(N) <= u * Math.sqrt(M * P))) { - for (N *= 2, k = M - P, A = l(N, k), k < 0 ? (j = (A - k) * .5, w = Math.sqrt(j / A), C = N / (A * w * 2)) : (C = Math.sqrt((A + k) / (A * 2)), w = N / (A * C * 2)), M = 0, P = 0, h = 2, E = C * e[v] + w * e[y], D = -w * e[v] + C * e[y], e[v] = E, e[y] = D, M += E * E, P += D * D, E = C * e[v + 1] + w * e[y + 1], D = -w * e[v + 1] + C * e[y + 1], e[v + 1] = E, e[y + 1] = D, M += E * E, P += D * D; h < a; h++) E = C * e[v + h] + w * e[y + h], D = -w * e[v + h] + C * e[y + h], e[v + h] = E, e[y + h] = D, M += E * E, P += D * D; - if (B[p] = M, B[m] = P, S = 1, r) for (b = p * i | 0, x = m * i | 0, h = 2, E = C * r[b] + w * r[x], D = -w * r[b] + C * r[x], r[b] = E, r[x] = D, E = C * r[b + 1] + w * r[x + 1], D = -w * r[b + 1] + C * r[x + 1], r[b + 1] = E, r[x + 1] = D; h < o; h++) E = C * r[b + h] + w * r[x + h], D = -w * r[b + h] + C * r[x + h], r[b + h] = E, r[x + h] = D; + if (d < r) { + if (p = m, u++, m = e[t + a[u]], m > i) { + n[t] = 0; + return; } + c -= d + p, h = 1; + break; } - if (S == 0) break; - } - for (p = 0; p < o; p++) { - for (h = 0, O = 0; h < a; h++) T = e[p * t + h], O += T * T; - B[p] = Math.sqrt(O); - } - for (p = 0; p < o - 1; p++) { - for (m = p, h = p + 1; h < o; h++) B[m] < B[h] && (m = h); - if (p != m && (c(B, p, m, O), r)) { - for (h = 0; h < a; h++) c(e, p * t + h, m * t + h, T); - for (h = 0; h < o; h++) c(r, p * i + h, m * i + h, T); - } - } - for (p = 0; p < o; p++) n[p] = B[p]; - if (!r) { - this.cache.put_buffer(z); + n[t] = 0; return; - } - for (p = 0; p < s; p++) { - for (O = p < o ? B[p] : 0; O <= d;) { - for (L = 1 / a, h = 0; h < a; h++) F = F * 214013 + 2531011, I = F >> 16 & 256 ? L : -L, e[p * t + h] = I; - for (g = 0; g < 2; g++) for (m = 0; m < p; m++) { - for (O = 0, h = 0; h < a; h++) O += e[p * t + h] * e[m * t + h]; - for (R = 0, h = 0; h < a; h++) T = e[p * t + h] - O * e[m * t + h], e[p * t + h] = T, R += Math.abs(T); - for (R = R ? 1 / R : 0, h = 0; h < a; h++) e[p * t + h] *= R; + case 9: + if (d < r) { + if (m > i) { + n[t] = 0; + return; } - for (O = 0, h = 0; h < a; h++) T = e[p * t + h], O += T * T; - O = Math.sqrt(O); - } - for (w = 1 / O, h = 0; h < a; h++) e[p * t + h] *= w; - } - this.cache.put_buffer(z); - } - lu_solve(e, t) { - let n = 0, r = 0, i = 0, a = 1, o = e.cols, s = e.data, l = t.data, u, d, p; - for (n = 0; n < o; n++) { - for (i = n, r = n + 1; r < o; r++) Math.abs(s[r * o + n]) > Math.abs(s[i * o + n]) && (i = r); - if (Math.abs(s[i * o + n]) < f.EPSILON) return 0; - if (i != n) { - for (r = n; r < o; r++) c(s, n * o + r, i * o + r, void 0); - c(l, n, i, void 0), a = -a; - } - for (d = -1 / s[n * o + n], r = n + 1; r < o; r++) { - for (u = s[r * o + n] * d, i = n + 1; i < o; i++) s[r * o + i] += u * s[n * o + i]; - l[r] += u * l[n]; + if (p = m, u++, m = e[t + a[u]], m > i) { + n[t] = 0; + return; + } + c -= d + p, h = 8; + break; } - s[n * o + n] = -d; - } - for (n = o - 1; n >= 0; n--) { - for (p = l[n], i = n + 1; i < o; i++) p -= s[n * o + i] * l[i]; - l[n] = p * s[n * o + n]; - } - return 1; - } - cholesky_solve(e, t) { - let n = 0, r = 0, i = 0, a = 0, o = 0, s = 0, c = 0, l = e.cols, u = e.data, d = t.data, f, p; - for (n = 0; n < l; n++) for (p = 1, a = n * l, o = a, r = n; r < l; r++) { - for (f = u[o + n], i = 0; i < n; i++) f -= u[i * l + n] * u[o + i]; - if (r == n) { - if (u[o + n] = f, f == 0) return 0; - p = 1 / f; - } else u[a + r] = f, u[o + n] = f * p; - o += l; - } - for (a = 0, s = 0; s < l; s++) { - for (f = d[s], c = 0; c < s; c++) f -= u[a + c] * d[c]; - d[s] = f, a += l; - } - for (a = 0, s = 0; s < l; s++) d[s] /= u[a + s], a += l; - for (s = l - 1; s >= 0; s--) { - for (f = d[s], c = s + 1, a = c * l; c < l; c++) f -= u[a + s] * d[c], a += l; - d[s] = f; - } - return 1; - } - svd_decompose(e, t, n, r, i) { - i === void 0 && (i = 0); - let a = 0, o = 0, s = e.rows, c = e.cols, l = s, u = c, d = e.type | f.C1_t; - l < u && (a = 1, o = l, l = u, u = o); - let m = this.cache.get_buffer(l * l << 3), h = this.cache.get_buffer(u << 3), g = this.cache.get_buffer(u * u << 3), _ = new p(l, l, d, m.data), v = new p(1, u, d, h.data), y = new p(u, u, d, g.data); - if (a == 0) this.matmath.transpose(_, e); - else { - for (o = 0; o < c * s; o++) _.data[o] = e.data[o]; - for (; o < u * l; o++) _.data[o] = 0; - } - if (this.JacobiSVDImpl(_.data, l, v.data, y.data, u, l, u, l), t) { - for (o = 0; o < u; o++) t.data[o] = v.data[o]; - for (; o < c; o++) t.data[o] = 0; - } - if (a == 0) { - if (n && i & f.SVD_U_T) for (o = l * l; --o >= 0;) n.data[o] = _.data[o]; - else n && this.matmath.transpose(n, _); - if (r && i & f.SVD_V_T) for (o = u * u; --o >= 0;) r.data[o] = y.data[o]; - else r && this.matmath.transpose(r, y); - } else { - if (n && i & f.SVD_U_T) for (o = u * u; --o >= 0;) n.data[o] = y.data[o]; - else n && this.matmath.transpose(n, y); - if (r && i & f.SVD_V_T) for (o = l * l; --o >= 0;) r.data[o] = _.data[o]; - else r && this.matmath.transpose(r, _); - } - this.cache.put_buffer(m), this.cache.put_buffer(h), this.cache.put_buffer(g); - } - svd_solve(e, t, n) { - let r = 0, i = 0, a = 0, o = 0, s = 0, c = e.rows, l = e.cols, u = 0, d = 0, m = 0, h = e.type | f.C1_t, g = this.cache.get_buffer(c * c << 3), _ = this.cache.get_buffer(l << 3), v = this.cache.get_buffer(l * l << 3), y = new p(c, c, h, g.data), b = new p(1, l, h, _.data), x = new p(l, l, h, v.data), S = n.data, C = y.data, w = b.data, T = x.data; - for (this.svd_decompose(e, b, y, x, 0), m = f.EPSILON * w[0] * l; r < l; r++, s += l) { - for (d = 0, i = 0; i < l; i++) if (w[i] > m) { - for (a = 0, u = 0, o = 0; a < c; a++, o += l) u += C[o + i] * S[a]; - d += u * T[s + i] / w[i]; + if (d > i) { + if (p = m, u++, m = e[t + a[u]], m < r) { + n[t] = 0; + return; + } + c -= d + p, h = 0; + break; } - t.data[r] = d; - } - this.cache.put_buffer(g), this.cache.put_buffer(_), this.cache.put_buffer(v); - } - svd_invert(e, t) { - let n = 0, r = 0, i = 0, a = 0, o = 0, s = 0, c = t.rows, l = t.cols, u = 0, d = 0, m = t.type | f.C1_t, h = this.cache.get_buffer(c * c << 3), g = this.cache.get_buffer(l << 3), _ = this.cache.get_buffer(l * l << 3), v = new p(c, c, m, h.data), y = new p(1, l, m, g.data), b = new p(l, l, m, _.data), x = e.data, S = v.data, C = y.data, w = b.data; - for (this.svd_decompose(t, y, v, b, 0), d = f.EPSILON * C[0] * l; n < l; n++, o += l) for (r = 0, a = 0; r < c; r++, s++) { - for (i = 0, u = 0; i < l; i++, a++) C[i] > d && (u += w[o + i] * S[a] / C[i]); - x[s] = u; - } - this.cache.put_buffer(h), this.cache.put_buffer(g), this.cache.put_buffer(_); + n[t] = 0; + return; + default: break; } - eigenVV(e, t, n) { - let r = e.cols, i = r * r, a = e.type | f.C1_t, o = this.cache.get_buffer(r * r << 3), s = this.cache.get_buffer(r << 3), c = new p(r, r, a, o.data), l = new p(1, r, a, s.data); - for (; --i >= 0;) c.data[i] = e.data[i]; - if (this.JacobiImpl(c.data, r, l.data, t ? t.data : null, r, r), n) for (; --r >= 0;) n.data[r] = l.data[r]; - this.cache.put_buffer(o), this.cache.put_buffer(s); + n[t] = c + s * e[t]; +} +var j = class { + constructor(e, t, n) { + this.dirs = /* @__PURE__ */ new Int32Array(1024), this.dirs_count = D(e, this.dirs, n) | 0, this.scores = new Int32Array(e * t), this.radius = n | 0; } -}, k.orb = class extends k { +}, M = class { constructor() { - super(), this.bit_pattern_31_ = new Int32Array(g), this.H = new p(3, 3, f.F32_t | f.C1_t), this.patch_img = new p(32, 32, f.U8_t | f.C1_t), this.imgproc = new k.imgproc(); + this.level_tables = [], this.tau = 7; } - describe(e, t, n, r) { - let i = 0, a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = 0, p = this.patch_img.data, m = 0; - r.type & f.U8_t ? r.resize(32, n, 1) : (r.type = f.U8_t, r.cols = 32, r.rows = n, r.channel = 1, r.allocate()); - let h = r.data, g = 0; - for (i = 0; i < n; ++i) { - for (o = t[i].x, s = t[i].y, c = t[i].angle, _(e, this.patch_img, c, o, s, 32, this.H, this.imgproc), m = 0, a = 0; a < 32; ++a) l = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d = l < u | 0, l = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d |= (l < u) << 1, l = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d |= (l < u) << 2, l = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d |= (l < u) << 3, l = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d |= (l < u) << 4, l = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d |= (l < u) << 5, l = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d |= (l < u) << 6, l = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, u = p[528 + this.bit_pattern_31_[m + 1] * 32 + this.bit_pattern_31_[m]], m += 2, d |= (l < u) << 7, h[g + a] = d; - g += 32; - } + init(e, t, n, r = 1) { + n = Math.min(n, 7), n = Math.max(n, 3); + for (let i = 0; i < r; ++i) this.level_tables[i] = new j(e >> i, t >> i, n); + } + detect(e, t, n = 4) { + let r = this.level_tables[0], i = r.radius | 0, a = i - 1 | 0, o = r.dirs, s = r.dirs_count | 0, c = s >> 1, l = e.data, u = e.cols | 0, d = e.rows | 0, f = u >> 1, p = r.scores, m = 0, h = 0, g = 0, _ = 0, v = 0, y = 0, b = 0, x = 0, S = this.tau | 0, C = 0, w, T = Math.max(i + 1, n) | 0, E = Math.max(i + 1, n) | 0, D = Math.min(u - i - 2, u - n) | 0, j = Math.min(d - i - 2, d - n) | 0; + for (g = E * u + T | 0, h = E; h < j; ++h, g += u) for (m = T, _ = g; m < D; ++m, ++_) v = l[_] + S, y = l[_] - S, y < l[_ + i] && l[_ + i] < v && y < l[_ - i] && l[_ - i] < v ? p[_] = 0 : A(l, _, p, y, v, o, c, s); + for (g = E * u + T | 0, h = E; h < j; ++h, g += u) for (m = T, _ = g; m < D; ++m, ++_) x = p[_], b = Math.abs(x), b < 5 ? (++m, ++_) : O(p, _, u) >= 3 && k(p, _, x, f, i) && (w = t[C], w.x = m, w.y = h, w.score = b, ++C, m += a, _ += a); + return C; } -}, k.yape = C, k.yape06 = class extends k { +}; +//#endregion +//#region src/yape06/yape06_utils.ts +function N(e, t, n, r, i, a, o, s, c) { + let l = 0, u = 0, d = o * n + a | 0, f = d; + for (l = o; l < c; ++l, d += n, f = d) for (u = a; u < s; ++u, ++f) f + r < e.length && f - r >= 0 && f + i < e.length && f - i >= 0 ? t[f] = -4 * e[f] + e[f + r] + e[f - r] + e[f + i] + e[f - i] : t[f] = 0; +} +function P(e, t, n, r, i, a, o) { + let s = -2 * e[t] + e[t + r] + e[t - r], c = -2 * e[t] + e[t + i] + e[t - i], l = e[t + a] + e[t - a] - e[t + o] - e[t - o], u = Math.sqrt((s - c) * (s - c) + 4 * l * l) | 0; + return Math.min(Math.abs(n - u), Math.abs(-(n + u))); +} +//#endregion +//#region src/yape06/yape06.ts +var F = class extends s { constructor() { super(), this.laplacian_threshold = 30, this.min_eigen_value_threshold = 25; } detect(e, t, n) { n === void 0 && (n = 5); - let r = 0, i = 0, a = e.cols, o = e.rows, s = e.data, c = 5 * a | 0, l = 3 + 3 * a | 0, u = 3 - 3 * a | 0, d = this.cache.get_buffer(a * o << 2), f = d.i32, p = 0, m = 0, h = 0, g = 0, _, v = 0, y = this.laplacian_threshold, b = this.min_eigen_value_threshold, x = Math.max(5, n) | 0, S = Math.max(3, n) | 0, C = Math.min(a - 5, a - n) | 0, E = Math.min(o - 3, o - n) | 0; + let r = 0, i = 0, a = e.cols, o = e.rows, s = e.data, c = 5 * a | 0, l = 3 + 3 * a | 0, u = 3 - 3 * a | 0, d = this.cache.get_buffer(a * o << 2), f = d.i32, p = 0, m = 0, h = 0, g = 0, _, v = 0, y = this.laplacian_threshold, b = this.min_eigen_value_threshold, x = Math.max(5, n) | 0, S = Math.max(3, n) | 0, C = Math.min(a - 5, a - n) | 0, w = Math.min(o - 3, o - n) | 0; for (r = a * o; --r >= 0;) f[r] = 0; - for (w(s, f, a, 5, c, x, S, C, E), m = S * a + x | 0, i = S; i < E; ++i, m += a) for (r = x, h = m; r < C; ++r, ++h) p = f[h], (p < -y && p < f[h - 1] && p < f[h + 1] && p < f[h - a] && p < f[h + a] && p < f[h - a - 1] && p < f[h + a - 1] && p < f[h - a + 1] && p < f[h + a + 1] || p > y && p > f[h - 1] && p > f[h + 1] && p > f[h - a] && p > f[h + a] && p > f[h - a - 1] && p > f[h + a - 1] && p > f[h - a + 1] && p > f[h + a + 1]) && (g = T(s, h, p, 5, c, l, u), g > b && (_ = t[v], _.x = r, _.y = i, _.score = g, ++v, ++r, ++h)); + for (N(s, f, a, 5, c, x, S, C, w), m = S * a + x | 0, i = S; i < w; ++i, m += a) for (r = x, h = m; r < C; ++r, ++h) p = f[h], (p < -y && p < f[h - 1] && p < f[h + 1] && p < f[h - a] && p < f[h + a] && p < f[h - a - 1] && p < f[h + a - 1] && p < f[h - a + 1] && p < f[h + a + 1] || p > y && p > f[h - 1] && p > f[h + 1] && p > f[h - a] && p > f[h + a] && p > f[h - a - 1] && p > f[h + a - 1] && p > f[h - a + 1] && p > f[h + a + 1]) && (g = P(s, h, p, 5, c, l, u), g > b && (_ = t[v], _.x = r, _.y = i, _.score = g, ++v, ++r, ++h)); return this.cache.put_buffer(d), v; } -}, k.motion_estimator = class extends k { +}, I = class { + constructor(e = 0, t = .5, n = .5, r = .99) { + this.size = e, this.thresh = t, this.eps = n, this.prob = r; + } + update_iters(e, t) { + let n = Math.log(1 - this.prob), r = Math.log(1 - Math.pow(1 - e, this.size)); + return (r >= 0 || -n >= t * -r ? t : Math.round(n / r)) | 0; + } +}, L = class extends s { constructor() { super(); } @@ -2512,68 +2460,130 @@ k.cache = r, k.pyramid_t = class extends k { for (e.error(n, r, t, o, i); l < i; ++l) u = o[l] <= d, s[l] = u, c += u; return c; } - ransac(e, t, n, r, i, a, o, s) { - if (s === void 0 && (s = 1e3), i < e.size) return !1; - let c = e.size, l = s, u = 0, d = !1, m = [], h = [], g = !1, _ = a.cols, v = a.rows, y = a.type | f.C1_t, b = this.cache.get_buffer(_ * v << 3), x = this.cache.get_buffer(i), S = this.cache.get_buffer(i << 2), C = new p(_, v, y, b.data), w = new p(i, 1, f.U8C1_t, x.data), T = -1, E = 0, D = 0, O = S.f32; - if (i == c) { - if (t.run(n, r, C, i) <= 0) return this.cache.put_buffer(b), this.cache.put_buffer(x), this.cache.put_buffer(S), !1; - if (C.copy_to(a), o) for (; --i >= 0;) o.data[i] = 1; + ransac(e, t, n, r, a, o, s, l) { + if (l === void 0 && (l = 1e3), a < e.size) return !1; + let u = e.size, d = l, f = 0, p = !1, m = [], h = [], g = !1, _ = o.cols, v = o.rows, y = o.type | i.C1_t, b = this.cache.get_buffer(_ * v << 3), x = this.cache.get_buffer(a), S = this.cache.get_buffer(a << 2), C = new c(_, v, y, b.data), w = new c(a, 1, i.U8C1_t, x.data), T = -1, E = 0, D = 0, O = S.f32; + if (a == u) { + if (t.run(n, r, C, a) <= 0) return this.cache.put_buffer(b), this.cache.put_buffer(x), this.cache.put_buffer(S), !1; + if (C.copy_to(o), s) for (; --a >= 0;) s.data[a] = 1; return this.cache.put_buffer(b), this.cache.put_buffer(x), this.cache.put_buffer(S), !0; } - for (; u < l; ++u) { - if (g = this.get_subset(t, n, r, c, i, m, h), !g) { - if (u == 0) return this.cache.put_buffer(b), this.cache.put_buffer(x), this.cache.put_buffer(S), !1; + for (; f < d; ++f) { + if (g = this.get_subset(t, n, r, u, a, m, h), !g) { + if (f == 0) return this.cache.put_buffer(b), this.cache.put_buffer(x), this.cache.put_buffer(S), !1; break; } - D = t.run(m, h, C, c), !(D <= 0) && (E = this.find_inliers(t, C, n, r, i, e.thresh, O, w.data), E > Math.max(T, c - 1) && (C.copy_to(a), T = E, o && w.copy_to(o), l = e.update_iters((i - E) / i, l), d = !0)); + D = t.run(m, h, C, u), !(D <= 0) && (E = this.find_inliers(t, C, n, r, a, e.thresh, O, w.data), E > Math.max(T, u - 1) && (C.copy_to(o), T = E, s && w.copy_to(s), d = e.update_iters((a - E) / a, d), p = !0)); } - return this.cache.put_buffer(b), this.cache.put_buffer(x), this.cache.put_buffer(S), d; + return this.cache.put_buffer(b), this.cache.put_buffer(x), this.cache.put_buffer(S), p; } - lmeds(e, t, n, r, i, a, o, s) { - if (s === void 0 && (s = 1e3), i < e.size) return !1; - let c = e.size, l = s, u = 0, d = !1, m = new k.math(), h = [], g = [], _ = !1, v = a.cols, y = a.rows, b = a.type | f.C1_t, x = this.cache.get_buffer(v * y << 3), S = this.cache.get_buffer(i), C = this.cache.get_buffer(i << 2), w = new p(v, y, b, x.data), T = new p(i, 1, f.U8_t | f.C1_t, S.data), E = 0, D = 0, O = C.f32, A = 1e9, j = 0, M = 0; - if (e.eps = .45, l = e.update_iters(e.eps, l), i == c) { - if (t.run(n, r, w, i) <= 0) return this.cache.put_buffer(x), this.cache.put_buffer(S), this.cache.put_buffer(C), !1; - if (w.copy_to(a), o) for (; --i >= 0;) o.data[i] = 1; - return this.cache.put_buffer(x), this.cache.put_buffer(S), this.cache.put_buffer(C), !0; + lmeds(e, t, n, r, a, o, s, l) { + if (l === void 0 && (l = 1e3), a < e.size) return !1; + let u = e.size, d = l, f = 0, m = !1, h = new p(), g = [], _ = [], v = !1, y = o.cols, b = o.rows, x = o.type | i.C1_t, S = this.cache.get_buffer(y * b << 3), C = this.cache.get_buffer(a), w = this.cache.get_buffer(a << 2), T = new c(y, b, x, S.data), E = new c(a, 1, i.U8_t | i.C1_t, C.data), D = 0, O = 0, k = w.f32, A = 1e9, j = 0, M = 0; + if (e.eps = .45, d = e.update_iters(e.eps, d), a == u) { + if (t.run(n, r, T, a) <= 0) return this.cache.put_buffer(S), this.cache.put_buffer(C), this.cache.put_buffer(w), !1; + if (T.copy_to(o), s) for (; --a >= 0;) s.data[a] = 1; + return this.cache.put_buffer(S), this.cache.put_buffer(C), this.cache.put_buffer(w), !0; } - for (; u < l; ++u) { - if (_ = this.get_subset(t, n, r, c, i, h, g), !_) { - if (u == 0) return this.cache.put_buffer(x), this.cache.put_buffer(S), this.cache.put_buffer(C), !1; + for (; f < d; ++f) { + if (v = this.get_subset(t, n, r, u, a, g, _), !v) { + if (f == 0) return this.cache.put_buffer(S), this.cache.put_buffer(C), this.cache.put_buffer(w), !1; break; } - D = t.run(h, g, w, c), !(D <= 0) && (t.error(n, r, w, O, i), M = m.median(O, 0, i - 1), M < A && (A = M, w.copy_to(a), d = !0)); + O = t.run(g, _, T, u), !(O <= 0) && (t.error(n, r, T, k, a), M = h.median(k, 0, a - 1), M < A && (A = M, T.copy_to(o), m = !0)); + } + return m && (j = 2.5 * 1.4826 * (1 + 5 / (a - u)) * Math.sqrt(A), j = Math.max(j, .001), D = this.find_inliers(t, o, n, r, a, j, k, E.data), s && E.copy_to(s), m = D >= u), this.cache.put_buffer(S), this.cache.put_buffer(C), this.cache.put_buffer(w), m; + } +}, R = class extends s { + constructor() { + super(), this.T0 = new c(3, 3, i.F32_t | i.C1_t), this.T1 = new c(3, 3, i.F32_t | i.C1_t), this.AtA = new c(6, 6, i.F32_t | i.C1_t), this.AtB = new c(6, 1, i.F32_t | i.C1_t); + } + sqr(e) { + return e * e; + } + iso_normalize_points(e, t, n, r, i) { + let a = 0, o = 0, s = 0, c = 0, l = 0, u = 0, d = 0, f = 0, p = 0, m = 0, h = 0; + for (; a < i; ++a) o += e[a].x, s += e[a].y, u += t[a].x, d += t[a].y; + for (o /= i, s /= i, u /= i, d /= i, a = 0; a < i; ++a) m = e[a].x - o, h = e[a].y - s, c += Math.sqrt(m * m + h * h), m = t[a].x - u, h = t[a].y - d, f += Math.sqrt(m * m + h * h); + c /= i, f /= i, l = Math.SQRT2 / c, p = Math.SQRT2 / f, n[0] = n[4] = l, n[2] = -o * l, n[5] = -s * l, n[1] = n[3] = n[6] = n[7] = 0, n[8] = 1, r[0] = r[4] = p, r[2] = -u * p, r[5] = -d * p, r[1] = r[3] = r[6] = r[7] = 0, r[8] = 1; + } + have_collinear_points(e, t) { + let n = 0, r = 0, a = t - 1 | 0, o = 0, s = 0, c = 0, l = 0; + for (; n < a; ++n) for (o = e[n].x - e[a].x, s = e[n].y - e[a].y, r = 0; r < n; ++r) if (c = e[r].x - e[a].x, l = e[r].y - e[a].y, Math.abs(c * s - l * o) <= i.EPSILON * (Math.abs(o) + Math.abs(s) + Math.abs(c) + Math.abs(l))) return !0; + return !1; + } +}, z = class extends R { + constructor() { + super(); + } + run(e, t, n, r) { + let a = 0, o = 0, s = n.type | i.C1_t, l = n.data, u = this.T0.data, d = this.T1.data, f, p, m = 0, h = 0, g = new _(), y = new v(); + this.iso_normalize_points(e, t, u, d, r); + let b = this.cache.get_buffer(2 * r * 6 << 3), x = this.cache.get_buffer(2 * r << 3), S = new c(6, 2 * r, s, b.data), C = new c(1, 2 * r, s, x.data), w = S.data, T = C.data; + for (; a < r; ++a) f = e[a], p = t[a], m = u[0] * f.x + u[1] * f.y + u[2], h = u[3] * f.x + u[4] * f.y + u[5], o = a * 2 * 6, w[o] = m, w[o + 1] = h, w[o + 2] = 1, w[o + 3] = 0, w[o + 4] = 0, w[o + 5] = 0, o += 6, w[o] = 0, w[o + 1] = 0, w[o + 2] = 0, w[o + 3] = m, w[o + 4] = h, w[o + 5] = 1, T[a << 1] = d[0] * p.x + d[1] * p.y + d[2], T[(a << 1) + 1] = d[3] * p.x + d[4] * p.y + d[5]; + return g.multiply_AtA(this.AtA, S), g.multiply_AtB(this.AtB, S, C), y.lu_solve(this.AtA, this.AtB), l[0] = this.AtB.data[0], l[1] = this.AtB.data[1], l[2] = this.AtB.data[2], l[3] = this.AtB.data[3], l[4] = this.AtB.data[4], l[5] = this.AtB.data[5], l[6] = 0, l[7] = 0, l[8] = 1, g.invert_3x3(this.T1, this.T1), g.multiply_3x3(n, this.T1, n), g.multiply_3x3(n, n, this.T0), this.cache.put_buffer(b), this.cache.put_buffer(x), 1; + } + error(e, t, n, r, i) { + let a = 0, o, s, c = n.data; + for (; a < i; ++a) o = e[a], s = t[a], r[a] = this.sqr(s.x - c[0] * o.x - c[1] * o.y - c[2]) + this.sqr(s.y - c[3] * o.x - c[4] * o.y - c[5]); + } + check_subset(e, t, n) { + return !0; + } +}, B = class extends R { + constructor() { + super(), this.mLtL = new c(9, 9, i.F32_t | i.C1_t), this.Evec = new c(9, 9, i.F32_t | i.C1_t); + } + run(e, t, n, r) { + let a = 0, o = 0, s = n.data, c = this.T0.data, l = this.T1.data, u = this.mLtL.data, d = this.Evec.data, f = 0, p = 0, m = 0, h = 0, g = new v(), y = new _(), b = 0, x = 0, S = 0, C = 0, w = 0, T = 0, E = 0, D = 0; + for (; a < r; ++a) S += t[a].x, C += t[a].y, E += e[a].x, D += e[a].y; + for (S /= r, C /= r, E /= r, D /= r, a = 0; a < r; ++a) b += Math.abs(t[a].x - S), x += Math.abs(t[a].y - C), w += Math.abs(e[a].x - E), T += Math.abs(e[a].y - D); + if (Math.abs(b) < i.EPSILON || Math.abs(x) < i.EPSILON || Math.abs(w) < i.EPSILON || Math.abs(T) < i.EPSILON) return 0; + for (b = r / b, x = r / x, w = r / w, T = r / T, c[0] = w, c[1] = 0, c[2] = -E * w, c[3] = 0, c[4] = T, c[5] = -D * T, c[6] = 0, c[7] = 0, c[8] = 1, l[0] = 1 / b, l[1] = 0, l[2] = S, l[3] = 0, l[4] = 1 / x, l[5] = C, l[6] = 0, l[7] = 0, l[8] = 1, a = 81; --a >= 0;) u[a] = 0; + for (a = 0; a < r; ++a) f = (t[a].x - S) * b, p = (t[a].y - C) * x, m = (e[a].x - E) * w, h = (e[a].y - D) * T, u[0] += m * m, u[1] += m * h, u[2] += m, u[6] += m * -f * m, u[7] += m * -f * h, u[8] += m * -f, u[10] += h * h, u[11] += h, u[15] += h * -f * m, u[16] += h * -f * h, u[17] += h * -f, u[20] += 1, u[24] += -f * m, u[25] += -f * h, u[26] += -f, u[30] += m * m, u[31] += m * h, u[32] += m, u[33] += m * -p * m, u[34] += m * -p * h, u[35] += m * -p, u[40] += h * h, u[41] += h, u[42] += h * -p * m, u[43] += h * -p * h, u[44] += h * -p, u[50] += 1, u[51] += -p * m, u[52] += -p * h, u[53] += -p, u[60] += -f * m * -f * m + -p * m * -p * m, u[61] += -f * m * -f * h + -p * m * -p * h, u[62] += -f * m * -f + -p * m * -p, u[70] += -f * h * -f * h + -p * h * -p * h, u[71] += -f * h * -f + -p * h * -p, u[80] += -f * -f + -p * -p; + for (a = 0; a < 9; ++a) for (o = 0; o < a; ++o) u[a * 9 + o] = u[o * 9 + a]; + return g.eigenVV(this.mLtL, this.Evec), s[0] = d[72], s[1] = d[73], s[2] = d[74], s[3] = d[75], s[4] = d[76], s[5] = d[77], s[6] = d[78], s[7] = d[79], s[8] = d[80], y.multiply_3x3(n, this.T1, n), y.multiply_3x3(n, n, this.T0), f = 1 / s[8], s[0] *= f, s[1] *= f, s[2] *= f, s[3] *= f, s[4] *= f, s[5] *= f, s[6] *= f, s[7] *= f, s[8] = 1, 1; + } + error(e, t, n, r, i) { + let a = 0, o, s, c = 0, l = 0, u = 0, d = n.data; + for (; a < i; ++a) o = e[a], s = t[a], c = 1 / (d[6] * o.x + d[7] * o.y + 1), l = (d[0] * o.x + d[1] * o.y + d[2]) * c - s.x, u = (d[3] * o.x + d[4] * o.y + d[5]) * c - s.y, r[a] = l * l + u * u; + } + check_subset(e, t, n) { + let r = new _(); + if (n == 4) { + let n = 0, i = e[0], a = e[1], o = e[2], s = e[3], c = t[0], l = t[1], u = t[2], d = t[3], f = i.x, p = i.y, m = a.x, h = a.y, g = o.x, _ = o.y, v = c.x, y = c.y, b = l.x, x = l.y, S = u.x, C = u.y, w = r.determinant_3x3(f, p, 1, m, h, 1, g, _, 1), T = r.determinant_3x3(v, y, 1, b, x, 1, S, C, 1); + if (w * T < 0 && n++, f = a.x, p = a.y, m = o.x, h = o.y, g = s.x, _ = s.y, v = l.x, y = l.y, b = u.x, x = u.y, S = d.x, C = d.y, w = r.determinant_3x3(f, p, 1, m, h, 1, g, _, 1), T = r.determinant_3x3(v, y, 1, b, x, 1, S, C, 1), w * T < 0 && n++, f = i.x, p = i.y, m = o.x, h = o.y, g = s.x, _ = s.y, v = c.x, y = c.y, b = u.x, x = u.y, S = d.x, C = d.y, w = r.determinant_3x3(f, p, 1, m, h, 1, g, _, 1), T = r.determinant_3x3(v, y, 1, b, x, 1, S, C, 1), w * T < 0 && n++, f = i.x, p = i.y, m = a.x, h = a.y, g = s.x, _ = s.y, v = c.x, y = c.y, b = l.x, x = l.y, S = d.x, C = d.y, w = r.determinant_3x3(f, p, 1, m, h, 1, g, _, 1), T = r.determinant_3x3(v, y, 1, b, x, 1, S, C, 1), w * T < 0 && n++, n != 0 && n != 4) return !1; } - return d && (j = 2.5 * 1.4826 * (1 + 5 / (i - c)) * Math.sqrt(A), j = Math.max(j, .001), E = this.find_inliers(t, a, n, r, i, j, O, T.data), o && T.copy_to(o), d = E >= c), this.cache.put_buffer(x), this.cache.put_buffer(S), this.cache.put_buffer(C), d; + return !0; } -}, k.ransac_params_t = E, k.affine2d = j, k.homography2d = M, k.optical_flow_lk = class extends k { +}, V = class extends s { constructor() { super(); - let e = new k.imgproc(); + let e = new m(); this.scharr_deriv = e.scharr_derivatives; } - track(e, t, n, r, i, a, o, s, c, l) { - o === void 0 && (o = 30), s === void 0 && (s = new Uint8Array(i)), c === void 0 && (c = .01), l === void 0 && (l = 1e-4); - let u = (a - 1) * .5, d = a * a | 0, m = d << 1, h = e.data, g = t.data, _ = h[0].data, v = g[0].data, y = h[0].cols, b = h[0].rows, x = 0, S = 0, C = this.cache.get_buffer(d << 2), w = this.cache.get_buffer(m << 2), T = this.cache.get_buffer(b * (y << 1) << 2), E = new p(y, b, f.S32C2_t, T.data), D = C.i32, O = w.i32, k = T.i32, A = 0, j = 0, M = 0, N = 0, P = 0, F = 0, I = 0, L = 0, R = 0, z = 0, B = 0, V = 0, ee = 0, H = 0, U = 0, W = 0, G = 0, K = 0, te = 0, q = 0, ne = 0, J = 0, Y = 0, X = 0, Z = 0, Q = 0, re = 0, ie = 0, ae = 0, oe = 0, se = 0, ce = 0, le = 16384, ue = 8192, de = 1 / (1 << 20), fe = 0, pe = 0, me = 0, he = 0, ge = 0, _e = 0, $ = 0, ve = 0, ye = 0, be = 0, xe = 0, Se = 0; - for (c *= c; q < i; ++q) s[q] = 1; + track(e, t, n, r, a, o, s, l, u, d) { + s === void 0 && (s = 30), l === void 0 && (l = new Uint8Array(a)), u === void 0 && (u = .01), d === void 0 && (d = 1e-4); + let f = (o - 1) * .5, p = o * o | 0, m = p << 1, h = e.data, g = t.data, _ = h[0].data, v = g[0].data, y = h[0].cols, b = h[0].rows, x = 0, S = 0, C = this.cache.get_buffer(p << 2), w = this.cache.get_buffer(m << 2), T = this.cache.get_buffer(b * (y << 1) << 2), E = new c(y, b, i.S32C2_t, T.data), D = C.i32, O = w.i32, k = T.i32, A = 0, j = 0, M = 0, N = 0, P = 0, F = 0, I = 0, L = 0, R = 0, z = 0, B = 0, V = 0, ee = 0, H = 0, U = 0, W = 0, G = 0, K = 0, te = 0, q = 0, ne = 0, J = 0, Y = 0, X = 0, Z = 0, Q = 0, re = 0, ie = 0, ae = 0, oe = 0, se = 0, ce = 0, le = 16384, ue = 8192, de = 1 / (1 << 20), fe = 0, pe = 0, me = 0, he = 0, ge = 0, _e = 0, $ = 0, ve = 0, ye = 0, be = 0, xe = 0, Se = 0; + for (u *= u; q < a; ++q) l[q] = 1; let Ce = e.levels - 1 | 0; - for (X = Ce; X >= 0; --X) for (I = 1 / (1 << X), x = y >> X, S = b >> X, A = x << 1, _ = h[X].data, v = g[X].data, re = x - a | 0, ie = S - a | 0, this.scharr_deriv(h[X], E), Z = 0; Z < i; ++Z) { - if (q = Z << 1, ne = q + 1, L = n[q] * I, R = n[ne] * I, X == Ce ? (z = L, B = R) : (z = r[q] * 2, B = r[ne] * 2), r[q] = z, r[ne] = B, L -= u, R -= u, W = L | 0, G = R | 0, J = W <= 0 || W >= re || G <= 0 || G >= ie, J != 0) { - X == 0 && (s[Z] = 0); + for (X = Ce; X >= 0; --X) for (I = 1 / (1 << X), x = y >> X, S = b >> X, A = x << 1, _ = h[X].data, v = g[X].data, re = x - o | 0, ie = S - o | 0, this.scharr_deriv(h[X], E), Z = 0; Z < a; ++Z) { + if (q = Z << 1, ne = q + 1, L = n[q] * I, R = n[ne] * I, X == Ce ? (z = L, B = R) : (z = r[q] * 2, B = r[ne] * 2), r[q] = z, r[ne] = B, L -= f, R -= f, W = L | 0, G = R | 0, J = W <= 0 || W >= re || G <= 0 || G >= ie, J != 0) { + X == 0 && (l[Z] = 0); continue; } - for (ae = L - W, oe = R - G, fe = (1 - ae) * (1 - oe) * le + .5 | 0, pe = ae * (1 - oe) * le + .5 | 0, me = (1 - ae) * oe * le + .5 | 0, he = le - fe - pe - me, ve = 0, ye = 0, be = 0, Y = 0; Y < a; ++Y) for (j = (Y + G) * x + W | 0, M = j << 1, N = Y * a | 0, P = N << 1, J = 0; J < a; ++J, ++j, ++N, M += 2) ge = _[j] * fe + _[j + 1] * pe + _[j + x] * me + _[j + x + 1] * he, ge = ge + 256 >> 9, _e = k[M] * fe + k[M + 2] * pe + k[M + A] * me + k[M + A + 2] * he, _e = _e + ue >> 14, $ = k[M + 1] * fe + k[M + 3] * pe + k[M + A + 1] * me + k[M + A + 3] * he, $ = $ + ue >> 14, D[N] = ge, O[P++] = _e, O[P++] = $, ve += _e * _e, ye += _e * $, be += $ * $; - if (ve *= de, ye *= de, be *= de, xe = ve * be - ye * ye, Se = (be + ve - Math.sqrt((ve - be) * (ve - be) + 4 * ye * ye)) / m, Se < l || xe < 1.1920929e-7) { - X == 0 && (s[Z] = 0); + for (ae = L - W, oe = R - G, fe = (1 - ae) * (1 - oe) * le + .5 | 0, pe = ae * (1 - oe) * le + .5 | 0, me = (1 - ae) * oe * le + .5 | 0, he = le - fe - pe - me, ve = 0, ye = 0, be = 0, Y = 0; Y < o; ++Y) for (j = (Y + G) * x + W | 0, M = j << 1, N = Y * o | 0, P = N << 1, J = 0; J < o; ++J, ++j, ++N, M += 2) ge = _[j] * fe + _[j + 1] * pe + _[j + x] * me + _[j + x + 1] * he, ge = ge + 256 >> 9, _e = k[M] * fe + k[M + 2] * pe + k[M + A] * me + k[M + A + 2] * he, _e = _e + ue >> 14, $ = k[M + 1] * fe + k[M + 3] * pe + k[M + A + 1] * me + k[M + A + 3] * he, $ = $ + ue >> 14, D[N] = ge, O[P++] = _e, O[P++] = $, ve += _e * _e, ye += _e * $, be += $ * $; + if (ve *= de, ye *= de, be *= de, xe = ve * be - ye * ye, Se = (be + ve - Math.sqrt((ve - be) * (ve - be) + 4 * ye * ye)) / m, Se < d || xe < 1.1920929e-7) { + X == 0 && (l[Z] = 0); continue; } - for (xe = 1 / xe, z -= u, B -= u, V = 0, ee = 0, Q = 0; Q < o; ++Q) { + for (xe = 1 / xe, z -= f, B -= f, V = 0, ee = 0, Q = 0; Q < s; ++Q) { if (K = z | 0, te = B | 0, J = K <= 0 || K >= re || te <= 0 || te >= ie, J != 0) { - X == 0 && (s[Z] = 0); + X == 0 && (l[Z] = 0); break; } - for (ae = z - K, oe = B - te, fe = (1 - ae) * (1 - oe) * le + .5 | 0, pe = ae * (1 - oe) * le + .5 | 0, me = (1 - ae) * oe * le + .5 | 0, he = le - fe - pe - me, se = 0, ce = 0, Y = 0; Y < a; ++Y) for (F = (Y + te) * x + K | 0, N = Y * a | 0, P = N << 1, J = 0; J < a; ++J, ++F, ++N) ge = v[F] * fe + v[F + 1] * pe + v[F + x] * me + v[F + x + 1] * he, ge = ge + 256 >> 9, ge -= D[N], se += ge * O[P++], ce += ge * O[P++]; - if (se *= de, ce *= de, H = (ye * ce - be * se) * xe, U = (ye * se - ve * ce) * xe, z += H, B += U, r[q] = z + u, r[ne] = B + u, H * H + U * U <= c) break; + for (ae = z - K, oe = B - te, fe = (1 - ae) * (1 - oe) * le + .5 | 0, pe = ae * (1 - oe) * le + .5 | 0, me = (1 - ae) * oe * le + .5 | 0, he = le - fe - pe - me, se = 0, ce = 0, Y = 0; Y < o; ++Y) for (F = (Y + te) * x + K | 0, N = Y * o | 0, P = N << 1, J = 0; J < o; ++J, ++F, ++N) ge = v[F] * fe + v[F + 1] * pe + v[F + x] * me + v[F + x + 1] * he, ge = ge + 256 >> 9, ge -= D[N], se += ge * O[P++], ce += ge * O[P++]; + if (se *= de, ce *= de, H = (ye * ce - be * se) * xe, U = (ye * se - ve * ce) * xe, z += H, B += U, r[q] = z + f, r[ne] = B + f, H * H + U * U <= u) break; if (Q > 0 && Math.abs(H + V) < .01 && Math.abs(U + ee) < .01) { r[q] -= H * .5, r[ne] -= U * .5; break; @@ -2583,9 +2593,10 @@ k.cache = r, k.pyramid_t = class extends k { } this.cache.put_buffer(C), this.cache.put_buffer(w), this.cache.put_buffer(T); } -}; +}, ee = s; +s.cache = r, s.pyramid_t = x, s.transform = S, s.matrix_t = c, s.keypoint_t = C, s.fast_corners = b, s.imgproc = m, s.math = p, s.matmath = _, s.linalg = v, s.orb = E, s.yape = M, s.yape06 = F, s.motion_estimator = L, s.ransac_params_t = I, s.affine2d = z, s.homography2d = B, s.optical_flow_lk = V; //#endregion //#region src/index.ts -var N = { jsfeatNext: k }; +var H = { jsfeatNext: ee }; //#endregion -export { N as default }; +export { H as default }; diff --git a/types/src/core/core.d.ts b/types/src/core/core.d.ts new file mode 100644 index 0000000..690d1d6 --- /dev/null +++ b/types/src/core/core.d.ts @@ -0,0 +1,69 @@ +import { cache } from '../cache/cache'; +import { imgproc } from '../imgproc/imgproc'; +import { fast_corners } from '../fast_corners/fast_corners'; +import { linalg } from '../linalg/linalg'; +import { math } from '../math/math'; +import { default as matmath } from '../matmath/matmath'; +import { matrix_t } from '../matrix_t/matrix_t'; +import { pyramid_t } from '../pyramid_t/pyramid_t'; +import { transform } from '../transform/transform'; +import { keypoint_t } from '../keypoint_t/keypoint_t'; +import { yape } from '../yape/yape'; +import { yape06 } from '../yape06/yape06'; +import { ransac_params_t } from '../motion_estimator/ransac_params_t'; +import { motion_estimator } from '../motion_estimator/motion_estimator'; +import { optical_flow_lk } from '../optical_flow_lk/optical_flow_lk'; +import { orb } from '../orb/orb'; +import { affine2d, homography2d } from '../motion_model/motion_model'; +export default class jsfeatNext { + private dt; + protected cache: cache; + static cache: typeof cache; + static fast_corners: typeof fast_corners; + static imgproc: typeof imgproc; + static linalg: typeof linalg; + static math: typeof math; + static matmath: typeof matmath; + static matrix_t: typeof matrix_t; + static pyramid_t: typeof pyramid_t; + static transform: typeof transform; + static keypoint_t: typeof keypoint_t; + static yape: typeof yape; + static yape06: typeof yape06; + static ransac_params_t: typeof ransac_params_t; + static affine2d: typeof affine2d; + static homography2d: typeof homography2d; + static motion_estimator: typeof motion_estimator; + static optical_flow_lk: typeof optical_flow_lk; + static orb: typeof orb; + constructor(); + static VERSION: string; + static EPSILON: number; + static FLT_MIN: number; + static U8_t: number; + static S32_t: number; + static F32_t: number; + static S64_t: number; + static F64_t: number; + static C1_t: number; + static C2_t: number; + static C3_t: number; + static C4_t: number; + static COLOR_RGBA2GRAY: number; + static COLOR_RGB2GRAY: number; + static COLOR_BGRA2GRAY: number; + static COLOR_BGR2GRAY: number; + static BOX_BLUR_NOSCALE: number; + static SVD_U_T: number; + static SVD_V_T: number; + static U8C1_t: number; + static U8C3_t: number; + static U8C4_t: number; + static F32C1_t: number; + static F32C2_t: number; + static S32C1_t: number; + static S32C2_t: number; + get_data_type(type: number): number; + get_channel(type: number): number; + get_data_type_size(type: number): number; +} diff --git a/types/src/fast_corners/fast_corners.d.ts b/types/src/fast_corners/fast_corners.d.ts index 63c65fc..bde1811 100644 --- a/types/src/fast_corners/fast_corners.d.ts +++ b/types/src/fast_corners/fast_corners.d.ts @@ -1,6 +1,14 @@ +import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; import { point_t } from '../point_t/point_t'; -export declare class fast_corners { +export declare class fast_corners extends jsfeatNext { + private offsets16; + _threshold: number; + threshold_tab: Uint8Array; + pixel_off: Int32Array; + score_diff: Int32Array; + constructor(); set_threshold(threshold: number): number; detect(src: matrix_t, corners: point_t[], border: number): number; + private _cmp_offsets; } diff --git a/types/src/imgproc/imgproc.d.ts b/types/src/imgproc/imgproc.d.ts index c8b56a8..d43f403 100644 --- a/types/src/imgproc/imgproc.d.ts +++ b/types/src/imgproc/imgproc.d.ts @@ -1,10 +1,12 @@ +import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; -export declare class imgproc { +export declare class imgproc extends jsfeatNext { + constructor(); grayscale(src: Uint8Array | Uint8ClampedArray, w: number, h: number, dst: matrix_t, code?: number): void; resample(src: matrix_t, dst: matrix_t, nw: number, nh: number): void; box_blur_gray(src: matrix_t, dst: matrix_t, radius: number, options: number): void; gaussian_blur(src: matrix_t, dst: matrix_t, kernel_size: number, sigma: number): void; - hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): Array; + hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): number[]; pyrdown(src: matrix_t, dst: matrix_t, sx?: number, sy?: number): void; scharr_derivatives(src: matrix_t, dst: matrix_t): void; sobel_derivatives(src: matrix_t, dst: matrix_t): void; diff --git a/types/src/jsfeatNext.d.ts b/types/src/jsfeatNext.d.ts index 75fab1d..7e92ef5 100644 --- a/types/src/jsfeatNext.d.ts +++ b/types/src/jsfeatNext.d.ts @@ -1,94 +1,2 @@ -import { cache } from './cache/cache'; -import { imgproc } from './imgproc/imgproc'; -import { linalg } from './linalg/linalg'; -import { fast_corners } from './fast_corners/fast_corners'; -import { math } from './math/math'; -import { default as matmath } from './matmath/matmath'; -import { matrix_t } from './matrix_t/matrix_t'; -import { pyramid_t } from './pyramid_t/pyramid_t'; -import { point_t } from './point_t/point_t'; -import { transform } from './transform/transform'; -import { keypoint_t } from './keypoint_t/keypoint_t'; -import { orb } from './orb/orb'; -import { yape } from './yape/yape'; -import { yape06 } from './yape06/yape06'; -import { ransac_params_t } from './motion_estimator/ransac_params_t'; -import { motion_estimator } from './motion_estimator/motion_estimator'; -import { optical_flow_lk } from './optical_flow_lk/optical_flow_lk'; -export default class jsfeatNext { - private dt; - protected cache: cache; - static cache: typeof cache; - static fast_corners: typeof fast_corners; - static imgproc: typeof imgproc; - static linalg: typeof linalg; - static math: typeof math; - static matmath: typeof matmath; - static matrix_t: typeof matrix_t; - static pyramid_t: typeof pyramid_t; - static transform: typeof transform; - static keypoint_t: typeof keypoint_t; - static yape: typeof yape; - static yape06: typeof yape06; - static ransac_params_t: typeof ransac_params_t; - static affine2d: typeof affine2d; - static homography2d: typeof homography2d; - static motion_estimator: typeof motion_estimator; - static optical_flow_lk: typeof optical_flow_lk; - static orb: typeof orb; - constructor(); - static VERSION: string; - static EPSILON: number; - static FLT_MIN: number; - static U8_t: number; - static S32_t: number; - static F32_t: number; - static S64_t: number; - static F64_t: number; - static C1_t: number; - static C2_t: number; - static C3_t: number; - static C4_t: number; - static COLOR_RGBA2GRAY: number; - static COLOR_RGB2GRAY: number; - static COLOR_BGRA2GRAY: number; - static COLOR_BGR2GRAY: number; - static BOX_BLUR_NOSCALE: number; - static SVD_U_T: number; - static SVD_V_T: number; - static U8C1_t: number; - static U8C3_t: number; - static U8C4_t: number; - static F32C1_t: number; - static F32C2_t: number; - static S32C1_t: number; - static S32C2_t: number; - get_data_type(type: number): number; - get_channel(type: number): number; - get_data_type_size(type: number): number; -} -declare class motion_model extends jsfeatNext { - T0: matrix_t; - T1: matrix_t; - AtA: matrix_t; - AtB: matrix_t; - constructor(); - sqr(x: number): number; - iso_normalize_points(from: point_t[], to: point_t[], T0: number[], T1: number[], count: number): void; - have_collinear_points(points: point_t[], count: number): boolean; -} -declare class affine2d extends motion_model { - constructor(); - run(from: point_t[], to: point_t[], model: matrix_t, count: number): number; - error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void; - check_subset(from: point_t[], to: point_t[], count: number): boolean; -} -declare class homography2d extends motion_model { - mLtL: matrix_t; - Evec: matrix_t; - constructor(); - run(from: point_t[], to: point_t[], model: matrix_t, count: number): number; - error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void; - check_subset(from: point_t[], to: point_t[], count: number): boolean; -} -export {}; +import { default as jsfeatNext } from './core/core'; +export default jsfeatNext; diff --git a/types/src/linalg/linalg.d.ts b/types/src/linalg/linalg.d.ts index a6c21b7..d18f0c8 100644 --- a/types/src/linalg/linalg.d.ts +++ b/types/src/linalg/linalg.d.ts @@ -1,5 +1,9 @@ +import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; -export declare class linalg { +import { default as matmath } from '../matmath/matmath'; +export declare class linalg extends jsfeatNext { + matmath: matmath; + constructor(); JacobiImpl(A: Int32Array | Float32Array | Float64Array, astep: number, W: Int32Array | Float32Array | Float64Array, V: Int32Array | Float32Array | Float64Array, vstep: number, n: number): void; JacobiSVDImpl(At: Int32Array | Float32Array | Float64Array, astep: number, _W: Int32Array | Float32Array | Float64Array, Vt: Int32Array | Float32Array | Float64Array, vstep: number, m: number, n: number, n1: number): void; lu_solve(A: matrix_t, B: matrix_t): number; diff --git a/types/src/math/math.d.ts b/types/src/math/math.d.ts index e9129e8..070bf0d 100644 --- a/types/src/math/math.d.ts +++ b/types/src/math/math.d.ts @@ -1,7 +1,10 @@ +import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; -export declare class math { - get_gaussian_kernel(kernel_size: number, sigma: number, filter: Int32Array | Float32Array, data_type: number): void; +export declare class math extends jsfeatNext { + private qsort_stack; + constructor(); + get_gaussian_kernel(size: number, sigma: number, kernel: Float32Array | Int32Array, data_type: number): void; perspective_4point_transform(model: matrix_t, src_x0: number, src_y0: number, dst_x0: number, dst_y0: number, src_x1: number, src_y1: number, dst_x1: number, dst_y1: number, src_x2: number, src_y2: number, dst_x2: number, dst_y2: number, src_x3: number, src_y3: number, dst_x3: number, dst_y3: number): void; qsort(array: number[], low: number, high: number, cmp: (a: number, b: number) => number): void; - median(array: any, low: number, high: number): number; + median(array: number[] | Int32Array | Float32Array, low: number, high: number): number; } diff --git a/types/src/motion_estimator/motion_estimator.d.ts b/types/src/motion_estimator/motion_estimator.d.ts index 84e6dd0..f3ebf76 100644 --- a/types/src/motion_estimator/motion_estimator.d.ts +++ b/types/src/motion_estimator/motion_estimator.d.ts @@ -1,10 +1,12 @@ -import { IHomography2d } from '../homography2d/homography2d'; +import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; import { point_t } from '../point_t/point_t'; import { ransac_params_t } from './ransac_params_t'; -export declare class motion_estimator { - get_subset(kernel: IHomography2d, from: point_t[], to: point_t[], need_cnt: number, max_cnt: number, from_sub: point_t[], to_sub: point_t[]): boolean; - find_inliers(kernel: IHomography2d, model: matrix_t, from: point_t[], to: point_t[], count: number, thresh: number, err: Int32Array | Float32Array, mask: number[]): number; +import { homography2d } from '../motion_model/motion_model'; +export declare class motion_estimator extends jsfeatNext { + constructor(); + get_subset(kernel: homography2d, from: point_t[], to: point_t[], need_cnt: number, max_cnt: number, from_sub: point_t[], to_sub: point_t[]): boolean; + find_inliers(kernel: homography2d, model: matrix_t, from: point_t[], to: point_t[], count: number, thresh: number, err: Int32Array | Float32Array, mask: number[]): number; ransac(params: ransac_params_t, kernel: any, from: point_t[], to: point_t[], count: number, model: matrix_t, mask: matrix_t, max_iters: number): boolean; lmeds(params: ransac_params_t, kernel: any, from: point_t[], to: point_t[], count: number, model: matrix_t, mask: matrix_t, max_iters: number): boolean; } diff --git a/types/src/motion_model/motion_model.d.ts b/types/src/motion_model/motion_model.d.ts new file mode 100644 index 0000000..3a20d88 --- /dev/null +++ b/types/src/motion_model/motion_model.d.ts @@ -0,0 +1,27 @@ +import { default as jsfeatNext } from '../core/core'; +import { matrix_t } from '../matrix_t/matrix_t'; +import { point_t } from '../point_t/point_t'; +export declare class motion_model extends jsfeatNext { + T0: matrix_t; + T1: matrix_t; + AtA: matrix_t; + AtB: matrix_t; + constructor(); + sqr(x: number): number; + iso_normalize_points(from: point_t[], to: point_t[], T0: number[], T1: number[], count: number): void; + have_collinear_points(points: point_t[], count: number): boolean; +} +export declare class affine2d extends motion_model { + constructor(); + run(from: point_t[], to: point_t[], model: matrix_t, count: number): number; + error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void; + check_subset(from: point_t[], to: point_t[], count: number): boolean; +} +export declare class homography2d extends motion_model { + mLtL: matrix_t; + Evec: matrix_t; + constructor(); + run(from: point_t[], to: point_t[], model: matrix_t, count: number): number; + error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void; + check_subset(from: point_t[], to: point_t[], count: number): boolean; +} diff --git a/types/src/optical_flow_lk/optical_flow_lk.d.ts b/types/src/optical_flow_lk/optical_flow_lk.d.ts index e9ef6b2..570a973 100644 --- a/types/src/optical_flow_lk/optical_flow_lk.d.ts +++ b/types/src/optical_flow_lk/optical_flow_lk.d.ts @@ -1,4 +1,7 @@ +import { default as jsfeatNext } from '../core/core'; import { pyramid_t } from '../pyramid_t/pyramid_t'; -export declare class optical_flow_lk { +export declare class optical_flow_lk extends jsfeatNext { + scharr_deriv: any; + constructor(); track(prev_pyr: pyramid_t, curr_pyr: pyramid_t, prev_xy: Float32Array, curr_xy: Float32Array, count: number, win_size: number, max_iter: number, status: Uint8Array, eps: number, min_eigen_threshold: number): void; } diff --git a/types/src/orb/orb.d.ts b/types/src/orb/orb.d.ts index 014beb5..5f39d57 100644 --- a/types/src/orb/orb.d.ts +++ b/types/src/orb/orb.d.ts @@ -1,5 +1,12 @@ +import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; import { keypoint_t } from '../keypoint_t/keypoint_t'; -export declare class orb { +import { imgproc } from '../imgproc/imgproc'; +export declare class orb extends jsfeatNext { + bit_pattern_31_: Int32Array; + H: matrix_t; + patch_img: matrix_t; + imgproc: imgproc; + constructor(); describe(src: matrix_t, corners: keypoint_t[], count: number, descriptors: matrix_t): void; } diff --git a/types/src/pyramid_t/pyramid_t.d.ts b/types/src/pyramid_t/pyramid_t.d.ts index e524233..510ccff 100644 --- a/types/src/pyramid_t/pyramid_t.d.ts +++ b/types/src/pyramid_t/pyramid_t.d.ts @@ -1,7 +1,9 @@ +import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; -export declare class pyramid_t { - data: any; +export declare class pyramid_t extends jsfeatNext { levels: number; + data: any; + private pyrdown; constructor(levels: number); allocate(start_w: number, start_h: number, data_type: number): void; build(input: matrix_t, skip_first_level: boolean): void; diff --git a/types/src/yape06/yape06.d.ts b/types/src/yape06/yape06.d.ts index 43d5771..6e546bd 100644 --- a/types/src/yape06/yape06.d.ts +++ b/types/src/yape06/yape06.d.ts @@ -1,5 +1,9 @@ +import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; import { keypoint_t } from '../keypoint_t/keypoint_t'; -export declare class yape06 { +export declare class yape06 extends jsfeatNext { + laplacian_threshold: number; + min_eigen_value_threshold: number; + constructor(); detect(src: matrix_t, points: keypoint_t[], border: number): number; } From 9563db2de29b318888dff94632194fd551937e27 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Thu, 9 Jul 2026 11:56:41 +0200 Subject: [PATCH 10/10] docs(src): add TSDoc to the whole API; set up TypeDoc generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comprehensive documentation pass over all 34 source files: every class, interface, method, property and standalone function now carries a TSDoc block (summaries, @param/@returns, plus parity notes vs original jsfeat, e.g. the fixed hough_transform, the transform calling-convention difference and the per-instance cache). Tooling: - tsconfig: removeComments false, so the docs propagate into the generated .d.ts and consumers get hover documentation in their IDE - typedoc devDependency + 'npm run docs' script + typedoc.json (all modules, HTML output to docs/api — gitignored, generated on demand; publishing/hosting is an org-wide decision tracked separately) - dist/ + types/ rebuilt (JS behavior unchanged; declarations carry docs) Verified: typedoc generates with zero warnings (86 pages); tsc --noEmit clean; npm test 57/57; prettier clean. Co-Authored-By: Claude Fable 5 --- .gitignore | 3 +- package-lock.json | 259 ++++++++++++++++++ package.json | 4 +- src/cache/cache.ts | 47 ++++ src/constants/constants.ts | 38 +++ src/core/core.ts | 20 +- src/data_type/data_type.ts | 30 ++ src/fast_corners/fast_corners.ts | 33 ++- src/fast_corners/fast_private.ts | 17 ++ src/homography2d/homography2d.ts | 21 ++ src/imgproc/convol.ts | 29 ++ src/imgproc/imgproc.ts | 159 ++++++++++- src/imgproc/resample.ts | 21 ++ src/index.ts | 15 + src/keypoint_t/keypoint_t.ts | 27 ++ src/linalg/linalg.ts | 93 ++++++- src/linalg/linalg_base.ts | 17 ++ src/math/math.ts | 58 +++- src/matmath/matmath.ts | 86 +++++- src/matrix_t/matrix_t.ts | 69 +++++ src/motion_estimator/motion_estimator.ts | 67 ++++- src/motion_estimator/ransac_params_t.ts | 26 ++ src/motion_model/motion_model.ts | 105 ++++++- src/node_utils/_pool_node_t.ts | 27 ++ src/node_utils/data_t.ts | 27 ++ src/optical_flow_lk/optical_flow_lk.ts | 33 ++- src/orb/bit_pattern_31.ts | 7 + src/orb/orb.ts | 28 +- src/orb/rectify_patch.ts | 15 + src/point_t/point_t.ts | 19 +- src/pyramid_t/pyramid_t.ts | 30 +- src/transform/transform.ts | 34 +++ src/yape/yape.ts | 33 +++ src/yape/yape_utils.ts | 51 ++++ src/yape06/yape06.ts | 22 +- src/yape06/yape06_utils.ts | 26 ++ tsconfig.json | 2 +- typedoc.json | 15 + types/src/cache/cache.d.ts | 43 +++ types/src/constants/constants.d.ts | 38 +++ types/src/core/core.d.ts | 28 ++ types/src/data_type/data_type.d.ts | 29 ++ types/src/fast_corners/fast_corners.d.ts | 31 +++ types/src/fast_corners/fast_private.d.ts | 16 ++ types/src/homography2d/homography2d.d.ts | 20 ++ types/src/imgproc/convol.d.ts | 29 ++ types/src/imgproc/imgproc.d.ts | 146 ++++++++++ types/src/imgproc/resample.d.ts | 21 ++ types/src/index.d.ts | 14 + types/src/keypoint_t/keypoint_t.d.ts | 26 ++ types/src/linalg/linalg.d.ts | 91 ++++++ types/src/linalg/linalg_base.d.ts | 17 ++ types/src/math/math.d.ts | 52 ++++ types/src/matmath/matmath.d.ts | 79 ++++++ types/src/matrix_t/matrix_t.d.ts | 69 +++++ .../motion_estimator/motion_estimator.d.ts | 65 +++++ .../src/motion_estimator/ransac_params_t.d.ts | 24 ++ types/src/motion_model/motion_model.d.ts | 98 +++++++ types/src/node_utils/_pool_node_t.d.ts | 26 ++ types/src/node_utils/data_t.d.ts | 26 ++ .../src/optical_flow_lk/optical_flow_lk.d.ts | 30 ++ types/src/orb/bit_pattern_31.d.ts | 7 + types/src/orb/orb.d.ts | 25 ++ types/src/orb/rectify_patch.d.ts | 15 + types/src/point_t/point_t.d.ts | 17 ++ types/src/pyramid_t/pyramid_t.d.ts | 27 ++ types/src/transform/transform.d.ts | 32 +++ types/src/yape/yape.d.ts | 31 +++ types/src/yape/yape_utils.d.ts | 51 ++++ types/src/yape06/yape06.d.ts | 21 ++ types/src/yape06/yape06_utils.d.ts | 26 ++ 71 files changed, 2811 insertions(+), 72 deletions(-) create mode 100644 typedoc.json diff --git a/.gitignore b/.gitignore index d35bbf7..0632416 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -.idea/ \ No newline at end of file +.idea/ +docs/api/ diff --git a/package-lock.json b/package-lock.json index 03ac60e..ee01d5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "LGPL-3.0-or-later", "devDependencies": { "prettier": "~3.5.3", + "typedoc": "^0.28.20", "typescript": "^6.0.3", "vite": "^8.1.3", "vite-plugin-dts": "^5.0.3", @@ -50,6 +51,20 @@ "tslib": "^2.4.0" } }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz", + "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.23.0", + "@shikijs/langs": "^3.23.0", + "@shikijs/themes": "^3.23.0", + "@shikijs/types": "^3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -434,6 +449,55 @@ } } }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -477,6 +541,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/expect": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", @@ -642,6 +723,13 @@ "node": ">=0.4.0" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -652,6 +740,29 @@ "node": ">=12" } }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -711,6 +822,19 @@ "node": ">=8" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -1030,6 +1154,26 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/linkify-it": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/local-pkg": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", @@ -1048,6 +1192,13 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1058,6 +1209,57 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-it": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mlly": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", @@ -1221,6 +1423,16 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/quansync": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", @@ -1373,6 +1585,30 @@ "license": "0BSD", "optional": true }, + "node_modules/typedoc": { + "version": "0.28.20", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.20.tgz", + "integrity": "sha512-uSKqkh8Cr48vllnEy+jdaAgOeR6Y+QCBW7usgUsKj7gJEfR7stw9U/fE49LBnj2tPRKPY0c0EBJSWe9Appmplg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.23.0", + "lunr": "^2.3.9", + "markdown-it": "^14.3.0", + "minimatch": "^10.2.5", + "yaml": "^2.9.0" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x" + } + }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -1387,6 +1623,13 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/ufo": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", @@ -1695,6 +1938,22 @@ "engines": { "node": ">=8" } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } } } } diff --git a/package.json b/package.json index 180bdf7..6b36e84 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "format-check": "prettier --check .", "format": "prettier --write .", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "docs": "typedoc" }, "repository": { "type": "git", @@ -49,6 +50,7 @@ "homepage": "https://github.com/webarkit/jsfeatNext#readme", "devDependencies": { "prettier": "~3.5.3", + "typedoc": "^0.28.20", "typescript": "^6.0.3", "vite": "^8.1.3", "vite-plugin-dts": "^5.0.3", diff --git a/src/cache/cache.ts b/src/cache/cache.ts index 7b895ff..d114f46 100644 --- a/src/cache/cache.ts +++ b/src/cache/cache.ts @@ -1,20 +1,50 @@ import _pool_node_t from "./../node_utils/_pool_node_t"; +/** + * Public shape of {@link cache}: a recycling pool of scratch buffers used by + * the algorithm modules to avoid per-call allocations in hot loops. + */ export interface ICache { + /** Pre-allocates the pool with `capacity` nodes of `data_size` bytes each. */ allocate: (capacity: any, data_size: number) => void; + /** Borrows a node with at least `size_in_bytes` of storage from the pool. */ get_buffer: (size_in_bytes: number) => _pool_node_t; + /** Returns a previously borrowed node to the pool. */ put_buffer: (node: any) => void; } +/** + * A linked-list pool of reusable scratch buffers ({@link _pool_node_t}). + * Algorithms borrow a buffer with {@link get_buffer}, use its typed-array + * views (`u8`/`i32`/`f32`/`f64`), and must hand it back with + * {@link put_buffer} when done. + * + * Mirrors `jsfeat.cache` from the original library, with one difference: + * the original keeps a single global pool, while jsfeatNext currently + * allocates one pool per module instance (see the base-class constructor + * in `src/core/core.ts`). + */ export class cache implements ICache { + /** First free node in the pool (borrow end of the list). */ private _pool_head: _pool_node_t; + /** Last node in the pool (return end of the list). */ private _pool_tail: _pool_node_t; + /** Number of nodes currently available in the pool. */ private _pool_size: number; + constructor() { this._pool_head; this._pool_tail; this._pool_size = 0; } + + /** + * Fills the pool with `capacity` nodes, each backed by `data_size` bytes. + * Must be called before the first {@link get_buffer}. + * + * @param capacity Number of pool nodes to create. + * @param data_size Initial byte size of each node's buffer. + */ allocate(capacity: any, data_size: number): void { this._pool_head = this._pool_tail = new _pool_node_t(data_size); for (let i = 0; i < capacity; ++i) { @@ -24,6 +54,16 @@ export class cache implements ICache { this._pool_size++; } } + + /** + * Borrows the next free node from the pool, growing its buffer when it is + * smaller than `size_in_bytes`. The pool assumes enough free nodes are + * available (no underflow check — callers must balance every `get` with a + * {@link put_buffer}). + * + * @param size_in_bytes Minimum byte size the caller needs. + * @returns A pool node whose typed-array views are at least the requested size. + */ get_buffer(size_in_bytes: number): _pool_node_t { // assume we have enough free nodes const node = this._pool_head; @@ -36,6 +76,13 @@ export class cache implements ICache { return node; } + + /** + * Returns a borrowed node to the tail of the pool, making it available to + * subsequent {@link get_buffer} calls. + * + * @param node The node previously obtained from {@link get_buffer}. + */ put_buffer(node: any): void { this._pool_tail = this._pool_tail.next = node; this._pool_size++; diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 26f0e07..4aab16e 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -1,37 +1,75 @@ +/** + * Library-wide constants, mirroring the constants of the original jsfeat. + * + * **Type signatures.** A matrix/image type is a bitwise OR of a data-type + * flag (`U8_t`, `S32_t`, `F32_t`, `S64_t`, `F64_t` — stored in the high byte) + * and a channel-count flag (`C1_t`…`C4_t` — stored in the low byte), e.g. + * `U8_t | C1_t` for an 8-bit grayscale image. The pre-combined popular + * formats (`U8C1_t`, `F32C1_t`, …) are provided for convenience. + * + * All of these are also re-exposed as static members of the `jsfeatNext` + * base class (see `src/core/core.ts`), which is how consumers usually + * access them (`jsfeatNext.U8_t`, `jsfeatNext.COLOR_RGBA2GRAY`, …). + */ export const JSFEAT_CONSTANTS = { // CONSTANTS + /** Smallest float32 difference considered significant (single-precision machine epsilon). */ EPSILON: 0.0000001192092896, + /** Smallest positive float used to guard divisions against zero. */ FLT_MIN: 1e-37, + /** Data type: unsigned 8-bit integer (`Uint8Array` backed). */ U8_t: 0x0100, + /** Data type: signed 32-bit integer (`Int32Array` backed). */ S32_t: 0x0200, + /** Data type: 32-bit float (`Float32Array` backed). */ F32_t: 0x0400, + /** Data type: signed 64-bit integer (reserved; no typed-array view). */ S64_t: 0x0800, + /** Data type: 64-bit float (`Float64Array` backed). */ F64_t: 0x1000, + /** Channel count: 1 channel (grayscale / scalar). */ C1_t: 0x01, + /** Channel count: 2 interleaved channels (e.g. gx/gy derivative pairs). */ C2_t: 0x02, + /** Channel count: 3 interleaved channels (e.g. RGB). */ C3_t: 0x03, + /** Channel count: 4 interleaved channels (e.g. RGBA). */ C4_t: 0x04, // color conversion + /** `imgproc.grayscale` code: source is RGBA (the browser canvas default). */ COLOR_RGBA2GRAY: 0, + /** `imgproc.grayscale` code: source is RGB (3 bytes per pixel). */ COLOR_RGB2GRAY: 1, + /** `imgproc.grayscale` code: source is BGRA. */ COLOR_BGRA2GRAY: 2, + /** `imgproc.grayscale` code: source is BGR (3 bytes per pixel). */ COLOR_BGR2GRAY: 3, // box blur option + /** `imgproc.box_blur_gray` option: keep raw window sums instead of averaging. */ BOX_BLUR_NOSCALE: 0x01, // svd options + /** `linalg.svd_decompose` option: return U transposed. */ SVD_U_T: 0x01, + /** `linalg.svd_decompose` option: return V transposed. */ SVD_V_T: 0x02, // popular formats + /** 8-bit unsigned, 1 channel (`U8_t | C1_t`) — grayscale images. */ U8C1_t: 0x0100 | 0x01, + /** 8-bit unsigned, 3 channels (`U8_t | C3_t`) — RGB images. */ U8C3_t: 0x0100 | 0x03, + /** 8-bit unsigned, 4 channels (`U8_t | C4_t`) — RGBA images. */ U8C4_t: 0x0100 | 0x04, + /** 32-bit float, 1 channel (`F32_t | C1_t`) — float matrices. */ F32C1_t: 0x0400 | 0x01, + /** 32-bit float, 2 channels (`F32_t | C2_t`) — float vector fields. */ F32C2_t: 0x0400 | 0x02, + /** 32-bit signed int, 1 channel (`S32_t | C1_t`). */ S32C1_t: 0x0200 | 0x01, + /** 32-bit signed int, 2 channels (`S32_t | C2_t`) — integer gx/gy maps. */ S32C2_t: 0x0200 | 0x02, }; diff --git a/src/core/core.ts b/src/core/core.ts index 54ec167..bf57c50 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -29,7 +29,13 @@ import type { affine2d, homography2d } from "../motion_model/motion_model"; * aggregator. */ export default class jsfeatNext { + /** Decoder for packed matrix type signatures. */ private dt: IData_Type; + /** + * Per-instance scratch-buffer pool (30 buffers of 2560 bytes, growable). + * NOTE: original jsfeat shares ONE global cache; jsfeatNext currently + * allocates a pool per module instance (see the parity audit, Axis 2). + */ protected cache: cache; static cache: typeof cache; static fast_corners: typeof fast_corners; @@ -56,7 +62,7 @@ export default class jsfeatNext { this.cache.allocate(30, 640 * 4); } - // VERSION + /** Library version, read from package.json at build time. */ static VERSION: string = pkg.version; // CONSTANTS @@ -94,14 +100,26 @@ export default class jsfeatNext { static S32C1_t = this.S32_t | this.C1_t; static S32C2_t = this.S32_t | this.C2_t; + /** + * @param type Packed type signature (e.g. `U8_t | C1_t`). + * @returns The data-type component alone (e.g. `U8_t`). + */ get_data_type(type: number): number { return this.dt._get_data_type(type); } + /** + * @param type Packed type signature. + * @returns The channel count (1–4). + */ get_channel(type: number): number { return this.dt._get_channel(type); } + /** + * @param type Packed type signature. + * @returns Bytes per element of the signature's data type (1, 4 or 8). + */ get_data_type_size(type: number): number { return this.dt._get_data_type_size(type); } diff --git a/src/data_type/data_type.ts b/src/data_type/data_type.ts index ec5335f..e2484ba 100644 --- a/src/data_type/data_type.ts +++ b/src/data_type/data_type.ts @@ -1,23 +1,53 @@ +/** + * Helper for decoding the packed matrix type signature + * (see `JSFEAT_CONSTANTS`): data-type flags live in the high byte, + * the channel count in the low byte. + */ export interface IData_Type { + /** Extracts the data-type component (`U8_t`, `S32_t`, …) from a packed signature. */ _get_data_type: (type: number) => number; + /** Extracts the channel count (1–4) from a packed signature. */ _get_channel: (type: number) => number; + /** Returns the byte size of one element of the given data type. */ _get_data_type_size: (type: number) => number; } +/** + * Decodes packed type signatures such as `U8_t | C1_t` into their data-type, + * channel-count and per-element byte-size components. Used internally by + * `matrix_t` when allocating storage. + */ export class data_type implements IData_Type { + /** + * Byte size per element, indexed by `(data_type_flag >> 8)`: + * U8 → 1, S32/F32 → 4, S64/F64 → 8; unused slots are -1. + */ private readonly _data_type_size: Int32Array; + constructor() { this._data_type_size = new Int32Array([-1, 1, 4, -1, 4, -1, -1, -1, 8, -1, -1, -1, -1, -1, -1, -1, 8]); } + /** + * @param type Packed type signature (e.g. `U8_t | C1_t`). + * @returns The data-type flag alone (high byte), e.g. `U8_t`. + */ _get_data_type(type: number): number { return type & 0xff00; } + /** + * @param type Packed type signature. + * @returns The channel count alone (low byte), 1–4. + */ _get_channel(type: number): number { return type & 0xff; } + /** + * @param type Packed type signature. + * @returns Bytes per element for the signature's data type (1, 4 or 8). + */ _get_data_type_size(type: number): number { return this._data_type_size[(type & 0xff00) >> 8]; } diff --git a/src/fast_corners/fast_corners.ts b/src/fast_corners/fast_corners.ts index 17b9e21..ac74213 100644 --- a/src/fast_corners/fast_corners.ts +++ b/src/fast_corners/fast_corners.ts @@ -4,16 +4,24 @@ import { point_t } from "../point_t/point_t"; import { _cmp_score_16 } from "./fast_private"; /** - * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). - * This file previously held a type-only stub whose methods threw - * "Method not implemented." — the implementation below is the inline code - * from the monolith, verbatim. + * FAST-16 corner detector (Features from Accelerated Segment Test): a pixel + * is a corner when ≥9 contiguous pixels on the 16-pixel Bresenham circle + * around it are all brighter or all darker than the center by the threshold. + * Detection is followed by score-based non-maximum suppression. + * + * Mirrors `jsfeat.fast_corners` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class fast_corners extends jsfeatNext { + /** The 16 (x, y) circle offsets, interleaved, for radius 3. */ private offsets16: Int32Array; + /** Current detection threshold (0–255); set via {@link set_threshold}. */ public _threshold: number; + /** 512-entry lookup: intensity difference (+255) → darker(1)/brighter(2)/similar(0). */ public threshold_tab: Uint8Array; + /** Circle offsets converted to flat pixel offsets for the current row stride. */ public pixel_off: Int32Array; + /** Scratch array used by the corner-score function. */ public score_diff: Int32Array; constructor() { @@ -28,6 +36,13 @@ export class fast_corners extends jsfeatNext { this.score_diff = new Int32Array(25); } + /** + * Sets the detection threshold and rebuilds the classification lookup + * table. Must be called at least once before {@link detect}. + * + * @param threshold Minimum center-vs-circle intensity difference, clamped to [0, 255]. + * @returns The clamped threshold actually stored. + */ set_threshold(threshold: number): number { this._threshold = Math.min(Math.max(threshold, 0), 255); for (let i = -255; i <= 255; ++i) { @@ -36,6 +51,16 @@ export class fast_corners extends jsfeatNext { return this._threshold; } + /** + * Detects FAST corners in a grayscale image, applying 3×3 non-maximum + * suppression on the corner scores. Results are written into the + * pre-allocated `corners` array (each entry gets `x`, `y`, `score`). + * + * @param src Source grayscale image (`U8C1`). + * @param corners Pre-allocated point pool to fill. + * @param border Pixels to skip along each edge (min 3). Default 3. + * @returns The number of corners written into `corners`. + */ detect(src: matrix_t, corners: point_t[], border: number): number { if (typeof border === "undefined") { border = 3; diff --git a/src/fast_corners/fast_private.ts b/src/fast_corners/fast_private.ts index e1837c4..3627429 100644 --- a/src/fast_corners/fast_private.ts +++ b/src/fast_corners/fast_private.ts @@ -1,4 +1,21 @@ // private functions + +/** + * Computes the FAST-16 corner score for a candidate pixel: the largest + * threshold for which the pixel would still be detected as a corner + * (used for non-maximum suppression in `fast_corners.detect`). + * + * The score is derived from the min/max intensity differences over every + * contiguous 9-pixel arc of the 16-pixel Bresenham circle around the + * candidate. + * + * @param src Grayscale image data. + * @param off Index of the candidate pixel in `src`. + * @param pixel Precomputed offsets of the 25 circle samples (16 + 9 wrap-around). + * @param d Scratch array receiving the 25 intensity differences. + * @param threshold Detector threshold; acts as the score lower bound. + * @returns The corner score (always ≥ `threshold` for detected corners). + */ export function _cmp_score_16( src: Uint8Array, off: number, diff --git a/src/homography2d/homography2d.ts b/src/homography2d/homography2d.ts index 79751bb..5401156 100644 --- a/src/homography2d/homography2d.ts +++ b/src/homography2d/homography2d.ts @@ -1,7 +1,28 @@ import { matrix_t } from "../matrix_t/matrix_t"; import { point_t } from "../point_t/point_t"; + +/** + * Contract every motion-model kernel must fulfil to be usable with + * `motion_estimator.ransac` / `lmeds`. Implemented by both `homography2d` + * and `affine2d` (see `src/motion_model/motion_model.ts`). + */ export interface IHomography2d { + /** + * Estimates a model from `count` point correspondences and writes it + * into `model` (a 3×3 matrix). + * + * @returns The number of models produced (0 on degenerate input). + */ run(from: point_t[], to: point_t[], model: matrix_t, count: number): number; + /** + * Computes the per-correspondence squared reprojection error of `model` + * into the `err` array. + */ error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void; + /** + * Validates a minimal sample before model estimation (e.g. rejects + * degenerate point configurations). Returning `false` makes the + * estimator draw a new sample. + */ check_subset(from: point_t[], to: point_t[], count: number): boolean; } diff --git a/src/imgproc/convol.ts b/src/imgproc/convol.ts index 8cb0e69..17b129e 100644 --- a/src/imgproc/convol.ts +++ b/src/imgproc/convol.ts @@ -1,3 +1,18 @@ +/** + * Separable 2D convolution for `U8` images with an integer kernel — the + * fixed-point fast path of `imgproc.gaussian_blur`. Runs a horizontal then a + * vertical 1D pass with edge replication, right-shifting by 8 to undo the + * kernel's 8-bit scaling and clamping results to 255. + * + * @param buf Scratch row/column buffer (from the cache pool). + * @param src_d Source image data. + * @param dst_d Destination image data (also used as the intermediate). + * @param w Image width. + * @param h Image height. + * @param filter 1D kernel, integer-scaled to sum to 256. + * @param kernel_size Number of kernel taps. + * @param half_kernel `kernel_size >> 1` (border padding size). + */ export function _convol_u8( buf: Int32Array | Float32Array, src_d: number[], @@ -107,6 +122,20 @@ export function _convol_u8( } } +/** + * Separable 2D convolution in floating point — the general path of + * `imgproc.gaussian_blur` for `S32`/`F32` data. Same two-pass structure as + * {@link _convol_u8} but without fixed-point scaling or clamping. + * + * @param buf Scratch row/column buffer (from the cache pool). + * @param src_d Source data. + * @param dst_d Destination data (also used as the intermediate). + * @param w Width. + * @param h Height. + * @param filter 1D kernel weights (normalized to sum to 1). + * @param kernel_size Number of kernel taps. + * @param half_kernel `kernel_size >> 1` (border padding size). + */ export function _convol( buf: Int32Array | Float32Array, src_d: number[], diff --git a/src/imgproc/imgproc.ts b/src/imgproc/imgproc.ts index d8163e1..cf3a3b0 100644 --- a/src/imgproc/imgproc.ts +++ b/src/imgproc/imgproc.ts @@ -6,17 +6,28 @@ import { _convol, _convol_u8 } from "./convol"; import { math } from "../math/math"; /** - * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). - * This file previously held a type-only stub whose methods threw - * "Method not implemented." — the implementation below is the inline code - * from the monolith, verbatim (the only change: gaussian_blur instantiates - * the math module directly instead of via the jsfeatNext.math static slot). + * Image-processing operations: color conversion, resampling, blurs, image + * derivatives, integral images, histogram equalization, Canny edges, Hough + * lines and geometric warps. All methods operate on {@link matrix_t} images + * and follow the original `jsfeat.imgproc` semantics. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class imgproc extends jsfeatNext { constructor() { super(); } + /** + * Converts an interleaved color buffer to a grayscale image using the + * integer-scaled BT.601 luma weights (`0.299 R + 0.587 G + 0.114 B` in + * 14-bit fixed point). + * + * @param src Source pixel buffer (e.g. canvas `ImageData.data`). + * @param w Source width. @param h Source height. + * @param dst Destination grayscale matrix (resized to `w`×`h`, 1 channel). + * @param code Channel layout of `src`: one of the `COLOR_*2GRAY` + * constants. Defaults to `COLOR_RGBA2GRAY`. + */ grayscale(src: Uint8Array | Uint8ClampedArray, w: number, h: number, dst: matrix_t, code?: number): void { // this is default image data representation in browser if (typeof code === "undefined") { @@ -62,7 +73,15 @@ export class imgproc extends jsfeatNext { } } - // derived from CCV library + /** + * Downsamples `src` to `nw`×`nh` by area averaging (derived from the CCV + * library). Chooses the fixed-point `U8` fast path when both matrices are + * `U8` and the area ratio is below 256, the float path otherwise. + * No-op unless both target dimensions are strictly smaller. + * + * @param src Source image. @param dst Destination (resized to `nw`×`nh`). + * @param nw Target width. @param nh Target height. + */ resample(src: matrix_t, dst: matrix_t, nw: number, nh: number): void { const h = src.rows, w = src.cols; @@ -77,6 +96,17 @@ export class imgproc extends jsfeatNext { } } + /** + * Box blur of a grayscale image via a sliding-window running sum + * (two transposing passes, O(1) per pixel regardless of radius). + * + * @param src Source grayscale image. + * @param dst Destination (resized to match `src`). Use an `S32` + * destination with `BOX_BLUR_NOSCALE` to avoid overflow. + * @param radius Blur radius; the window is `(2·radius + 1)²`. + * @param options `BOX_BLUR_NOSCALE` keeps raw window sums instead of + * dividing by the window area. Defaults to 0 (scaled). + */ box_blur_gray(src: matrix_t, dst: matrix_t, radius: number, options: number): void { if (typeof options === "undefined") { options = 0; @@ -257,6 +287,15 @@ export class imgproc extends jsfeatNext { this.cache.put_buffer(tmp_buff); } + /** + * Gaussian blur via separable convolution. Kernel weights come from + * `math.get_gaussian_kernel`; `U8` images use the integer fast path. + * + * @param src Source image. + * @param dst Destination (resized to match `src`). + * @param kernel_size Number of taps; 0 derives it from `sigma`. + * @param sigma Gaussian σ; 0 derives it from `kernel_size`. + */ gaussian_blur(src: matrix_t, dst: matrix_t, kernel_size: number, sigma: number): void { const jsfeatmath = new math(); if (typeof sigma === "undefined") { @@ -306,6 +345,20 @@ export class imgproc extends jsfeatNext { this.cache.put_buffer(filt_node); } + /** + * Standard Hough transform for line detection on a binary edge image + * (e.g. `canny` output). Returns lines sorted by accumulator strength. + * + * NOTE (parity): the original jsfeat version of this function is broken + * (it references undeclared `min_theta`/`max_theta` and throws in strict + * mode); jsfeatNext fixes it. + * + * @param img Binary edge image (non-zero pixels vote). + * @param rho_res Distance resolution of the accumulator, in pixels. + * @param theta_res Angle resolution, in radians. + * @param threshold Minimum accumulator votes for a line. + * @returns Array of `[rho, theta]` pairs describing detected lines. + */ hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): number[] { let r; let i; @@ -386,6 +439,15 @@ export class imgproc extends jsfeatNext { return lines; } + /** + * Halves an image with 2×2 box filtering (each output pixel is the + * rounded mean of the corresponding 2×2 source block). The optional + * source offset exists for the (not yet ported) BBF detector. + * + * @param src Source image. + * @param dst Destination (resized to `w>>1`×`h>>1`). + * @param sx Source x offset. Default 0. @param sy Source y offset. Default 0. + */ pyrdown(src: matrix_t, dst: matrix_t, sx?: number, sy?: number): void { // this is needed for bbf if (typeof sx === "undefined") { @@ -429,7 +491,15 @@ export class imgproc extends jsfeatNext { } } - // dst: [gx,gy,...] + /** + * Computes first-order image derivatives with the 3×3 Scharr operator + * (weights 3/10/3 — more rotationally accurate than Sobel). Output is a + * 2-channel map with interleaved `[gx, gy]` per pixel; borders are + * handled by reflection. + * + * @param src Source grayscale image. + * @param dst Destination derivative map (resized to `src` size, 2 channels). + */ scharr_derivatives(src: matrix_t, dst: matrix_t): void { const w = src.cols, h = src.rows; @@ -517,8 +587,14 @@ export class imgproc extends jsfeatNext { this.cache.put_buffer(buf1_node); } - // compute gradient using Sobel kernel [1 2 1] * [-1 0 1]^T - // dst: [gx,gy,...] + /** + * Computes first-order image derivatives with the 3×3 Sobel operator + * (`[1 2 1] ⊗ [-1 0 1]`). Output is a 2-channel map with interleaved + * `[gx, gy]` per pixel; borders are handled by reflection. + * + * @param src Source grayscale image. + * @param dst Destination derivative map (resized to `src` size, 2 channels). + */ sobel_derivatives(src: matrix_t, dst: matrix_t): void { const w = src.cols, h = src.rows; @@ -606,8 +682,19 @@ export class imgproc extends jsfeatNext { this.cache.put_buffer(buf1_node); } - // please note: - // dst_(type) size should be cols = src.cols+1, rows = src.rows+1 + /** + * Computes integral images ("summed-area tables") over `src` — any + * combination of: plain sum, squared sum, and 45°-tilted sum (as used by + * Haar-cascade detectors). Pass a falsy value to skip an output. + * + * Each destination must be sized `(src.cols + 1) × (src.rows + 1)`; + * the first row/column are zero. + * + * @param src Source grayscale image. + * @param dst_sum Output for pixel sums, or falsy to skip. + * @param dst_sqsum Output for squared-pixel sums, or falsy to skip. + * @param dst_tilted Output for 45°-tilted sums, or falsy to skip. + */ compute_integral_image(src: matrix_t, dst_sum: number[], dst_sqsum: number[], dst_tilted: any[]): void { const w0 = src.cols | 0, h0 = src.rows | 0, @@ -722,6 +809,13 @@ export class imgproc extends jsfeatNext { } } + /** + * Histogram equalization of a grayscale image: remaps intensities + * through the normalized cumulative histogram to maximize contrast. + * + * @param src Source grayscale image (`U8`). + * @param dst Destination (resized to match `src`). + */ equalize_histogram(src: matrix_t, dst: matrix_t): void { const w = src.cols, h = src.rows, @@ -755,6 +849,16 @@ export class imgproc extends jsfeatNext { this.cache.put_buffer(hist0_node); } + /** + * Canny edge detector: Sobel gradients → L1-magnitude non-maximum + * suppression → double-threshold hysteresis tracking. Edge pixels are + * 255, everything else 0. Blur the input first for stable results. + * + * @param src Source grayscale image. + * @param dst Destination edge map (resized to match `src`). + * @param low_thresh Lower hysteresis threshold (gradient magnitude). + * @param high_thresh Upper hysteresis threshold (strong-edge seed). + */ canny(src: matrix_t, dst: matrix_t, low_thresh: number, high_thresh: number): void { const w = src.cols, h = src.rows, @@ -936,7 +1040,17 @@ export class imgproc extends jsfeatNext { this.cache.put_buffer(stack_node); } - // transform is 3x3 matrix_t + /** + * Warps an image through a 3×3 perspective transform with bilinear + * sampling. For every destination pixel the INVERSE mapping is applied, + * so `transform` must map destination → source coordinates (invert a + * forward homography with `transform.invert_perspective_transform` first). + * + * @param src Source grayscale image. + * @param dst Destination image (same size as `src`). + * @param transform 3×3 dst→src homography. + * @param fill_value Intensity for samples falling outside `src`. Default 0. + */ warp_perspective(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { if (typeof fill_value === "undefined") { fill_value = 0; @@ -994,7 +1108,16 @@ export class imgproc extends jsfeatNext { } } - // transform is 3x3 or 2x3 matrix_t only first 6 values referenced + /** + * Warps an image through an affine transform with bilinear sampling. + * Only the first 6 coefficients of `transform` are used, mapping + * destination → source coordinates (inverse warping). + * + * @param src Source grayscale image. + * @param dst Destination image (same size as `src`). + * @param transform 2×3 (or 3×3, first 6 entries) dst→src affine transform. + * @param fill_value Intensity for samples falling outside `src`. Default 0. + */ warp_affine(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void { if (typeof fill_value === "undefined") { fill_value = 0; @@ -1045,8 +1168,14 @@ export class imgproc extends jsfeatNext { } } - // Basic RGB Skin detection filter - // from http://popscan.blogspot.fr/2012/08/skin-detection-in-digital-images.html + /** + * Basic RGB skin-color filter (rule-based, from + * http://popscan.blogspot.fr/2012/08/skin-detection-in-digital-images.html): + * writes 255 for skin-classified pixels and 0 otherwise. + * + * @param src RGBA image-like object (`width`, `height`, `data`). + * @param dst Output array of per-pixel 0/255 values (length `w·h`). + */ skindetector(src: { width: number; height: number; data: any[] }, dst: number[]): void { let r, g, b, j; let i = src.width * src.height; diff --git a/src/imgproc/resample.ts b/src/imgproc/resample.ts index 8ca182f..05f73b9 100644 --- a/src/imgproc/resample.ts +++ b/src/imgproc/resample.ts @@ -1,6 +1,17 @@ import { matrix_t } from "../matrix_t/matrix_t"; import { cache } from "../cache/cache"; +/** + * Area-average downsampling for `U8` images — the fixed-point fast path of + * `imgproc.resample`, using 8.8 fixed-point weights to avoid float math. + * Only valid when the area ratio `(w*h)/(nw*nh)` is below 256. + * + * @param src Source image (`U8`, 1–4 channels). + * @param dst Destination image, already sized to `nw`×`nh`. + * @param cache Buffer pool used for the row accumulators and offset table. + * @param nw Target width (must be < source width). + * @param nh Target height (must be < source height). + */ export function _resample_u8(src: matrix_t, dst: matrix_t, cache: cache, nw: number, nh: number) { let xofs_count = 0; const ch = src.channel, @@ -106,6 +117,16 @@ export function _resample_u8(src: matrix_t, dst: matrix_t, cache: cache, nw: num cache.put_buffer(xofs_node); } +/** + * Area-average downsampling in floating point — the general path of + * `imgproc.resample`, used for non-`U8` data or large scale factors. + * + * @param src Source image/matrix (1–4 channels). + * @param dst Destination, already sized to `nw`×`nh`. + * @param cache Buffer pool used for the row accumulators and offset table. + * @param nw Target width (must be < source width). + * @param nh Target height (must be < source height). + */ export function _resample(src: matrix_t, dst: matrix_t, cache: cache, nw: number, nh: number) { let xofs_count = 0; const ch = src.channel, diff --git a/src/index.ts b/src/index.ts index fae210e..b29c052 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,19 @@ import jsfeatNext from "./jsfeatNext"; + +/** + * Package entry point. The default export wraps the {@link jsfeatNext} class + * in an object, which is why consumers of the UMD bundle (global + * `jsfeatNext`) and of the npm package access the library as + * `jsfeatNext.jsfeatNext` — a known quirk scheduled to be addressed in the + * API-parity work (issue #41). + * + * @example + * ```ts + * import pkg from "@webarkit/jsfeat-next"; + * const jsfeat = pkg.jsfeatNext; + * const ip = new jsfeat.imgproc(); + * ``` + */ export default { jsfeatNext, }; diff --git a/src/keypoint_t/keypoint_t.ts b/src/keypoint_t/keypoint_t.ts index a4cde9b..e951f0d 100644 --- a/src/keypoint_t/keypoint_t.ts +++ b/src/keypoint_t/keypoint_t.ts @@ -1,9 +1,36 @@ +/** + * A 2D feature keypoint with position, detector response, pyramid level and + * orientation. Used by the detectors (`fast_corners`, `yape`, `yape06`) as + * output slots and by `orb.describe` as descriptor anchors. + * + * Mirrors `jsfeat.keypoint_t` from the original library. + * + * @example + * ```ts + * // pre-allocate a corner pool for a detector + * const corners = []; + * for (let i = 0; i < 500; i++) corners.push(new keypoint_t(0, 0, 0, 0, -1)); + * ``` + */ export class keypoint_t { + /** X (column) coordinate in pixels. */ public x: number; + /** Y (row) coordinate in pixels. */ public y: number; + /** Detector response / corner strength (higher = stronger). */ public score: number; + /** Pyramid level the keypoint was detected on. */ public level: number; + /** Orientation in radians; -1 when not yet computed. */ public angle: number; + + /** + * @param x X (column) coordinate. Default 0. + * @param y Y (row) coordinate. Default 0. + * @param score Detector response. Default 0. + * @param level Pyramid level. Default 0. + * @param angle Orientation in radians. Default -1 (unset). + */ constructor(x: number = 0, y: number = 0, score: number = 0, level: number = 0, angle: number = -1.0) { this.x = x; this.y = y; diff --git a/src/linalg/linalg.ts b/src/linalg/linalg.ts index f5cc296..c0b7907 100644 --- a/src/linalg/linalg.ts +++ b/src/linalg/linalg.ts @@ -5,12 +5,14 @@ import { swap, hypot } from "./linalg_base"; import matmath from "../matmath/matmath"; /** - * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). - * This file previously held a type-only stub whose methods threw - * "Method not implemented." — the implementation below is the inline code - * from the monolith, verbatim. + * Dense linear-algebra solvers built on Jacobi rotations: LU and Cholesky + * linear-system solvers, singular value decomposition (and SVD-based solve / + * pseudo-inverse) and symmetric eigen-decomposition. Mirrors `jsfeat.linalg` + * from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class linalg extends jsfeatNext { + /** Matrix-arithmetic helper used by the SVD-based routines. */ public matmath: matmath; constructor() { @@ -18,6 +20,19 @@ export class linalg extends jsfeatNext { this.matmath = new matmath(); } + /** + * Cyclic Jacobi eigen-decomposition of a symmetric `n`×`n` matrix + * (internal kernel of {@link eigenVV}). On return `W` holds the + * eigenvalues in descending order and `V` (when given) the corresponding + * eigenvectors as rows. `A` is destroyed in the process. + * + * @param A Symmetric input matrix data (mutated). + * @param astep Row stride of `A`. + * @param W Output eigenvalues (length `n`). + * @param V Output eigenvector rows, or null to skip. + * @param vstep Row stride of `V`. + * @param n Matrix dimension. + */ JacobiImpl( A: Int32Array | Float32Array | Float64Array, astep: number, @@ -187,6 +202,21 @@ export class linalg extends jsfeatNext { this.cache.put_buffer(indC_buff); } + /** + * One-sided Jacobi SVD (internal kernel of {@link svd_decompose} and + * friends). Operates on `At` (the input stored transposed) in place, + * accumulating right singular vectors into `Vt` when given; singular + * values come out in `W` in descending order, with sign correction + * applied to keep them non-negative. + * + * @param At Input matrix data, transposed (mutated into U·diag(W)). + * @param astep Row stride of `At`. + * @param _W Output singular values. + * @param Vt Output right singular vectors (transposed), or null. + * @param vstep Row stride of `Vt`. + * @param m Rows of the original matrix. @param n Columns. + * @param n1 Number of U columns to normalize (m, or 0 to skip U). + */ JacobiSVDImpl( At: Int32Array | Float32Array | Float64Array, astep: number, @@ -413,6 +443,15 @@ export class linalg extends jsfeatNext { this.cache.put_buffer(W_buff); } + /** + * Solves the square linear system `A·x = B` in place by Gaussian + * elimination with partial pivoting. `A` is destroyed and `B` is + * overwritten with the solution `x`. + * + * @param A Square coefficient matrix (mutated). + * @param B Right-hand side (n×1); receives the solution. + * @returns 1 on success, 0 when `A` is singular. + */ lu_solve(A: matrix_t, B: matrix_t): number { let i = 0, j = 0, @@ -470,6 +509,16 @@ export class linalg extends jsfeatNext { return 1; // OK } + /** + * Solves `A·x = B` in place for a symmetric positive-definite `A` via + * Cholesky-style LDL decomposition (no pivoting — faster than + * {@link lu_solve} but requires SPD input). `A` is destroyed and `B` is + * overwritten with the solution. + * + * @param A SPD coefficient matrix (mutated). + * @param B Right-hand side (n×1); receives the solution. + * @returns 1 (the decomposition does not detect failure). + */ cholesky_solve(A: matrix_t, B: matrix_t): number { let col = 0, row = 0, @@ -542,6 +591,17 @@ export class linalg extends jsfeatNext { return 1; } + /** + * Singular value decomposition `A = U · diag(W) · Vᵀ` via one-sided + * Jacobi rotations. Singular values arrive in descending order. + * + * @param A Input m×n matrix (not modified). + * @param W Output singular values (min(m,n)×1). + * @param U Output left singular vectors (m×m), or null to skip. + * @param V Output right singular vectors (n×n), or null to skip. + * @param options Bitmask of `SVD_U_T` / `SVD_V_T` to receive U and/or V + * already transposed (avoids an extra transpose). + */ svd_decompose(A: any, W: matrix_t, U: matrix_t, V: matrix_t, options: number): void { if (typeof options === "undefined") { options = 0; @@ -636,6 +696,15 @@ export class linalg extends jsfeatNext { this.cache.put_buffer(v_buff); } + /** + * Solves `A·x = B` in the least-squares sense through the SVD + * pseudo-inverse: `x = V · diag(1/w) · Uᵀ · B`, with tiny singular + * values zeroed for stability. Works for rectangular / rank-deficient A. + * + * @param A Input m×n matrix (not modified). + * @param X Output solution vector (n×1). + * @param B Right-hand side (m×1). + */ svd_solve(A: matrix_t, X: matrix_t, B: matrix_t): void { let i = 0, j = 0, @@ -684,6 +753,13 @@ export class linalg extends jsfeatNext { this.cache.put_buffer(v_buff); } + /** + * Moore–Penrose pseudo-inverse via SVD: `Ai = V · diag(1/w) · Uᵀ`, + * with tiny singular values zeroed. Valid for any matrix shape/rank. + * + * @param Ai Output pseudo-inverse (n×m). + * @param A Input m×n matrix (not modified). + */ svd_invert(Ai: matrix_t, A: matrix_t): void { let i = 0, j = 0, @@ -728,6 +804,15 @@ export class linalg extends jsfeatNext { this.cache.put_buffer(v_buff); } + /** + * Eigen-decomposition of a symmetric matrix by cyclic Jacobi rotations. + * Eigenvalues come out in descending order; eigenvectors are the rows of + * `vects`. Used by `homography2d.run` to solve the DLT system. + * + * @param A Symmetric input matrix (not modified; copied internally). + * @param vects Output eigenvector rows (n×n), or null to skip. + * @param vals Output eigenvalues (n×1), optional. + */ eigenVV(A: matrix_t, vects: matrix_t, vals?: matrix_t): void { let n = A.cols, i = n * n; diff --git a/src/linalg/linalg_base.ts b/src/linalg/linalg_base.ts index 2fe31fe..71d2c0a 100644 --- a/src/linalg/linalg_base.ts +++ b/src/linalg/linalg_base.ts @@ -1,9 +1,26 @@ +/** + * Swaps two elements of a typed array in place. + * + * @param A The array to mutate. + * @param i0 Index of the first element. + * @param i1 Index of the second element. + * @param t Scratch variable (its incoming value is ignored). + */ export function swap(A: Int32Array | Float32Array | Float64Array, i0: number, i1: number, t: number): void { t = A[i0]; A[i0] = A[i1]; A[i1] = t; } +/** + * Numerically stable `sqrt(a² + b²)` (Euclidean hypotenuse) that avoids + * overflow/underflow by factoring out the larger magnitude — the classic + * BLAS-style formulation used inside the Jacobi SVD/eigen routines. + * + * @param a First component. + * @param b Second component. + * @returns `sqrt(a² + b²)` computed without squaring the raw inputs. + */ export function hypot(a: number, b: number): number { a = Math.abs(a); b = Math.abs(b); diff --git a/src/math/math.ts b/src/math/math.ts index 7709ca3..bfb3991 100644 --- a/src/math/math.ts +++ b/src/math/math.ts @@ -3,12 +3,12 @@ import { matrix_t } from "../matrix_t/matrix_t"; import { JSFEAT_CONSTANTS } from "../constants/constants"; /** - * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). - * This file previously held a type-only stub whose methods threw - * "Method not implemented." — the implementation below is the inline code - * from the monolith, verbatim. + * General math utilities: Gaussian-kernel generation, an in-place quicksort + * and a selection-based median. Mirrors `jsfeat.math` from the original + * library. (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class math extends jsfeatNext { + /** Iterative-quicksort bounds stack (48 nesting levels × lo/hi pairs). */ private qsort_stack: Int32Array; constructor() { @@ -16,6 +16,18 @@ export class math extends jsfeatNext { this.qsort_stack = new Int32Array(48 * 2); } + /** + * Fills `kernel` with a normalized 1D Gaussian. For small odd sizes (≤7) + * with `sigma <= 0` the classic fixed binomial kernels are used; + * otherwise the kernel is sampled from `exp(-x²/2σ²)` with the OpenCV + * default `σ = 0.3·((size-1)/2 - 1) + 0.8` when `sigma <= 0`. + * + * @param size Number of taps (kernel length). + * @param sigma Gaussian standard deviation; `<= 0` selects the default. + * @param kernel Output array of `size` weights. + * @param data_type `U8_t` scales weights to integers summing to 256; + * any other type yields floats summing to 1. + */ get_gaussian_kernel(size: number, sigma: number, kernel: Float32Array | Int32Array, data_type: number): void { let i = 0, x = 0.0, @@ -85,6 +97,19 @@ export class math extends jsfeatNext { this.cache.put_buffer(kern_node); } + /** + * Computes the 3×3 perspective transform mapping four source points onto + * four destination points and writes it into `model`. + * + * @deprecated Use `transform.perspective_4point_transform()` instead — + * this copy exists only for parity with the distributed jsfeat bundle + * (where it lives under `jsfeat.math`) and logs a deprecation warning. + * + * @param model 3×3 destination matrix. + * + * The remaining sixteen number arguments are the four `(src, dst)` point + * pairs, interleaved as `src_x0, src_y0, dst_x0, dst_y0, …` for points 0–3. + */ // model is 3x3 matrix_t perspective_4point_transform( model: matrix_t, @@ -223,6 +248,15 @@ export class math extends jsfeatNext { // The current implementation was derived from *BSD system qsort(): // Copyright (c) 1992, 1993 // The Regents of the University of California. All rights reserved. + /** + * In-place iterative quicksort of `array[low..high]` (inclusive bounds) + * using median-of-three pivoting and an insertion sort for tiny spans. + * + * @param array Values to sort (mutated in place). + * @param low First index of the range. + * @param high Last index of the range (inclusive). + * @param cmp "Less than" comparator: truthy when `a < b`. + */ qsort(array: number[], low: number, high: number, cmp: (a: number, b: number) => number): void { const isort_thresh = 7; let t, ta, tb, tc; @@ -435,9 +469,19 @@ export class math extends jsfeatNext { } } - // NB: motion_estimator.lmeds calls this with a Float32Array cache buffer, - // so the signature accepts typed arrays as well as plain number[] (the old - // stub hid this behind `any`). + /** + * Selects the median of `array[low..high]` by Hoare's selection + * (quickselect). The array is PARTIALLY REORDERED in the process — pass a + * copy if the original order matters. + * + * NB: `motion_estimator.lmeds` calls this with a `Float32Array` cache + * buffer, so the signature accepts typed arrays as well as `number[]`. + * + * @param array Values to select from (mutated). + * @param low First index of the range. + * @param high Last index of the range (inclusive). + * @returns The median value of the range. + */ median(array: number[] | Int32Array | Float32Array, low: number, high: number): number { let w; let middle = 0, diff --git a/src/matmath/matmath.ts b/src/matmath/matmath.ts index 783e25d..51099a0 100644 --- a/src/matmath/matmath.ts +++ b/src/matmath/matmath.ts @@ -1,7 +1,21 @@ import { matrix_t } from "../matrix_t/matrix_t"; + +/** + * General matrix arithmetic on {@link matrix_t} operands: transpose, + * several multiplication variants optimized for common shapes, and small + * fixed-size 3×3 helpers used by the geometric-transform code. + * + * Mirrors `jsfeat.matmath` from the original library. + */ export default class matmath { constructor() {} + /** + * Fills `M` with `value` on the main diagonal and zeros elsewhere. + * + * @param M Matrix to overwrite. + * @param value Diagonal value; defaults to 1. + */ identity(M: matrix_t, value: number): void { if (typeof value === "undefined") { value = 1; @@ -21,6 +35,12 @@ export default class matmath { } } + /** + * Writes the transpose of `A` into `At` (`At = Aᵀ`). + * + * @param At Destination (`A.cols` × `A.rows`). + * @param A Source matrix. + */ transpose(At: matrix_t, A: matrix_t): void { let i = 0, j = 0, @@ -38,7 +58,12 @@ export default class matmath { } } - // C = A * B + /** + * General matrix product `C = A · B`. + * + * @param C Destination (`B.cols` × `A.rows`); must not alias A or B. + * @param A Left operand. @param B Right operand (`B.rows === A.cols`). + */ multiply(C: matrix_t, A: matrix_t, B: matrix_t): void { let i = 0, j = 0, @@ -69,7 +94,12 @@ export default class matmath { } } - // C = A * B' + /** + * Product with transposed right operand: `C = A · Bᵀ`. + * + * @param C Destination (`B.rows` × `A.rows`). + * @param A Left operand. @param B Right operand (`B.cols === A.cols`). + */ multiply_ABt(C: matrix_t, A: matrix_t, B: matrix_t): void { let i = 0, j = 0, @@ -98,7 +128,12 @@ export default class matmath { } } - // C = A' * B + /** + * Product with transposed left operand: `C = Aᵀ · B`. + * + * @param C Destination (`B.cols` × `A.cols`). + * @param A Left operand (`A.rows === B.rows`). @param B Right operand. + */ multiply_AtB(C: matrix_t, A: matrix_t, B: matrix_t): void { let i = 0, j = 0, @@ -129,7 +164,12 @@ export default class matmath { } } - // C = A * A' + /** + * Symmetric self-product `C = A · Aᵀ`, computing only the upper triangle + * and mirroring it. + * + * @param C Destination (`A.rows` × `A.rows`, symmetric). @param A Operand. + */ multiply_AAt(C: matrix_t, A: matrix_t): void { let i = 0, j = 0, @@ -162,7 +202,12 @@ export default class matmath { } } - // C = A' * A + /** + * Symmetric self-product `C = Aᵀ · A` (the normal-equations matrix), + * computing only the upper triangle and mirroring it. + * + * @param C Destination (`A.cols` × `A.cols`, symmetric). @param A Operand. + */ multiply_AtA(C: matrix_t, A: matrix_t): void { let i = 0, j = 0, @@ -197,6 +242,11 @@ export default class matmath { } // various small matrix operations + /** + * Fills a 3×3 matrix with `value` on the diagonal and zeros elsewhere. + * + * @param M 3×3 destination. @param value Diagonal value; defaults to 1. + */ identity_3x3(M: matrix_t, value: number) { if (typeof value === "undefined") { value = 1; @@ -207,6 +257,13 @@ export default class matmath { dt[5] = dt[6] = dt[7] = 0; } + /** + * Inverts a 3×3 matrix by the adjugate/determinant closed form + * (no pivoting — the input must be non-singular). Safe to call with + * `from === to` (in-place inversion). + * + * @param from 3×3 source matrix. @param to 3×3 destination. + */ invert_3x3(from: matrix_t, to: matrix_t): void { const A = from.data, invA = to.data; @@ -238,7 +295,12 @@ export default class matmath { invA[8] = (t9 - t15) * t26; } - // C = A * B + /** + * Fixed-size 3×3 product `C = A · B`, fully unrolled. All operands are + * read into locals first, so `C` may alias `A` or `B`. + * + * @param C 3×3 destination. @param A Left operand. @param B Right operand. + */ multiply_3x3(C: matrix_t, A: matrix_t, B: matrix_t): void { const Cd = C.data, Ad = A.data, @@ -274,6 +336,11 @@ export default class matmath { Cd[8] = m1_6 * m2_2 + m1_7 * m2_5 + m1_8 * m2_8; } + /** + * Determinant of a 3×3 matrix by cofactor expansion. + * + * @param M 3×3 matrix. @returns `det(M)`. + */ mat3x3_determinant(M: matrix_t): number { const md = M.data; return ( @@ -286,6 +353,13 @@ export default class matmath { ); } + /** + * Determinant of a 3×3 matrix given as nine scalars (row-major M11…M33). + * Scalar variant of {@link mat3x3_determinant}, used by the RANSAC + * degeneracy checks without building a matrix. + * + * @returns The determinant value. + */ determinant_3x3( M11: number, M12: number, diff --git a/src/matrix_t/matrix_t.ts b/src/matrix_t/matrix_t.ts index 67929aa..a00aa9f 100644 --- a/src/matrix_t/matrix_t.ts +++ b/src/matrix_t/matrix_t.ts @@ -2,27 +2,75 @@ import { IData_Type, data_type } from "../data_type/data_type"; import { data_t } from "../node_utils/data_t"; import { JSFEAT_CONSTANTS } from "../constants/constants"; +/** + * Public shape of {@link matrix_t}: a 2D dense matrix (or image) backed by a + * typed array. + */ export interface IMatrix_T { + /** Number of columns (image width). */ cols: number; + /** Number of rows (image height). */ rows: number; + /** Data-type component of the matrix type signature (e.g. `U8_t`, `F32_t`). */ type: number; + /** Number of interleaved channels per element (1–4). */ channel: number; + /** The typed-array view holding the matrix elements, row-major. */ data: any; + /** The underlying {@link data_t} buffer that `data` is a view over. */ buffer: data_t; + /** (Re)allocates the backing buffer from the current cols/rows/channel/type. */ allocate: () => void; + /** Copies this matrix's elements into another matrix of at least equal size. */ copy_to: (other: any) => void; + /** Changes the logical dimensions, reallocating only when the buffer is too small. */ resize: (c: number, r: number, ch: any) => void; } +/** + * The fundamental data container of jsfeatNext: a dense, row-major 2D matrix + * backed by a single typed array. Used for grayscale images, multi-channel + * derivative maps, transformation matrices and linear-algebra operands alike. + * + * The element type and channel count are packed into a single type signature, + * e.g. `jsfeatNext.U8_t | jsfeatNext.C1_t` for an 8-bit single-channel image + * or `jsfeatNext.F32_t | jsfeatNext.C1_t` for a float matrix. + * + * Mirrors `jsfeat.matrix_t` from the original library. + * + * @example + * ```ts + * const img = new matrix_t(640, 480, jsfeatNext.U8_t | jsfeatNext.C1_t); + * img.data[0] = 255; // top-left pixel + * ``` + */ export class matrix_t implements IMatrix_T { + /** Data-type helper used to decode the packed type signature. */ private dt: IData_Type; + /** Data-type component of the packed type signature (`U8_t`, `S32_t`, `F32_t`, `F64_t`). */ public type: number; + /** Number of interleaved channels per element (1–4). */ public channel: number; + /** Number of columns (image width). */ public cols: number; + /** Number of rows (image height). */ public rows: number; + /** + * Typed-array view over {@link buffer} matching {@link type}: + * `Uint8Array`, `Int32Array`, `Float32Array` or `Float64Array`. + * Element `(row, col, ch)` lives at index `(row * cols + col) * channel + ch`. + */ public data: any; + /** Raw backing storage; several views of it are exposed through {@link data}. */ public buffer: data_t; + /** + * @param c Number of columns (width). + * @param r Number of rows (height). + * @param _data_type Packed type signature, e.g. `U8_t | C1_t`. + * @param _data_buffer Optional pre-existing buffer to wrap instead of + * allocating a new one (used with cache-pool buffers). + */ constructor(c: number, r: number, _data_type: number, _data_buffer?: data_t) { this.dt = new data_type(); this.type = this.dt._get_data_type(_data_type) | 0; @@ -45,6 +93,11 @@ export class matrix_t implements IMatrix_T { } } + /** + * Allocates a fresh backing buffer sized from the current + * `cols * rows * channel * sizeof(type)` and points {@link data} at the + * view matching {@link type}. Any previous buffer reference is dropped. + */ allocate(): void { // clear references delete this.data; @@ -61,6 +114,13 @@ export class matrix_t implements IMatrix_T { : this.buffer.f64; } + /** + * Copies every element of this matrix into `other` (unrolled by 4 for + * speed). The destination must be at least `cols * rows * channel` + * elements large; no bounds checking is performed. + * + * @param other Destination matrix receiving the element values. + */ copy_to(other: IMatrix_T): void { const od = other.data, td = this.data; @@ -77,6 +137,15 @@ export class matrix_t implements IMatrix_T { } } + /** + * Changes the logical dimensions of the matrix. The backing buffer is + * reallocated only when the new size does not fit in the current one; + * otherwise the existing storage (and its contents) are reused. + * + * @param c New number of columns. + * @param r New number of rows. + * @param ch New channel count; defaults to the current {@link channel}. + */ resize(c: number, r: number, ch: number): void { if (typeof ch === "undefined") { ch = this.channel; diff --git a/src/motion_estimator/motion_estimator.ts b/src/motion_estimator/motion_estimator.ts index 40ed428..6896440 100644 --- a/src/motion_estimator/motion_estimator.ts +++ b/src/motion_estimator/motion_estimator.ts @@ -8,16 +8,32 @@ import { homography2d } from "../motion_model/motion_model"; import { math } from "../math/math"; /** - * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). - * This file previously held a type-only stub — the implementation below is the - * inline code from the monolith, verbatim (the only change: lmeds instantiates - * the math module directly instead of via the jsfeatNext.math static slot). + * Robust motion-model estimation from noisy point correspondences via + * RANSAC or LMEDS, parameterized by a kernel implementing + * {@link IHomography2d} (`homography2d` or `affine2d` from + * `src/motion_model/motion_model.ts`). + * + * Mirrors `jsfeat.motion_estimator` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class motion_estimator extends jsfeatNext { constructor() { super(); } + /** + * Draws a random minimal sample of `need_cnt` distinct correspondences + * (via `Math.random`) and validates it with `kernel.check_subset`. + * Retries up to 1000 times before giving up. + * + * @param kernel The motion-model kernel (validates the sample). + * @param from Source points. @param to Destination points. + * @param need_cnt Sample size to draw. + * @param max_cnt Total number of correspondences to draw from. + * @param from_sub Output array receiving the sampled source points. + * @param to_sub Output array receiving the sampled destination points. + * @returns `true` when a valid subset was found. + */ get_subset( kernel: homography2d, from: point_t[], @@ -63,6 +79,19 @@ export class motion_estimator extends jsfeatNext { return i == need_cnt && ssiter < max_try; } + /** + * Classifies every correspondence as inlier/outlier by thresholding the + * kernel's squared reprojection error of `model`. + * + * @param kernel The motion-model kernel (provides `error`). + * @param model Model to evaluate. + * @param from Source points. @param to Destination points. + * @param count Number of correspondences. + * @param thresh Inlier error threshold in pixels (squared internally). + * @param err Scratch array receiving per-point squared errors. + * @param mask Output 0/1 inlier mask (length `count`). + * @returns The number of inliers. + */ find_inliers( kernel: homography2d, model: matrix_t, @@ -88,6 +117,21 @@ export class motion_estimator extends jsfeatNext { return numinliers; } + /** + * RANSAC estimation: repeatedly fits the kernel's model to random + * minimal samples, keeps the hypothesis with the most inliers (adapting + * the iteration count from the observed inlier ratio), and finally + * refits the model on all inliers of the best hypothesis. + * + * @param params Estimation parameters ({@link ransac_params_t}). + * @param kernel Motion-model kernel (`homography2d` / `affine2d`). + * @param from Source points. @param to Destination points. + * @param count Number of correspondences. + * @param model Output 3×3 model matrix. + * @param mask Output 0/1 inlier mask (`count`×1 matrix), optional. + * @param max_iters Iteration cap. Default 1000. + * @returns `true` when a model with enough inliers was found. + */ ransac( params: ransac_params_t, kernel: any, @@ -186,6 +230,21 @@ export class motion_estimator extends jsfeatNext { return result; } + /** + * Least-median-of-squares estimation: like {@link ransac} but scores each + * hypothesis by the MEDIAN squared error (no inlier threshold needed — + * robust up to 50% outliers), then derives an inlier threshold from the + * winning median's robust standard deviation and refits on the inliers. + * + * @param params Estimation parameters (`thresh` is ignored). + * @param kernel Motion-model kernel (`homography2d` / `affine2d`). + * @param from Source points. @param to Destination points. + * @param count Number of correspondences. + * @param model Output 3×3 model matrix. + * @param mask Output 0/1 inlier mask (`count`×1 matrix), optional. + * @param max_iters Iteration cap. Default 1000. + * @returns `true` when a model was found. + */ lmeds( params: ransac_params_t, kernel: any, diff --git a/src/motion_estimator/ransac_params_t.ts b/src/motion_estimator/ransac_params_t.ts index 61936a3..e5c4410 100644 --- a/src/motion_estimator/ransac_params_t.ts +++ b/src/motion_estimator/ransac_params_t.ts @@ -1,14 +1,40 @@ +/** + * Parameter block for `motion_estimator.ransac` / `motion_estimator.lmeds`. + * + * Mirrors `jsfeat.ransac_params_t` from the original library. + */ export class ransac_params_t { + /** Minimal sample size per model hypothesis (e.g. 4 for homography2d, 3 for affine2d). */ public size: number; + /** Inlier reprojection-error threshold in pixels (unused by LMEDS). */ public thresh: number; + /** Assumed outlier ratio (0–1) used to derive the iteration count. */ public eps: number; + /** Desired probability (0–1) of finding an outlier-free sample. */ public prob: number; + + /** + * @param size Minimal sample size per hypothesis. Default 0. + * @param thresh Inlier error threshold in pixels. Default 0.5. + * @param eps Assumed outlier ratio. Default 0.5. + * @param prob Desired success probability. Default 0.99. + */ constructor(size: number = 0, thresh: number = 0.5, eps: number = 0.5, prob: number = 0.99) { this.size = size; this.thresh = thresh; this.eps = eps; this.prob = prob; } + + /** + * Recomputes the RANSAC iteration count from the standard formula + * `log(1 - prob) / log(1 - (1 - eps)^size)`, capped at `max_iters`. + * Called by the estimator whenever a better inlier ratio is found. + * + * @param _eps Current outlier-ratio estimate. + * @param max_iters Upper bound on the number of iterations. + * @returns The updated iteration count (integer). + */ update_iters(_eps: number, max_iters: number): number { const num = Math.log(1 - this.prob); const denom = Math.log(1 - Math.pow(1 - _eps, this.size)); diff --git a/src/motion_model/motion_model.ts b/src/motion_model/motion_model.ts index 6bdd68e..2eca099 100644 --- a/src/motion_model/motion_model.ts +++ b/src/motion_model/motion_model.ts @@ -6,16 +6,20 @@ import matmath from "../matmath/matmath"; import { linalg } from "../linalg/linalg"; /** - * Motion-model kernels for motion_estimator (issue #47): the motion_model - * base plus the affine2d and homography2d kernels, moved verbatim from the - * src/jsfeatNext.ts monolith (the only change: kernels instantiate linalg - * via direct module import instead of the jsfeatNext.linalg static slot). - * In original jsfeat these live under the jsfeat.motion_model namespace. + * Shared base of the motion-model kernels ({@link affine2d}, + * {@link homography2d}): scratch matrices plus the point-normalization and + * degeneracy helpers both kernels use. In original jsfeat these classes live + * under the `jsfeat.motion_model` namespace. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class motion_model extends jsfeatNext { + /** 3×3 normalization transform for the source points. */ public T0: matrix_t; + /** 3×3 normalization transform for the destination points. */ public T1: matrix_t; + /** 6×6 normal-equations matrix scratch (`Aᵀ·A`). */ public AtA: matrix_t; + /** 6×1 normal-equations right-hand side scratch (`Aᵀ·B`). */ public AtB: matrix_t; constructor() { @@ -26,11 +30,22 @@ export class motion_model extends jsfeatNext { this.AtB = new matrix_t(6, 1, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); } + /** @returns `x²`. */ sqr(x: number): number { return x * x; } - // does isotropic normalization + /** + * Computes isotropic (Hartley) normalization transforms for both point + * sets: each is translated to its centroid and scaled so the mean + * distance from the origin is √2 — the standard conditioning step before + * solving for a transform. + * + * @param from Source points. @param to Destination points. + * @param T0 Output 3×3 transform (row-major array) for `from`. + * @param T1 Output 3×3 transform (row-major array) for `to`. + * @param count Number of points. + */ iso_normalize_points(from: point_t[], to: point_t[], T0: number[], T1: number[], count: number): void { let i = 0; let cx0 = 0.0, @@ -84,6 +99,14 @@ export class motion_model extends jsfeatNext { T1[8] = 1.0; } + /** + * Checks whether the last point of a minimal sample lies on a line + * through any two previously selected points (a degenerate + * configuration for transform estimation). + * + * @param points The sampled points. @param count Sample size. + * @returns `true` when a collinear triple exists. + */ have_collinear_points(points: point_t[], count: number): boolean { let j = 0, k = 0, @@ -112,11 +135,26 @@ export class motion_model extends jsfeatNext { } } +/** + * Affine (6-DOF) motion-model kernel for {@link motion_estimator}: estimates + * the 2×3 affine transform (stored in a 3×3 matrix with `[0,0,1]` bottom + * row) by least squares over normalized points. Minimal sample size: 3. + */ export class affine2d extends motion_model { constructor() { super(); } + /** + * Estimates the affine transform mapping `from` → `to` by solving the + * normal equations (`lu_solve`) over isotropically normalized points, + * then denormalizes into `model`. + * + * @param from Source points. @param to Destination points. + * @param model Output 3×3 matrix (last row set to `[0, 0, 1]`). + * @param count Number of correspondences (≥ 3). + * @returns 1 (one model produced). + */ run(from: point_t[], to: point_t[], model: matrix_t, count: number): number { let i = 0, j = 0; @@ -179,9 +217,17 @@ export class affine2d extends motion_model { return 1; } - // Per-point reprojection error for the affine model. Ported from original - // jsfeat's affine2d; jsfeatNext was missing it, which made RANSAC/LMEDS - // with an affine2d kernel throw. See issue #51. + /** + * Per-point squared reprojection error of the affine model: + * `err[i] = |to[i] - A·from[i]|²`. (Ported from original jsfeat's + * affine2d; jsfeatNext was missing it, which made RANSAC/LMEDS with an + * affine2d kernel throw — see issue #51.) + * + * @param from Source points. @param to Destination points. + * @param model 3×3 affine model (first 6 entries used). + * @param err Output per-point squared errors. + * @param count Number of correspondences. + */ error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void { let i = 0; let pt0, pt1; @@ -197,13 +243,27 @@ export class affine2d extends motion_model { } } + /** + * Affine sampling has no degenerate-quad check — every minimal sample is + * accepted (matches original jsfeat). + * + * @returns Always `true`. + */ check_subset(from: point_t[], to: point_t[], count: number): boolean { return true; // all good } } +/** + * Homography (8-DOF perspective) motion-model kernel for + * {@link motion_estimator}: estimates the 3×3 homography by the normalized + * DLT method (smallest eigenvector of `LᵀL` via `linalg.eigenVV`). + * Minimal sample size: 4. + */ export class homography2d extends motion_model { + /** 9×9 scratch for the DLT normal matrix `LᵀL`. */ public mLtL: matrix_t; + /** 9×9 scratch for its eigenvectors. */ public Evec: matrix_t; constructor() { @@ -212,6 +272,17 @@ export class homography2d extends motion_model { this.Evec = new matrix_t(9, 9, JSFEAT_CONSTANTS.F32_t | JSFEAT_CONSTANTS.C1_t); } + /** + * Estimates the homography mapping `from` -> `to` by normalized DLT: + * builds the 9x9 normal matrix over normalized points, takes the + * eigenvector of the smallest eigenvalue as the model, denormalizes and + * scales so `model[8] === 1`. + * + * @param from Source points. @param to Destination points. + * @param model Output 3x3 homography. + * @param count Number of correspondences (>= 4). + * @returns 1 on success, 0 on a degenerate (zero-spread) configuration. + */ run(from: point_t[], to: point_t[], model: matrix_t, count: number): number { let i = 0, j = 0; @@ -373,6 +444,15 @@ export class homography2d extends motion_model { return 1; } + /** + * Per-point squared reprojection error of the homography: + * `err[i] = |to[i] - project(model, from[i])|^2`. + * + * @param from Source points. @param to Destination points. + * @param model 3x3 homography to evaluate. + * @param err Output per-point squared errors. + * @param count Number of correspondences. + */ error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void { let i = 0; let pt0, @@ -393,6 +473,13 @@ export class homography2d extends motion_model { } } + /** + * Rejects minimal samples whose four points are not consistently + * oriented (mixed triangle-orientation signs between the source and + * destination quads), which would produce a flipped homography. + * + * @returns `true` when the 4-point sample is usable. + */ check_subset(from: point_t[], to: point_t[], count: number): boolean { // seems to reject good subsets actually //if( have_collinear_points(from, count) || have_collinear_points(to, count) ) { diff --git a/src/node_utils/_pool_node_t.ts b/src/node_utils/_pool_node_t.ts index 908ae81..e826095 100644 --- a/src/node_utils/_pool_node_t.ts +++ b/src/node_utils/_pool_node_t.ts @@ -1,19 +1,37 @@ import { IData_T, data_t } from "./data_t"; +/** Public shape of {@link _pool_node_t}. */ export interface IPool_Node_T { + /** Replaces the node's storage with a larger buffer. */ resize: (size_in_bytes: number) => void; } +/** + * One node of the `cache` buffer pool: a linked-list entry wrapping a + * {@link data_t} and mirroring its typed-array views directly on the node, + * so borrowers can use `node.f32`, `node.i32`, etc. without indirection. + */ export default class _pool_node_t implements IPool_Node_T { + /** Next node in the pool's linked list (`null` at the tail). */ public next: any; + /** The wrapped storage object. */ public data?: IData_T; + /** Byte size of the current storage (aligned to a multiple of 8). */ public size: number; + /** The underlying `ArrayBuffer` (mirror of `data.buffer`). */ public buffer: any; + /** Unsigned 8-bit view (mirror of `data.u8`). */ public u8: Uint8Array; + /** Signed 32-bit integer view (mirror of `data.i32`). */ public i32: Int32Array; + /** 32-bit float view (mirror of `data.f32`). */ public f32: Float32Array; + /** 64-bit float view (mirror of `data.f64`). */ public f64: Float64Array; + /** + * @param size_in_bytes Initial byte size of the node's storage. + */ constructor(size_in_bytes: number) { this.next = null; this.data = new data_t(size_in_bytes); @@ -24,6 +42,15 @@ export default class _pool_node_t implements IPool_Node_T { this.f32 = this.data.f32; this.f64 = this.data.f64; } + + /** + * Discards the current storage and allocates a fresh, larger one, + * refreshing every typed-array view. Called by `cache.get_buffer` when a + * borrower requests more space than the node currently holds. Previous + * contents are NOT preserved. + * + * @param size_in_bytes New byte size (aligned up to a multiple of 8). + */ resize(size_in_bytes: number): void { delete this.data; this.data = new data_t(size_in_bytes); diff --git a/src/node_utils/data_t.ts b/src/node_utils/data_t.ts index 4fc2bde..8237ab5 100644 --- a/src/node_utils/data_t.ts +++ b/src/node_utils/data_t.ts @@ -1,19 +1,46 @@ +/** Public shape of {@link data_t}: raw storage with multi-type views. */ export interface IData_T { + /** Byte size of the buffer (aligned to a multiple of 8). */ size: number; + /** The underlying `ArrayBuffer`. */ buffer: ArrayBuffer; + /** Unsigned 8-bit view over {@link buffer}. */ u8: Uint8Array; + /** Signed 32-bit integer view over {@link buffer}. */ i32: Int32Array; + /** 32-bit float view over {@link buffer}. */ f32: Float32Array; + /** 64-bit float view over {@link buffer}. */ f64: Float64Array; } +/** + * Raw byte storage exposing typed-array views of every element type the + * library uses. `matrix_t` and the cache pool build on it: allocating one + * buffer and reading it as `u8`/`i32`/`f32`/`f64` lets algorithms reinterpret + * scratch memory without extra allocations. + * + * The byte size is aligned up to a multiple of 8 so the `f64` view is valid. + */ export class data_t implements IData_T { + /** Byte size of the buffer (aligned to a multiple of 8). */ public size: number; + /** The underlying `ArrayBuffer`. */ public buffer: ArrayBuffer; + /** Unsigned 8-bit view over {@link buffer}. */ public u8: Uint8Array; + /** Signed 32-bit integer view over {@link buffer}. */ public i32: Int32Array; + /** 32-bit float view over {@link buffer}. */ public f32: Float32Array; + /** 64-bit float view over {@link buffer}. */ public f64: Float64Array; + + /** + * @param size_in_bytes Requested byte size; rounded up to a multiple of 8. + * @param buffer Optional existing buffer to wrap instead of + * allocating (its length becomes {@link size}). + */ constructor(size_in_bytes: number, buffer?: any) { // we need align size to multiple of 8 this.size = ((size_in_bytes + 7) | 0) & -8; diff --git a/src/optical_flow_lk/optical_flow_lk.ts b/src/optical_flow_lk/optical_flow_lk.ts index 9606c7f..8917a14 100644 --- a/src/optical_flow_lk/optical_flow_lk.ts +++ b/src/optical_flow_lk/optical_flow_lk.ts @@ -5,13 +5,16 @@ import { JSFEAT_CONSTANTS } from "../constants/constants"; import { imgproc } from "../imgproc/imgproc"; /** - * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). - * This file previously held a type-only stub — the implementation below is the - * inline code from the monolith, verbatim (the only change: the constructor - * instantiates the imgproc module directly instead of via the - * jsfeatNext.imgproc static slot). + * Pyramidal Lucas–Kanade sparse optical flow: tracks a set of points from a + * previous frame to the current one by iteratively minimizing the local + * intensity difference, coarse-to-fine across image pyramids (the classic + * Bouguet formulation, using Scharr derivatives). + * + * Mirrors `jsfeat.optical_flow_lk` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class optical_flow_lk extends jsfeatNext { + /** Bound `imgproc.scharr_derivatives`, used to build the gradient maps. */ public scharr_deriv: any; constructor() { @@ -20,6 +23,26 @@ export class optical_flow_lk extends jsfeatNext { this.scharr_deriv = _imgproc.scharr_derivatives; } + /** + * Tracks `count` points between two image pyramids (both already built + * with `pyramid_t.build`). For each point the flow is estimated at the + * coarsest level and refined down to level 0. + * + * @param prev_pyr Pyramid of the previous frame. + * @param curr_pyr Pyramid of the current frame. + * @param prev_xy Input point coordinates, interleaved `[x0,y0,x1,y1,…]`. + * @param curr_xy Output tracked coordinates (same layout). Seed it with + * a prediction or a copy of `prev_xy`. + * @param count Number of points to track. + * @param win_size Side of the square tracking window (e.g. 15 or 21). + * @param max_iter Max refinement iterations per level. Default 30. + * @param status Output per-point flags: 1 = tracked, 0 = lost. + * Allocated internally when omitted. + * @param eps Convergence threshold on the update step. Default 0.01. + * @param min_eigen_threshold Minimum normalized eigenvalue of the + * spatial-gradient matrix; below it a point is dropped + * (textureless window). Default 0.0001. + */ track( prev_pyr: pyramid_t, curr_pyr: pyramid_t, diff --git a/src/orb/bit_pattern_31.ts b/src/orb/bit_pattern_31.ts index 4d31eb6..b9148fa 100644 --- a/src/orb/bit_pattern_31.ts +++ b/src/orb/bit_pattern_31.ts @@ -1,3 +1,10 @@ +/** + * The learned ORB sampling pattern: 256 pixel-pair comparisons inside a + * 31×31 patch, stored flat as `[x1, y1, x2, y2, …]` (1024 numbers). Each + * pair contributes one bit of the 256-bit binary descriptor produced by + * `orb.describe`. Taken verbatim from the original ORB paper / OpenCV + * implementation (the inline comments carry the training statistics). + */ export const bit_pattern_31 = [ 8, -3, 9, 5 /*mean (0), correlation (0)*/, 4, 2, 7, -12 /*mean (1.12461e-05), correlation (0.0437584)*/, -11, 9, -8, 2 /*mean (3.37382e-05), correlation (0.0617409)*/, 7, -12, 12, -13 /*mean (5.62303e-05), correlation (0.0636977)*/, diff --git a/src/orb/orb.ts b/src/orb/orb.ts index 5c7e0ff..9665015 100644 --- a/src/orb/orb.ts +++ b/src/orb/orb.ts @@ -7,16 +7,23 @@ import { bit_pattern_31 } from "./bit_pattern_31"; import { rectify_patch } from "./rectify_patch"; /** - * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). - * This file previously held a type-only stub — the implementation below is the - * inline code from the monolith, verbatim (the only change: the constructor - * instantiates the imgproc module directly instead of via the - * jsfeatNext.imgproc static slot). + * ORB binary descriptor extractor (Oriented FAST and Rotated BRIEF): for + * each keypoint a rotation-rectified 32×32 patch is sampled and 256 + * pixel-pair comparisons from the learned {@link bit_pattern_31} pattern are + * packed into a 32-byte binary descriptor. Descriptors are matched with + * Hamming distance. + * + * Mirrors `jsfeat.orb` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class orb extends jsfeatNext { + /** The learned 256-pair sampling pattern (flat `[x1,y1,x2,y2,…]`). */ public bit_pattern_31_: Int32Array; + /** Scratch 3×3 matrix for the per-keypoint rectification transform. */ public H: matrix_t; + /** Scratch 32×32 patch the keypoint neighborhood is warped into. */ public patch_img: matrix_t; + /** Image-processing helper used for the affine patch warp. */ public imgproc: imgproc; constructor() { @@ -27,6 +34,17 @@ export class orb extends jsfeatNext { this.imgproc = new imgproc(); } + /** + * Computes 256-bit (32-byte) binary descriptors for `count` keypoints. + * Each keypoint's `angle` is used to rotation-rectify its patch, making + * the descriptor rotation-invariant. + * + * @param src Source grayscale image the keypoints live in. + * @param corners Keypoints to describe (uses `x`, `y`, `angle`). + * @param count Number of keypoints to process. + * @param descriptors Destination matrix, resized to 32×`count` `U8` — + * one 32-byte descriptor per row. + */ describe(src: matrix_t, corners: keypoint_t[], count: number, descriptors: matrix_t): void { const DESCR_SIZE = 32; // bytes; let i = 0, diff --git a/src/orb/rectify_patch.ts b/src/orb/rectify_patch.ts index 3819165..71a2591 100644 --- a/src/orb/rectify_patch.ts +++ b/src/orb/rectify_patch.ts @@ -1,6 +1,21 @@ import { matrix_t } from "../matrix_t/matrix_t"; import { imgproc } from "../imgproc/imgproc"; +/** + * Extracts a rotation-rectified square patch around a keypoint: builds a 2×3 + * affine transform that rotates by `angle` around `(px, py)` and centers a + * `psize`×`psize` window, then warps the source image through it. Used by + * `orb.describe` to make the BRIEF-style descriptor rotation-invariant. + * + * @param src Source grayscale image. + * @param dst Destination patch (resized to `psize`×`psize` by the warp). + * @param angle Keypoint orientation in radians. + * @param px Keypoint x coordinate in `src`. + * @param py Keypoint y coordinate in `src`. + * @param psize Patch side length in pixels (ORB uses 32). + * @param H 3×3 scratch matrix receiving the affine transform (first 6 entries used). + * @param imgProcessor The `imgproc` instance whose `warp_affine` performs the sampling. + */ export function rectify_patch( src: matrix_t, dst: matrix_t, diff --git a/src/point_t/point_t.ts b/src/point_t/point_t.ts index 2815adc..3cc5db6 100644 --- a/src/point_t/point_t.ts +++ b/src/point_t/point_t.ts @@ -1,16 +1,33 @@ -interface IPoint_t { +/** Public shape of {@link point_t}. */ +export interface IPoint_t { + /** X (column) coordinate in pixels. */ x: number; + /** Y (row) coordinate in pixels. */ y: number; + /** Pyramid level the point belongs to. */ level: number; + /** Detector response / corner strength. */ score: number; + /** Feature orientation in radians (-1 when not computed). */ angle: number; } +/** + * A lightweight 2D feature point. Unlike `keypoint_t` the fields are + * not initialized by the constructor — detector code (e.g. + * `fast_corners.detect`) assigns them directly on pre-allocated arrays of + * points, so no per-point construction cost is paid in hot loops. + */ export class point_t implements IPoint_t { + /** X (column) coordinate in pixels. */ public x: number; + /** Y (row) coordinate in pixels. */ public y: number; + /** Pyramid level the point belongs to. */ level: number; + /** Detector response / corner strength. */ score: number; + /** Feature orientation in radians (-1 when not computed). */ angle: number; constructor() {} } diff --git a/src/pyramid_t/pyramid_t.ts b/src/pyramid_t/pyramid_t.ts index f2ddb7c..5922f91 100644 --- a/src/pyramid_t/pyramid_t.ts +++ b/src/pyramid_t/pyramid_t.ts @@ -3,15 +3,19 @@ import { matrix_t } from "../matrix_t/matrix_t"; import { imgproc } from "../imgproc/imgproc"; /** - * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). - * This file previously held a type-only stub — the implementation below is the - * inline code from the monolith, verbatim (the only change: the constructor - * instantiates the imgproc module directly instead of via the - * jsfeatNext.imgproc static slot). + * An image pyramid: level 0 holds the full-resolution image and each + * subsequent level halves the previous one via `imgproc.pyrdown`. Consumed + * by `optical_flow_lk.track` for coarse-to-fine tracking. + * + * Mirrors `jsfeat.pyramid_t` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class pyramid_t extends jsfeatNext { + /** Number of pyramid levels. */ public levels: number; + /** The level images: `data[i]` is a {@link matrix_t} of size `w>>i` × `h>>i`. */ public data: any; + /** Bound `imgproc.pyrdown` used to build the levels. */ private pyrdown: any; constructor(levels: number) { @@ -22,6 +26,13 @@ export class pyramid_t extends jsfeatNext { this.pyrdown = _imgproc.pyrdown; } + /** + * Allocates the per-level matrices for a base image of + * `start_w`×`start_h` (level `i` gets `start_w>>i` × `start_h>>i`). + * + * @param start_w Level-0 width. @param start_h Level-0 height. + * @param data_type Packed type signature for the level images (e.g. `U8_t | C1_t`). + */ allocate(start_w: number, start_h: number, data_type: number): void { let i = this.levels; while (--i >= 0) { @@ -29,6 +40,15 @@ export class pyramid_t extends jsfeatNext { } } + /** + * Fills the pyramid from `input`: optionally copies it into level 0, + * then repeatedly downsamples to populate the remaining levels. + * {@link allocate} must have been called first. + * + * @param input Level-0 source image. + * @param skip_first_level When `true` (the default) level 0 is assumed + * to already hold the input and is not copied. + */ build(input: matrix_t, skip_first_level: boolean): void { if (typeof skip_first_level === "undefined") { skip_first_level = true; diff --git a/src/transform/transform.ts b/src/transform/transform.ts index f1b993c..a6f5b03 100644 --- a/src/transform/transform.ts +++ b/src/transform/transform.ts @@ -1,4 +1,13 @@ import { matrix_t } from "../matrix_t/matrix_t"; + +/** + * 2D geometric transform construction and inversion. + * + * NOTE (parity): the original jsfeat `transform` module was never included + * in any distributed jsfeat build, and its functions take RAW ARRAYS — + * jsfeatNext's methods take {@link matrix_t} instead (same math, different + * calling convention; see the parity audit, Axis 2). + */ export class transform { constructor() {} @@ -8,6 +17,17 @@ export class transform { // we need linear algebra module first };*/ + /** + * Computes the 3×3 perspective transform (homography) that maps four + * source points onto four destination points, via the closed-form + * `R = Hl · Hr⁻¹` construction (both quads are first mapped to the unit + * square). Writes the 9 coefficients into `model`. + * + * @param model 3×3 destination matrix. + * + * The remaining sixteen number arguments are the four `(src, dst)` point + * pairs, interleaved as `src_x0, src_y0, dst_x0, dst_y0, …` for points 0–3. + */ perspective_4point_transform( model: matrix_t, src_x0: number, @@ -139,6 +159,13 @@ export class transform { mat[8] = -Hl6 * t50 - Hl7 * (t44 * t15) + t47 * t15; } + /** + * Inverts a 2×3 affine transform in closed form (via the 2×2 linear + * part's determinant). Only the first 6 entries of `src`/`dst` are used. + * + * @param src Source affine transform (2×3 coefficients). + * @param dst Destination for the inverse (2×3 coefficients). + */ invert_affine_transform(src: matrix_t, dst: matrix_t): void { const src_d = src.data; const dst_d = dst.data; @@ -161,6 +188,13 @@ export class transform { dst_d[5] = det * (m13 * m21 - m11 * m23); } + /** + * Inverts a 3×3 perspective transform (homography) by the + * adjugate/determinant closed form. The input must be non-singular. + * + * @param src Source 3×3 transform. + * @param dst Destination for the inverse (3×3). + */ invert_perspective_transform(src: matrix_t, dst: matrix_t): void { const src_d = src.data; const dst_d = dst.data; diff --git a/src/yape/yape.ts b/src/yape/yape.ts index 3713ba8..a71cdcc 100644 --- a/src/yape/yape.ts +++ b/src/yape/yape.ts @@ -1,14 +1,36 @@ import { third_check, is_local_maxima, perform_one_point, lev_table_t } from "./yape_utils"; import { matrix_t } from "../matrix_t/matrix_t"; import { keypoint_t } from "../keypoint_t/keypoint_t"; + +/** + * YAPE ("Yet Another Point Extractor") interest-point detector: scores each + * pixel by comparing it against a precomputed circle of samples at the given + * radius, then keeps directionally consistent local maxima. + * + * Requires {@link init} to be called once with the image dimensions before + * the first {@link detect}. Mirrors `jsfeat.yape` from the original library. + */ export class yape { + /** Per-pyramid-level precomputed circle offsets and score maps. */ private level_tables: lev_table_t[]; + /** Intensity tolerance: samples within ±tau of the center are "similar". */ private tau: number; + constructor() { this.level_tables = []; this.tau = 7; } + /** + * Precomputes the per-level sampling tables (circle offsets and score + * buffers). Must be called before {@link detect}, and again whenever the + * image size changes. + * + * @param width Image width at level 0. + * @param height Image height at level 0. + * @param radius Sampling-circle radius, clamped to [3, 7]. + * @param pyramid_levels Number of levels to prepare. Default 1. + */ init(width: number, height: number, radius: number, pyramid_levels: number = 1): void { radius = Math.min(radius, 7); radius = Math.max(radius, 3); @@ -17,6 +39,17 @@ export class yape { } } + /** + * Detects interest points in a grayscale image: scores every pixel via + * the circle test, then emits points that pass the third-check and + * local-maxima suppression, writing them into the pre-allocated `points` + * array (each entry gets `x`, `y` and `score`). + * + * @param src Source grayscale image (size must match {@link init}). + * @param points Pre-allocated keypoint pool to fill. + * @param border Pixels to skip along each edge. Default 4. + * @returns The number of points written into `points`. + */ detect(src: matrix_t, points: keypoint_t[], border: number = 4): number { const t = this.level_tables[0]; const R = t.radius | 0, diff --git a/src/yape/yape_utils.ts b/src/yape/yape_utils.ts index 9156371..e45636d 100644 --- a/src/yape/yape_utils.ts +++ b/src/yape/yape_utils.ts @@ -1,3 +1,13 @@ +/** + * Precomputes the flat pixel offsets of a Bresenham-style circle of radius + * `R` for an image with row stride `step`, walking the circle once around. + * The first two offsets are duplicated at the end for wrap-around access. + * + * @param step Image row stride (width). + * @param dirs Output offset table (must hold the circle + 2 entries). + * @param R Circle radius in pixels. + * @returns The number of unique circle offsets written. + */ export function precompute_directions(step: number, dirs: Int32Array, R: number): number { let i = 0; let x, y; @@ -41,6 +51,14 @@ export function precompute_directions(step: number, dirs: Int32Array, R: number) return i; } +/** + * Counts how many of the 8 neighbors of a score-map pixel are non-zero — + * YAPE's "third check" requiring a candidate to be supported by at least 3 + * responding neighbors. + * + * @param Sb Score map. @param off Pixel index. @param step Row stride. + * @returns The number of non-zero neighbors (0–8). + */ export function third_check(Sb: Int32Array | number[], off: number, step: number) { let n = 0; if (Sb[off + 1] != 0) n++; @@ -55,6 +73,16 @@ export function third_check(Sb: Int32Array | number[], off: number, step: number return n; } +/** + * Signed local-extremum test over a square neighborhood: for positive `v` + * no neighbor may exceed it; for negative `v` no neighbor may be smaller. + * + * @param p Score map. @param off Pixel index. + * @param v Score value at `off`. + * @param step Row stride of the neighborhood scan. + * @param neighborhood Half-size of the square window. + * @returns `true` when the pixel is a local extremum of its sign. + */ export function is_local_maxima(p: Int32Array, off: number, v: number, step: number, neighborhood: number) { let x, y; @@ -78,6 +106,21 @@ export function is_local_maxima(p: Int32Array, off: number, v: number, step: num return true; } +/** + * Scores one candidate pixel with the YAPE circle test: walks the sampling + * circle as a small state machine tracking runs of brighter/darker/similar + * samples (relative to the `[Im, Ip]` tolerance band) and writes the + * accumulated signed score — or 0 when the pattern disqualifies the pixel — + * into `Scores[x]`. + * + * @param I Image data. @param x Candidate pixel index. + * @param Scores Output score map. + * @param Im Lower tolerance bound (`center - tau`). + * @param Ip Upper tolerance bound (`center + tau`). + * @param dirs Precomputed circle offsets (from {@link precompute_directions}). + * @param opposite Index offset of the diametrically opposite sample. + * @param dirs_nb Number of circle samples. + */ export function perform_one_point( I: { [x: string]: number }, x: number, @@ -627,10 +670,18 @@ export function perform_one_point( Scores[x] = score + dirs_nb * I[x]; } +/** + * Per-pyramid-level lookup table for the YAPE detector: the precomputed + * sampling circle plus a full-frame score buffer for the level's dimensions. + */ export class lev_table_t { + /** Flat pixel offsets of the sampling circle (with wrap-around entries). */ public dirs: Int32Array; + /** Number of unique circle offsets in {@link dirs}. */ public dirs_count: number; + /** Per-pixel signed score map (`w · h`). */ public scores: Int32Array; + /** Circle radius the table was built for. */ public radius: number; constructor(w: number, h: number, r: number) { this.dirs = new Int32Array(1024); diff --git a/src/yape06/yape06.ts b/src/yape06/yape06.ts index b2cd21a..e8afede 100644 --- a/src/yape06/yape06.ts +++ b/src/yape06/yape06.ts @@ -4,12 +4,17 @@ import { keypoint_t } from "../keypoint_t/keypoint_t"; import { compute_laplacian, hessian_min_eigen_value } from "./yape06_utils"; /** - * Real implementation, moved out of the src/jsfeatNext.ts monolith (issue #47). - * This file previously held a type-only stub — the implementation below is the - * inline code from the monolith, verbatim. + * YAPE06 interest-point detector: thresholds a Laplacian response map, then + * rejects edge-like responses via the minimum eigenvalue of the local + * Hessian, followed by 3×3 non-maximum suppression. + * + * Mirrors `jsfeat.yape06` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) */ export class yape06 extends jsfeatNext { + /** Minimum |Laplacian| response for a candidate point. Default 30. */ public laplacian_threshold: number; + /** Minimum Hessian min-eigenvalue (cornerness) for a candidate. Default 25. */ public min_eigen_value_threshold: number; constructor() { @@ -18,6 +23,17 @@ export class yape06 extends jsfeatNext { this.min_eigen_value_threshold = 25; } + /** + * Detects interest points in a grayscale image. Results are written into + * the pre-allocated `points` array (each entry gets `x`, `y`, `score`). + * Tune sensitivity through {@link laplacian_threshold} and + * {@link min_eigen_value_threshold}. + * + * @param src Source grayscale image (`U8C1`). + * @param points Pre-allocated keypoint pool to fill. + * @param border Pixels to skip along each edge. Default 5. + * @returns The number of points written into `points`. + */ detect(src: matrix_t, points: keypoint_t[], border: number): number { if (typeof border === "undefined") { border = 5; diff --git a/src/yape06/yape06_utils.ts b/src/yape06/yape06_utils.ts index d3072f5..855706f 100644 --- a/src/yape06/yape06_utils.ts +++ b/src/yape06/yape06_utils.ts @@ -1,3 +1,16 @@ +/** + * Computes a discrete Laplacian response map over a region of interest: + * `dst[p] = -4·src[p] + src[p±Dxx] + src[p±Dyy]`. Out-of-bounds samples + * write 0. Used by `yape06.detect` as the first interest-point filter. + * + * @param src Source grayscale data. + * @param dst Destination Laplacian map (same layout as `src`). + * @param w Image width (row stride). + * @param Dxx Horizontal sample offset (scaled by the detector radius). + * @param Dyy Vertical sample offset (`radius * w`). + * @param sx Region start x. @param sy Region start y. + * @param ex Region end x (exclusive). @param ey Region end y (exclusive). + */ export function compute_laplacian( src: Int32Array | Float32Array, dst: Int32Array | Float32Array, @@ -25,6 +38,19 @@ export function compute_laplacian( } } +/** + * Estimates the minimum eigenvalue magnitude of the local Hessian at a + * candidate point, from the discrete second derivatives Ixx/Iyy/Ixy. + * `yape06.detect` thresholds this to reject edge-like (non-corner) responses. + * + * @param src Source grayscale data. + * @param off Index of the candidate pixel. + * @param tr Laplacian (trace) response at the pixel. + * @param Dxx Horizontal second-derivative offset. + * @param Dyy Vertical second-derivative offset. + * @param Dxy First diagonal offset. @param Dyx Second diagonal offset. + * @returns The smaller absolute eigenvalue of the local Hessian. + */ export function hessian_min_eigen_value( src: number[], off: number, diff --git a/tsconfig.json b/tsconfig.json index 119ac0b..b6bdea5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "allowSyntheticDefaultImports": true, "esModuleInterop": true, "resolveJsonModule": true, - "removeComments": true, + "removeComments": false, "strict": true, "strictNullChecks": false, "noImplicitAny": true, diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..fd1eb87 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "name": "jsfeatNext", + "entryPoints": ["src"], + "entryPointStrategy": "expand", + "exclude": ["**/node_modules/**"], + "out": "docs/api", + "readme": "README.md", + "includeVersion": true, + "excludePrivate": false, + "excludeInternal": false, + "navigationLinks": { + "GitHub": "https://github.com/webarkit/jsfeatNext" + } +} diff --git a/types/src/cache/cache.d.ts b/types/src/cache/cache.d.ts index 706660a..5848ec6 100644 --- a/types/src/cache/cache.d.ts +++ b/types/src/cache/cache.d.ts @@ -1,15 +1,58 @@ import { default as _pool_node_t } from './../node_utils/_pool_node_t'; +/** + * Public shape of {@link cache}: a recycling pool of scratch buffers used by + * the algorithm modules to avoid per-call allocations in hot loops. + */ export interface ICache { + /** Pre-allocates the pool with `capacity` nodes of `data_size` bytes each. */ allocate: (capacity: any, data_size: number) => void; + /** Borrows a node with at least `size_in_bytes` of storage from the pool. */ get_buffer: (size_in_bytes: number) => _pool_node_t; + /** Returns a previously borrowed node to the pool. */ put_buffer: (node: any) => void; } +/** + * A linked-list pool of reusable scratch buffers ({@link _pool_node_t}). + * Algorithms borrow a buffer with {@link get_buffer}, use its typed-array + * views (`u8`/`i32`/`f32`/`f64`), and must hand it back with + * {@link put_buffer} when done. + * + * Mirrors `jsfeat.cache` from the original library, with one difference: + * the original keeps a single global pool, while jsfeatNext currently + * allocates one pool per module instance (see the base-class constructor + * in `src/core/core.ts`). + */ export declare class cache implements ICache { + /** First free node in the pool (borrow end of the list). */ private _pool_head; + /** Last node in the pool (return end of the list). */ private _pool_tail; + /** Number of nodes currently available in the pool. */ private _pool_size; constructor(); + /** + * Fills the pool with `capacity` nodes, each backed by `data_size` bytes. + * Must be called before the first {@link get_buffer}. + * + * @param capacity Number of pool nodes to create. + * @param data_size Initial byte size of each node's buffer. + */ allocate(capacity: any, data_size: number): void; + /** + * Borrows the next free node from the pool, growing its buffer when it is + * smaller than `size_in_bytes`. The pool assumes enough free nodes are + * available (no underflow check — callers must balance every `get` with a + * {@link put_buffer}). + * + * @param size_in_bytes Minimum byte size the caller needs. + * @returns A pool node whose typed-array views are at least the requested size. + */ get_buffer(size_in_bytes: number): _pool_node_t; + /** + * Returns a borrowed node to the tail of the pool, making it available to + * subsequent {@link get_buffer} calls. + * + * @param node The node previously obtained from {@link get_buffer}. + */ put_buffer(node: any): void; } diff --git a/types/src/constants/constants.d.ts b/types/src/constants/constants.d.ts index 856c938..73b9156 100644 --- a/types/src/constants/constants.d.ts +++ b/types/src/constants/constants.d.ts @@ -1,27 +1,65 @@ +/** + * Library-wide constants, mirroring the constants of the original jsfeat. + * + * **Type signatures.** A matrix/image type is a bitwise OR of a data-type + * flag (`U8_t`, `S32_t`, `F32_t`, `S64_t`, `F64_t` — stored in the high byte) + * and a channel-count flag (`C1_t`…`C4_t` — stored in the low byte), e.g. + * `U8_t | C1_t` for an 8-bit grayscale image. The pre-combined popular + * formats (`U8C1_t`, `F32C1_t`, …) are provided for convenience. + * + * All of these are also re-exposed as static members of the `jsfeatNext` + * base class (see `src/core/core.ts`), which is how consumers usually + * access them (`jsfeatNext.U8_t`, `jsfeatNext.COLOR_RGBA2GRAY`, …). + */ export declare const JSFEAT_CONSTANTS: { + /** Smallest float32 difference considered significant (single-precision machine epsilon). */ EPSILON: number; + /** Smallest positive float used to guard divisions against zero. */ FLT_MIN: number; + /** Data type: unsigned 8-bit integer (`Uint8Array` backed). */ U8_t: number; + /** Data type: signed 32-bit integer (`Int32Array` backed). */ S32_t: number; + /** Data type: 32-bit float (`Float32Array` backed). */ F32_t: number; + /** Data type: signed 64-bit integer (reserved; no typed-array view). */ S64_t: number; + /** Data type: 64-bit float (`Float64Array` backed). */ F64_t: number; + /** Channel count: 1 channel (grayscale / scalar). */ C1_t: number; + /** Channel count: 2 interleaved channels (e.g. gx/gy derivative pairs). */ C2_t: number; + /** Channel count: 3 interleaved channels (e.g. RGB). */ C3_t: number; + /** Channel count: 4 interleaved channels (e.g. RGBA). */ C4_t: number; + /** `imgproc.grayscale` code: source is RGBA (the browser canvas default). */ COLOR_RGBA2GRAY: number; + /** `imgproc.grayscale` code: source is RGB (3 bytes per pixel). */ COLOR_RGB2GRAY: number; + /** `imgproc.grayscale` code: source is BGRA. */ COLOR_BGRA2GRAY: number; + /** `imgproc.grayscale` code: source is BGR (3 bytes per pixel). */ COLOR_BGR2GRAY: number; + /** `imgproc.box_blur_gray` option: keep raw window sums instead of averaging. */ BOX_BLUR_NOSCALE: number; + /** `linalg.svd_decompose` option: return U transposed. */ SVD_U_T: number; + /** `linalg.svd_decompose` option: return V transposed. */ SVD_V_T: number; + /** 8-bit unsigned, 1 channel (`U8_t | C1_t`) — grayscale images. */ U8C1_t: number; + /** 8-bit unsigned, 3 channels (`U8_t | C3_t`) — RGB images. */ U8C3_t: number; + /** 8-bit unsigned, 4 channels (`U8_t | C4_t`) — RGBA images. */ U8C4_t: number; + /** 32-bit float, 1 channel (`F32_t | C1_t`) — float matrices. */ F32C1_t: number; + /** 32-bit float, 2 channels (`F32_t | C2_t`) — float vector fields. */ F32C2_t: number; + /** 32-bit signed int, 1 channel (`S32_t | C1_t`). */ S32C1_t: number; + /** 32-bit signed int, 2 channels (`S32_t | C2_t`) — integer gx/gy maps. */ S32C2_t: number; }; diff --git a/types/src/core/core.d.ts b/types/src/core/core.d.ts index 690d1d6..0908ae6 100644 --- a/types/src/core/core.d.ts +++ b/types/src/core/core.d.ts @@ -15,8 +15,23 @@ import { motion_estimator } from '../motion_estimator/motion_estimator'; import { optical_flow_lk } from '../optical_flow_lk/optical_flow_lk'; import { orb } from '../orb/orb'; import { affine2d, homography2d } from '../motion_model/motion_model'; +/** + * Base class of the library: holds the shared constants, the per-instance + * cache/data-type helpers, and the static slots the algorithm modules are + * attached to (in src/jsfeatNext.ts, the aggregator). + * + * Extracted from the src/jsfeatNext.ts monolith (issue #47) so that module + * files can `extend` it without creating a circular import with the + * aggregator. + */ export default class jsfeatNext { + /** Decoder for packed matrix type signatures. */ private dt; + /** + * Per-instance scratch-buffer pool (30 buffers of 2560 bytes, growable). + * NOTE: original jsfeat shares ONE global cache; jsfeatNext currently + * allocates a pool per module instance (see the parity audit, Axis 2). + */ protected cache: cache; static cache: typeof cache; static fast_corners: typeof fast_corners; @@ -37,6 +52,7 @@ export default class jsfeatNext { static optical_flow_lk: typeof optical_flow_lk; static orb: typeof orb; constructor(); + /** Library version, read from package.json at build time. */ static VERSION: string; static EPSILON: number; static FLT_MIN: number; @@ -63,7 +79,19 @@ export default class jsfeatNext { static F32C2_t: number; static S32C1_t: number; static S32C2_t: number; + /** + * @param type Packed type signature (e.g. `U8_t | C1_t`). + * @returns The data-type component alone (e.g. `U8_t`). + */ get_data_type(type: number): number; + /** + * @param type Packed type signature. + * @returns The channel count (1–4). + */ get_channel(type: number): number; + /** + * @param type Packed type signature. + * @returns Bytes per element of the signature's data type (1, 4 or 8). + */ get_data_type_size(type: number): number; } diff --git a/types/src/data_type/data_type.d.ts b/types/src/data_type/data_type.d.ts index 5bd029d..5e09808 100644 --- a/types/src/data_type/data_type.d.ts +++ b/types/src/data_type/data_type.d.ts @@ -1,12 +1,41 @@ +/** + * Helper for decoding the packed matrix type signature + * (see `JSFEAT_CONSTANTS`): data-type flags live in the high byte, + * the channel count in the low byte. + */ export interface IData_Type { + /** Extracts the data-type component (`U8_t`, `S32_t`, …) from a packed signature. */ _get_data_type: (type: number) => number; + /** Extracts the channel count (1–4) from a packed signature. */ _get_channel: (type: number) => number; + /** Returns the byte size of one element of the given data type. */ _get_data_type_size: (type: number) => number; } +/** + * Decodes packed type signatures such as `U8_t | C1_t` into their data-type, + * channel-count and per-element byte-size components. Used internally by + * `matrix_t` when allocating storage. + */ export declare class data_type implements IData_Type { + /** + * Byte size per element, indexed by `(data_type_flag >> 8)`: + * U8 → 1, S32/F32 → 4, S64/F64 → 8; unused slots are -1. + */ private readonly _data_type_size; constructor(); + /** + * @param type Packed type signature (e.g. `U8_t | C1_t`). + * @returns The data-type flag alone (high byte), e.g. `U8_t`. + */ _get_data_type(type: number): number; + /** + * @param type Packed type signature. + * @returns The channel count alone (low byte), 1–4. + */ _get_channel(type: number): number; + /** + * @param type Packed type signature. + * @returns Bytes per element for the signature's data type (1, 4 or 8). + */ _get_data_type_size(type: number): number; } diff --git a/types/src/fast_corners/fast_corners.d.ts b/types/src/fast_corners/fast_corners.d.ts index bde1811..d2fca39 100644 --- a/types/src/fast_corners/fast_corners.d.ts +++ b/types/src/fast_corners/fast_corners.d.ts @@ -1,14 +1,45 @@ import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; import { point_t } from '../point_t/point_t'; +/** + * FAST-16 corner detector (Features from Accelerated Segment Test): a pixel + * is a corner when ≥9 contiguous pixels on the 16-pixel Bresenham circle + * around it are all brighter or all darker than the center by the threshold. + * Detection is followed by score-based non-maximum suppression. + * + * Mirrors `jsfeat.fast_corners` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class fast_corners extends jsfeatNext { + /** The 16 (x, y) circle offsets, interleaved, for radius 3. */ private offsets16; + /** Current detection threshold (0–255); set via {@link set_threshold}. */ _threshold: number; + /** 512-entry lookup: intensity difference (+255) → darker(1)/brighter(2)/similar(0). */ threshold_tab: Uint8Array; + /** Circle offsets converted to flat pixel offsets for the current row stride. */ pixel_off: Int32Array; + /** Scratch array used by the corner-score function. */ score_diff: Int32Array; constructor(); + /** + * Sets the detection threshold and rebuilds the classification lookup + * table. Must be called at least once before {@link detect}. + * + * @param threshold Minimum center-vs-circle intensity difference, clamped to [0, 255]. + * @returns The clamped threshold actually stored. + */ set_threshold(threshold: number): number; + /** + * Detects FAST corners in a grayscale image, applying 3×3 non-maximum + * suppression on the corner scores. Results are written into the + * pre-allocated `corners` array (each entry gets `x`, `y`, `score`). + * + * @param src Source grayscale image (`U8C1`). + * @param corners Pre-allocated point pool to fill. + * @param border Pixels to skip along each edge (min 3). Default 3. + * @returns The number of corners written into `corners`. + */ detect(src: matrix_t, corners: point_t[], border: number): number; private _cmp_offsets; } diff --git a/types/src/fast_corners/fast_private.d.ts b/types/src/fast_corners/fast_private.d.ts index ae6869c..5c8fbbb 100644 --- a/types/src/fast_corners/fast_private.d.ts +++ b/types/src/fast_corners/fast_private.d.ts @@ -1 +1,17 @@ +/** + * Computes the FAST-16 corner score for a candidate pixel: the largest + * threshold for which the pixel would still be detected as a corner + * (used for non-maximum suppression in `fast_corners.detect`). + * + * The score is derived from the min/max intensity differences over every + * contiguous 9-pixel arc of the 16-pixel Bresenham circle around the + * candidate. + * + * @param src Grayscale image data. + * @param off Index of the candidate pixel in `src`. + * @param pixel Precomputed offsets of the 25 circle samples (16 + 9 wrap-around). + * @param d Scratch array receiving the 25 intensity differences. + * @param threshold Detector threshold; acts as the score lower bound. + * @returns The corner score (always ≥ `threshold` for detected corners). + */ export declare function _cmp_score_16(src: Uint8Array, off: number, pixel: Uint8Array | Int32Array, d: Uint8Array | Int32Array, threshold: number): number; diff --git a/types/src/homography2d/homography2d.d.ts b/types/src/homography2d/homography2d.d.ts index 184a0a1..dce4868 100644 --- a/types/src/homography2d/homography2d.d.ts +++ b/types/src/homography2d/homography2d.d.ts @@ -1,7 +1,27 @@ import { matrix_t } from '../matrix_t/matrix_t'; import { point_t } from '../point_t/point_t'; +/** + * Contract every motion-model kernel must fulfil to be usable with + * `motion_estimator.ransac` / `lmeds`. Implemented by both `homography2d` + * and `affine2d` (see `src/motion_model/motion_model.ts`). + */ export interface IHomography2d { + /** + * Estimates a model from `count` point correspondences and writes it + * into `model` (a 3×3 matrix). + * + * @returns The number of models produced (0 on degenerate input). + */ run(from: point_t[], to: point_t[], model: matrix_t, count: number): number; + /** + * Computes the per-correspondence squared reprojection error of `model` + * into the `err` array. + */ error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void; + /** + * Validates a minimal sample before model estimation (e.g. rejects + * degenerate point configurations). Returning `false` makes the + * estimator draw a new sample. + */ check_subset(from: point_t[], to: point_t[], count: number): boolean; } diff --git a/types/src/imgproc/convol.d.ts b/types/src/imgproc/convol.d.ts index db3b431..3eefa1d 100644 --- a/types/src/imgproc/convol.d.ts +++ b/types/src/imgproc/convol.d.ts @@ -1,2 +1,31 @@ +/** + * Separable 2D convolution for `U8` images with an integer kernel — the + * fixed-point fast path of `imgproc.gaussian_blur`. Runs a horizontal then a + * vertical 1D pass with edge replication, right-shifting by 8 to undo the + * kernel's 8-bit scaling and clamping results to 255. + * + * @param buf Scratch row/column buffer (from the cache pool). + * @param src_d Source image data. + * @param dst_d Destination image data (also used as the intermediate). + * @param w Image width. + * @param h Image height. + * @param filter 1D kernel, integer-scaled to sum to 256. + * @param kernel_size Number of kernel taps. + * @param half_kernel `kernel_size >> 1` (border padding size). + */ export declare function _convol_u8(buf: Int32Array | Float32Array, src_d: number[], dst_d: number[], w: number, h: number, filter: Int32Array | Float32Array, kernel_size: number, half_kernel: number): void; +/** + * Separable 2D convolution in floating point — the general path of + * `imgproc.gaussian_blur` for `S32`/`F32` data. Same two-pass structure as + * {@link _convol_u8} but without fixed-point scaling or clamping. + * + * @param buf Scratch row/column buffer (from the cache pool). + * @param src_d Source data. + * @param dst_d Destination data (also used as the intermediate). + * @param w Width. + * @param h Height. + * @param filter 1D kernel weights (normalized to sum to 1). + * @param kernel_size Number of kernel taps. + * @param half_kernel `kernel_size >> 1` (border padding size). + */ export declare function _convol(buf: Int32Array | Float32Array, src_d: number[], dst_d: number[], w: number, h: number, filter: Int32Array | Float32Array, kernel_size: number, half_kernel: number): void; diff --git a/types/src/imgproc/imgproc.d.ts b/types/src/imgproc/imgproc.d.ts index d43f403..b077ea1 100644 --- a/types/src/imgproc/imgproc.d.ts +++ b/types/src/imgproc/imgproc.d.ts @@ -1,20 +1,166 @@ import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; +/** + * Image-processing operations: color conversion, resampling, blurs, image + * derivatives, integral images, histogram equalization, Canny edges, Hough + * lines and geometric warps. All methods operate on {@link matrix_t} images + * and follow the original `jsfeat.imgproc` semantics. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class imgproc extends jsfeatNext { constructor(); + /** + * Converts an interleaved color buffer to a grayscale image using the + * integer-scaled BT.601 luma weights (`0.299 R + 0.587 G + 0.114 B` in + * 14-bit fixed point). + * + * @param src Source pixel buffer (e.g. canvas `ImageData.data`). + * @param w Source width. @param h Source height. + * @param dst Destination grayscale matrix (resized to `w`×`h`, 1 channel). + * @param code Channel layout of `src`: one of the `COLOR_*2GRAY` + * constants. Defaults to `COLOR_RGBA2GRAY`. + */ grayscale(src: Uint8Array | Uint8ClampedArray, w: number, h: number, dst: matrix_t, code?: number): void; + /** + * Downsamples `src` to `nw`×`nh` by area averaging (derived from the CCV + * library). Chooses the fixed-point `U8` fast path when both matrices are + * `U8` and the area ratio is below 256, the float path otherwise. + * No-op unless both target dimensions are strictly smaller. + * + * @param src Source image. @param dst Destination (resized to `nw`×`nh`). + * @param nw Target width. @param nh Target height. + */ resample(src: matrix_t, dst: matrix_t, nw: number, nh: number): void; + /** + * Box blur of a grayscale image via a sliding-window running sum + * (two transposing passes, O(1) per pixel regardless of radius). + * + * @param src Source grayscale image. + * @param dst Destination (resized to match `src`). Use an `S32` + * destination with `BOX_BLUR_NOSCALE` to avoid overflow. + * @param radius Blur radius; the window is `(2·radius + 1)²`. + * @param options `BOX_BLUR_NOSCALE` keeps raw window sums instead of + * dividing by the window area. Defaults to 0 (scaled). + */ box_blur_gray(src: matrix_t, dst: matrix_t, radius: number, options: number): void; + /** + * Gaussian blur via separable convolution. Kernel weights come from + * `math.get_gaussian_kernel`; `U8` images use the integer fast path. + * + * @param src Source image. + * @param dst Destination (resized to match `src`). + * @param kernel_size Number of taps; 0 derives it from `sigma`. + * @param sigma Gaussian σ; 0 derives it from `kernel_size`. + */ gaussian_blur(src: matrix_t, dst: matrix_t, kernel_size: number, sigma: number): void; + /** + * Standard Hough transform for line detection on a binary edge image + * (e.g. `canny` output). Returns lines sorted by accumulator strength. + * + * NOTE (parity): the original jsfeat version of this function is broken + * (it references undeclared `min_theta`/`max_theta` and throws in strict + * mode); jsfeatNext fixes it. + * + * @param img Binary edge image (non-zero pixels vote). + * @param rho_res Distance resolution of the accumulator, in pixels. + * @param theta_res Angle resolution, in radians. + * @param threshold Minimum accumulator votes for a line. + * @returns Array of `[rho, theta]` pairs describing detected lines. + */ hough_transform(img: matrix_t, rho_res: number, theta_res: number, threshold: number): number[]; + /** + * Halves an image with 2×2 box filtering (each output pixel is the + * rounded mean of the corresponding 2×2 source block). The optional + * source offset exists for the (not yet ported) BBF detector. + * + * @param src Source image. + * @param dst Destination (resized to `w>>1`×`h>>1`). + * @param sx Source x offset. Default 0. @param sy Source y offset. Default 0. + */ pyrdown(src: matrix_t, dst: matrix_t, sx?: number, sy?: number): void; + /** + * Computes first-order image derivatives with the 3×3 Scharr operator + * (weights 3/10/3 — more rotationally accurate than Sobel). Output is a + * 2-channel map with interleaved `[gx, gy]` per pixel; borders are + * handled by reflection. + * + * @param src Source grayscale image. + * @param dst Destination derivative map (resized to `src` size, 2 channels). + */ scharr_derivatives(src: matrix_t, dst: matrix_t): void; + /** + * Computes first-order image derivatives with the 3×3 Sobel operator + * (`[1 2 1] ⊗ [-1 0 1]`). Output is a 2-channel map with interleaved + * `[gx, gy]` per pixel; borders are handled by reflection. + * + * @param src Source grayscale image. + * @param dst Destination derivative map (resized to `src` size, 2 channels). + */ sobel_derivatives(src: matrix_t, dst: matrix_t): void; + /** + * Computes integral images ("summed-area tables") over `src` — any + * combination of: plain sum, squared sum, and 45°-tilted sum (as used by + * Haar-cascade detectors). Pass a falsy value to skip an output. + * + * Each destination must be sized `(src.cols + 1) × (src.rows + 1)`; + * the first row/column are zero. + * + * @param src Source grayscale image. + * @param dst_sum Output for pixel sums, or falsy to skip. + * @param dst_sqsum Output for squared-pixel sums, or falsy to skip. + * @param dst_tilted Output for 45°-tilted sums, or falsy to skip. + */ compute_integral_image(src: matrix_t, dst_sum: number[], dst_sqsum: number[], dst_tilted: any[]): void; + /** + * Histogram equalization of a grayscale image: remaps intensities + * through the normalized cumulative histogram to maximize contrast. + * + * @param src Source grayscale image (`U8`). + * @param dst Destination (resized to match `src`). + */ equalize_histogram(src: matrix_t, dst: matrix_t): void; + /** + * Canny edge detector: Sobel gradients → L1-magnitude non-maximum + * suppression → double-threshold hysteresis tracking. Edge pixels are + * 255, everything else 0. Blur the input first for stable results. + * + * @param src Source grayscale image. + * @param dst Destination edge map (resized to match `src`). + * @param low_thresh Lower hysteresis threshold (gradient magnitude). + * @param high_thresh Upper hysteresis threshold (strong-edge seed). + */ canny(src: matrix_t, dst: matrix_t, low_thresh: number, high_thresh: number): void; + /** + * Warps an image through a 3×3 perspective transform with bilinear + * sampling. For every destination pixel the INVERSE mapping is applied, + * so `transform` must map destination → source coordinates (invert a + * forward homography with `transform.invert_perspective_transform` first). + * + * @param src Source grayscale image. + * @param dst Destination image (same size as `src`). + * @param transform 3×3 dst→src homography. + * @param fill_value Intensity for samples falling outside `src`. Default 0. + */ warp_perspective(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void; + /** + * Warps an image through an affine transform with bilinear sampling. + * Only the first 6 coefficients of `transform` are used, mapping + * destination → source coordinates (inverse warping). + * + * @param src Source grayscale image. + * @param dst Destination image (same size as `src`). + * @param transform 2×3 (or 3×3, first 6 entries) dst→src affine transform. + * @param fill_value Intensity for samples falling outside `src`. Default 0. + */ warp_affine(src: matrix_t, dst: matrix_t, transform: matrix_t, fill_value: number): void; + /** + * Basic RGB skin-color filter (rule-based, from + * http://popscan.blogspot.fr/2012/08/skin-detection-in-digital-images.html): + * writes 255 for skin-classified pixels and 0 otherwise. + * + * @param src RGBA image-like object (`width`, `height`, `data`). + * @param dst Output array of per-pixel 0/255 values (length `w·h`). + */ skindetector(src: { width: number; height: number; diff --git a/types/src/imgproc/resample.d.ts b/types/src/imgproc/resample.d.ts index 14cd237..5fd87c8 100644 --- a/types/src/imgproc/resample.d.ts +++ b/types/src/imgproc/resample.d.ts @@ -1,4 +1,25 @@ import { matrix_t } from '../matrix_t/matrix_t'; import { cache } from '../cache/cache'; +/** + * Area-average downsampling for `U8` images — the fixed-point fast path of + * `imgproc.resample`, using 8.8 fixed-point weights to avoid float math. + * Only valid when the area ratio `(w*h)/(nw*nh)` is below 256. + * + * @param src Source image (`U8`, 1–4 channels). + * @param dst Destination image, already sized to `nw`×`nh`. + * @param cache Buffer pool used for the row accumulators and offset table. + * @param nw Target width (must be < source width). + * @param nh Target height (must be < source height). + */ export declare function _resample_u8(src: matrix_t, dst: matrix_t, cache: cache, nw: number, nh: number): void; +/** + * Area-average downsampling in floating point — the general path of + * `imgproc.resample`, used for non-`U8` data or large scale factors. + * + * @param src Source image/matrix (1–4 channels). + * @param dst Destination, already sized to `nw`×`nh`. + * @param cache Buffer pool used for the row accumulators and offset table. + * @param nw Target width (must be < source width). + * @param nh Target height (must be < source height). + */ export declare function _resample(src: matrix_t, dst: matrix_t, cache: cache, nw: number, nh: number): void; diff --git a/types/src/index.d.ts b/types/src/index.d.ts index d0af402..8108520 100644 --- a/types/src/index.d.ts +++ b/types/src/index.d.ts @@ -1,4 +1,18 @@ import { default as jsfeatNext } from './jsfeatNext'; +/** + * Package entry point. The default export wraps the {@link jsfeatNext} class + * in an object, which is why consumers of the UMD bundle (global + * `jsfeatNext`) and of the npm package access the library as + * `jsfeatNext.jsfeatNext` — a known quirk scheduled to be addressed in the + * API-parity work (issue #41). + * + * @example + * ```ts + * import pkg from "@webarkit/jsfeat-next"; + * const jsfeat = pkg.jsfeatNext; + * const ip = new jsfeat.imgproc(); + * ``` + */ declare const _default: { jsfeatNext: typeof jsfeatNext; }; diff --git a/types/src/keypoint_t/keypoint_t.d.ts b/types/src/keypoint_t/keypoint_t.d.ts index bd0af48..3e72313 100644 --- a/types/src/keypoint_t/keypoint_t.d.ts +++ b/types/src/keypoint_t/keypoint_t.d.ts @@ -1,8 +1,34 @@ +/** + * A 2D feature keypoint with position, detector response, pyramid level and + * orientation. Used by the detectors (`fast_corners`, `yape`, `yape06`) as + * output slots and by `orb.describe` as descriptor anchors. + * + * Mirrors `jsfeat.keypoint_t` from the original library. + * + * @example + * ```ts + * // pre-allocate a corner pool for a detector + * const corners = []; + * for (let i = 0; i < 500; i++) corners.push(new keypoint_t(0, 0, 0, 0, -1)); + * ``` + */ export declare class keypoint_t { + /** X (column) coordinate in pixels. */ x: number; + /** Y (row) coordinate in pixels. */ y: number; + /** Detector response / corner strength (higher = stronger). */ score: number; + /** Pyramid level the keypoint was detected on. */ level: number; + /** Orientation in radians; -1 when not yet computed. */ angle: number; + /** + * @param x X (column) coordinate. Default 0. + * @param y Y (row) coordinate. Default 0. + * @param score Detector response. Default 0. + * @param level Pyramid level. Default 0. + * @param angle Orientation in radians. Default -1 (unset). + */ constructor(x?: number, y?: number, score?: number, level?: number, angle?: number); } diff --git a/types/src/linalg/linalg.d.ts b/types/src/linalg/linalg.d.ts index d18f0c8..634107c 100644 --- a/types/src/linalg/linalg.d.ts +++ b/types/src/linalg/linalg.d.ts @@ -1,15 +1,106 @@ import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; import { default as matmath } from '../matmath/matmath'; +/** + * Dense linear-algebra solvers built on Jacobi rotations: LU and Cholesky + * linear-system solvers, singular value decomposition (and SVD-based solve / + * pseudo-inverse) and symmetric eigen-decomposition. Mirrors `jsfeat.linalg` + * from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class linalg extends jsfeatNext { + /** Matrix-arithmetic helper used by the SVD-based routines. */ matmath: matmath; constructor(); + /** + * Cyclic Jacobi eigen-decomposition of a symmetric `n`×`n` matrix + * (internal kernel of {@link eigenVV}). On return `W` holds the + * eigenvalues in descending order and `V` (when given) the corresponding + * eigenvectors as rows. `A` is destroyed in the process. + * + * @param A Symmetric input matrix data (mutated). + * @param astep Row stride of `A`. + * @param W Output eigenvalues (length `n`). + * @param V Output eigenvector rows, or null to skip. + * @param vstep Row stride of `V`. + * @param n Matrix dimension. + */ JacobiImpl(A: Int32Array | Float32Array | Float64Array, astep: number, W: Int32Array | Float32Array | Float64Array, V: Int32Array | Float32Array | Float64Array, vstep: number, n: number): void; + /** + * One-sided Jacobi SVD (internal kernel of {@link svd_decompose} and + * friends). Operates on `At` (the input stored transposed) in place, + * accumulating right singular vectors into `Vt` when given; singular + * values come out in `W` in descending order, with sign correction + * applied to keep them non-negative. + * + * @param At Input matrix data, transposed (mutated into U·diag(W)). + * @param astep Row stride of `At`. + * @param W Output singular values. + * @param Vt Output right singular vectors (transposed), or null. + * @param vstep Row stride of `Vt`. + * @param m Rows of the original matrix. @param n Columns. + * @param n1 Number of U columns to normalize (m, or 0 to skip U). + */ JacobiSVDImpl(At: Int32Array | Float32Array | Float64Array, astep: number, _W: Int32Array | Float32Array | Float64Array, Vt: Int32Array | Float32Array | Float64Array, vstep: number, m: number, n: number, n1: number): void; + /** + * Solves the square linear system `A·x = B` in place by Gaussian + * elimination with partial pivoting. `A` is destroyed and `B` is + * overwritten with the solution `x`. + * + * @param A Square coefficient matrix (mutated). + * @param B Right-hand side (n×1); receives the solution. + * @returns 1 on success, 0 when `A` is singular. + */ lu_solve(A: matrix_t, B: matrix_t): number; + /** + * Solves `A·x = B` in place for a symmetric positive-definite `A` via + * Cholesky-style LDL decomposition (no pivoting — faster than + * {@link lu_solve} but requires SPD input). `A` is destroyed and `B` is + * overwritten with the solution. + * + * @param A SPD coefficient matrix (mutated). + * @param B Right-hand side (n×1); receives the solution. + * @returns 1 (the decomposition does not detect failure). + */ cholesky_solve(A: matrix_t, B: matrix_t): number; + /** + * Singular value decomposition `A = U · diag(W) · Vᵀ` via one-sided + * Jacobi rotations. Singular values arrive in descending order. + * + * @param A Input m×n matrix (not modified). + * @param W Output singular values (min(m,n)×1). + * @param U Output left singular vectors (m×m), or null to skip. + * @param V Output right singular vectors (n×n), or null to skip. + * @param options Bitmask of `SVD_U_T` / `SVD_V_T` to receive U and/or V + * already transposed (avoids an extra transpose). + */ svd_decompose(A: any, W: matrix_t, U: matrix_t, V: matrix_t, options: number): void; + /** + * Solves `A·x = B` in the least-squares sense through the SVD + * pseudo-inverse: `x = V · diag(1/w) · Uᵀ · B`, with tiny singular + * values zeroed for stability. Works for rectangular / rank-deficient A. + * + * @param A Input m×n matrix (not modified). + * @param X Output solution vector (n×1). + * @param B Right-hand side (m×1). + */ svd_solve(A: matrix_t, X: matrix_t, B: matrix_t): void; + /** + * Moore–Penrose pseudo-inverse via SVD: `Ai = V · diag(1/w) · Uᵀ`, + * with tiny singular values zeroed. Valid for any matrix shape/rank. + * + * @param Ai Output pseudo-inverse (n×m). + * @param A Input m×n matrix (not modified). + */ svd_invert(Ai: matrix_t, A: matrix_t): void; + /** + * Eigen-decomposition of a symmetric matrix by cyclic Jacobi rotations. + * Eigenvalues come out in descending order; eigenvectors are the rows of + * `vects`. Used by `homography2d.run` to solve the DLT system. + * + * @param A Symmetric input matrix (not modified; copied internally). + * @param vects Output eigenvector rows (n×n), or null to skip. + * @param vals Output eigenvalues (n×1), optional. + */ eigenVV(A: matrix_t, vects: matrix_t, vals?: matrix_t): void; } diff --git a/types/src/linalg/linalg_base.d.ts b/types/src/linalg/linalg_base.d.ts index 9813ec4..43257bd 100644 --- a/types/src/linalg/linalg_base.d.ts +++ b/types/src/linalg/linalg_base.d.ts @@ -1,2 +1,19 @@ +/** + * Swaps two elements of a typed array in place. + * + * @param A The array to mutate. + * @param i0 Index of the first element. + * @param i1 Index of the second element. + * @param t Scratch variable (its incoming value is ignored). + */ export declare function swap(A: Int32Array | Float32Array | Float64Array, i0: number, i1: number, t: number): void; +/** + * Numerically stable `sqrt(a² + b²)` (Euclidean hypotenuse) that avoids + * overflow/underflow by factoring out the larger magnitude — the classic + * BLAS-style formulation used inside the Jacobi SVD/eigen routines. + * + * @param a First component. + * @param b Second component. + * @returns `sqrt(a² + b²)` computed without squaring the raw inputs. + */ export declare function hypot(a: number, b: number): number; diff --git a/types/src/math/math.d.ts b/types/src/math/math.d.ts index 070bf0d..6dbd2d6 100644 --- a/types/src/math/math.d.ts +++ b/types/src/math/math.d.ts @@ -1,10 +1,62 @@ import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; +/** + * General math utilities: Gaussian-kernel generation, an in-place quicksort + * and a selection-based median. Mirrors `jsfeat.math` from the original + * library. (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class math extends jsfeatNext { + /** Iterative-quicksort bounds stack (48 nesting levels × lo/hi pairs). */ private qsort_stack; constructor(); + /** + * Fills `kernel` with a normalized 1D Gaussian. For small odd sizes (≤7) + * with `sigma <= 0` the classic fixed binomial kernels are used; + * otherwise the kernel is sampled from `exp(-x²/2σ²)` with the OpenCV + * default `σ = 0.3·((size-1)/2 - 1) + 0.8` when `sigma <= 0`. + * + * @param size Number of taps (kernel length). + * @param sigma Gaussian standard deviation; `<= 0` selects the default. + * @param kernel Output array of `size` weights. + * @param data_type `U8_t` scales weights to integers summing to 256; + * any other type yields floats summing to 1. + */ get_gaussian_kernel(size: number, sigma: number, kernel: Float32Array | Int32Array, data_type: number): void; + /** + * Computes the 3×3 perspective transform mapping four source points onto + * four destination points and writes it into `model`. + * + * @deprecated Use `transform.perspective_4point_transform()` instead — + * this copy exists only for parity with the distributed jsfeat bundle + * (where it lives under `jsfeat.math`) and logs a deprecation warning. + * + * @param model 3×3 destination matrix. + * @param src_x0…dst_y3 The four `(src, dst)` point pairs, interleaved as + * `src_x0, src_y0, dst_x0, dst_y0, …` for points 0–3. + */ perspective_4point_transform(model: matrix_t, src_x0: number, src_y0: number, dst_x0: number, dst_y0: number, src_x1: number, src_y1: number, dst_x1: number, dst_y1: number, src_x2: number, src_y2: number, dst_x2: number, dst_y2: number, src_x3: number, src_y3: number, dst_x3: number, dst_y3: number): void; + /** + * In-place iterative quicksort of `array[low..high]` (inclusive bounds) + * using median-of-three pivoting and an insertion sort for tiny spans. + * + * @param array Values to sort (mutated in place). + * @param low First index of the range. + * @param high Last index of the range (inclusive). + * @param cmp "Less than" comparator: truthy when `a < b`. + */ qsort(array: number[], low: number, high: number, cmp: (a: number, b: number) => number): void; + /** + * Selects the median of `array[low..high]` by Hoare's selection + * (quickselect). The array is PARTIALLY REORDERED in the process — pass a + * copy if the original order matters. + * + * NB: `motion_estimator.lmeds` calls this with a `Float32Array` cache + * buffer, so the signature accepts typed arrays as well as `number[]`. + * + * @param array Values to select from (mutated). + * @param low First index of the range. + * @param high Last index of the range (inclusive). + * @returns The median value of the range. + */ median(array: number[] | Int32Array | Float32Array, low: number, high: number): number; } diff --git a/types/src/matmath/matmath.d.ts b/types/src/matmath/matmath.d.ts index 9d32d13..6076ffc 100644 --- a/types/src/matmath/matmath.d.ts +++ b/types/src/matmath/matmath.d.ts @@ -1,16 +1,95 @@ import { matrix_t } from '../matrix_t/matrix_t'; +/** + * General matrix arithmetic on {@link matrix_t} operands: transpose, + * several multiplication variants optimized for common shapes, and small + * fixed-size 3×3 helpers used by the geometric-transform code. + * + * Mirrors `jsfeat.matmath` from the original library. + */ export default class matmath { constructor(); + /** + * Fills `M` with `value` on the main diagonal and zeros elsewhere. + * + * @param M Matrix to overwrite. + * @param value Diagonal value; defaults to 1. + */ identity(M: matrix_t, value: number): void; + /** + * Writes the transpose of `A` into `At` (`At = Aᵀ`). + * + * @param At Destination (`A.cols` × `A.rows`). + * @param A Source matrix. + */ transpose(At: matrix_t, A: matrix_t): void; + /** + * General matrix product `C = A · B`. + * + * @param C Destination (`B.cols` × `A.rows`); must not alias A or B. + * @param A Left operand. @param B Right operand (`B.rows === A.cols`). + */ multiply(C: matrix_t, A: matrix_t, B: matrix_t): void; + /** + * Product with transposed right operand: `C = A · Bᵀ`. + * + * @param C Destination (`B.rows` × `A.rows`). + * @param A Left operand. @param B Right operand (`B.cols === A.cols`). + */ multiply_ABt(C: matrix_t, A: matrix_t, B: matrix_t): void; + /** + * Product with transposed left operand: `C = Aᵀ · B`. + * + * @param C Destination (`B.cols` × `A.cols`). + * @param A Left operand (`A.rows === B.rows`). @param B Right operand. + */ multiply_AtB(C: matrix_t, A: matrix_t, B: matrix_t): void; + /** + * Symmetric self-product `C = A · Aᵀ`, computing only the upper triangle + * and mirroring it. + * + * @param C Destination (`A.rows` × `A.rows`, symmetric). @param A Operand. + */ multiply_AAt(C: matrix_t, A: matrix_t): void; + /** + * Symmetric self-product `C = Aᵀ · A` (the normal-equations matrix), + * computing only the upper triangle and mirroring it. + * + * @param C Destination (`A.cols` × `A.cols`, symmetric). @param A Operand. + */ multiply_AtA(C: matrix_t, A: matrix_t): void; + /** + * Fills a 3×3 matrix with `value` on the diagonal and zeros elsewhere. + * + * @param M 3×3 destination. @param value Diagonal value; defaults to 1. + */ identity_3x3(M: matrix_t, value: number): void; + /** + * Inverts a 3×3 matrix by the adjugate/determinant closed form + * (no pivoting — the input must be non-singular). Safe to call with + * `from === to` (in-place inversion). + * + * @param from 3×3 source matrix. @param to 3×3 destination. + */ invert_3x3(from: matrix_t, to: matrix_t): void; + /** + * Fixed-size 3×3 product `C = A · B`, fully unrolled. All operands are + * read into locals first, so `C` may alias `A` or `B`. + * + * @param C 3×3 destination. @param A Left operand. @param B Right operand. + */ multiply_3x3(C: matrix_t, A: matrix_t, B: matrix_t): void; + /** + * Determinant of a 3×3 matrix by cofactor expansion. + * + * @param M 3×3 matrix. @returns `det(M)`. + */ mat3x3_determinant(M: matrix_t): number; + /** + * Determinant of a 3×3 matrix given as nine scalars (row-major M11…M33). + * Scalar variant of {@link mat3x3_determinant}, used by the RANSAC + * degeneracy checks without building a matrix. + * + * @returns The determinant value. + */ determinant_3x3(M11: number, M12: number, M13: number, M21: number, M22: number, M23: number, M31: number, M32: number, M33: number): number; } diff --git a/types/src/matrix_t/matrix_t.d.ts b/types/src/matrix_t/matrix_t.d.ts index 831d42c..80411ff 100644 --- a/types/src/matrix_t/matrix_t.d.ts +++ b/types/src/matrix_t/matrix_t.d.ts @@ -1,25 +1,94 @@ import { data_t } from '../node_utils/data_t'; +/** + * Public shape of {@link matrix_t}: a 2D dense matrix (or image) backed by a + * typed array. + */ export interface IMatrix_T { + /** Number of columns (image width). */ cols: number; + /** Number of rows (image height). */ rows: number; + /** Data-type component of the matrix type signature (e.g. `U8_t`, `F32_t`). */ type: number; + /** Number of interleaved channels per element (1–4). */ channel: number; + /** The typed-array view holding the matrix elements, row-major. */ data: any; + /** The underlying {@link data_t} buffer that `data` is a view over. */ buffer: data_t; + /** (Re)allocates the backing buffer from the current cols/rows/channel/type. */ allocate: () => void; + /** Copies this matrix's elements into another matrix of at least equal size. */ copy_to: (other: any) => void; + /** Changes the logical dimensions, reallocating only when the buffer is too small. */ resize: (c: number, r: number, ch: any) => void; } +/** + * The fundamental data container of jsfeatNext: a dense, row-major 2D matrix + * backed by a single typed array. Used for grayscale images, multi-channel + * derivative maps, transformation matrices and linear-algebra operands alike. + * + * The element type and channel count are packed into a single type signature, + * e.g. `jsfeatNext.U8_t | jsfeatNext.C1_t` for an 8-bit single-channel image + * or `jsfeatNext.F32_t | jsfeatNext.C1_t` for a float matrix. + * + * Mirrors `jsfeat.matrix_t` from the original library. + * + * @example + * ```ts + * const img = new matrix_t(640, 480, jsfeatNext.U8_t | jsfeatNext.C1_t); + * img.data[0] = 255; // top-left pixel + * ``` + */ export declare class matrix_t implements IMatrix_T { + /** Data-type helper used to decode the packed type signature. */ private dt; + /** Data-type component of the packed type signature (`U8_t`, `S32_t`, `F32_t`, `F64_t`). */ type: number; + /** Number of interleaved channels per element (1–4). */ channel: number; + /** Number of columns (image width). */ cols: number; + /** Number of rows (image height). */ rows: number; + /** + * Typed-array view over {@link buffer} matching {@link type}: + * `Uint8Array`, `Int32Array`, `Float32Array` or `Float64Array`. + * Element `(row, col, ch)` lives at index `(row * cols + col) * channel + ch`. + */ data: any; + /** Raw backing storage; several views of it are exposed through {@link data}. */ buffer: data_t; + /** + * @param c Number of columns (width). + * @param r Number of rows (height). + * @param _data_type Packed type signature, e.g. `U8_t | C1_t`. + * @param _data_buffer Optional pre-existing buffer to wrap instead of + * allocating a new one (used with cache-pool buffers). + */ constructor(c: number, r: number, _data_type: number, _data_buffer?: data_t); + /** + * Allocates a fresh backing buffer sized from the current + * `cols * rows * channel * sizeof(type)` and points {@link data} at the + * view matching {@link type}. Any previous buffer reference is dropped. + */ allocate(): void; + /** + * Copies every element of this matrix into `other` (unrolled by 4 for + * speed). The destination must be at least `cols * rows * channel` + * elements large; no bounds checking is performed. + * + * @param other Destination matrix receiving the element values. + */ copy_to(other: IMatrix_T): void; + /** + * Changes the logical dimensions of the matrix. The backing buffer is + * reallocated only when the new size does not fit in the current one; + * otherwise the existing storage (and its contents) are reused. + * + * @param c New number of columns. + * @param r New number of rows. + * @param ch New channel count; defaults to the current {@link channel}. + */ resize(c: number, r: number, ch: number): void; } diff --git a/types/src/motion_estimator/motion_estimator.d.ts b/types/src/motion_estimator/motion_estimator.d.ts index f3ebf76..8bbea30 100644 --- a/types/src/motion_estimator/motion_estimator.d.ts +++ b/types/src/motion_estimator/motion_estimator.d.ts @@ -3,10 +3,75 @@ import { matrix_t } from '../matrix_t/matrix_t'; import { point_t } from '../point_t/point_t'; import { ransac_params_t } from './ransac_params_t'; import { homography2d } from '../motion_model/motion_model'; +/** + * Robust motion-model estimation from noisy point correspondences via + * RANSAC or LMEDS, parameterized by a kernel implementing + * {@link IHomography2d} (`homography2d` or `affine2d` from + * `src/motion_model/motion_model.ts`). + * + * Mirrors `jsfeat.motion_estimator` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class motion_estimator extends jsfeatNext { constructor(); + /** + * Draws a random minimal sample of `need_cnt` distinct correspondences + * (via `Math.random`) and validates it with `kernel.check_subset`. + * Retries up to 1000 times before giving up. + * + * @param kernel The motion-model kernel (validates the sample). + * @param from Source points. @param to Destination points. + * @param need_cnt Sample size to draw. + * @param max_cnt Total number of correspondences to draw from. + * @param from_sub Output array receiving the sampled source points. + * @param to_sub Output array receiving the sampled destination points. + * @returns `true` when a valid subset was found. + */ get_subset(kernel: homography2d, from: point_t[], to: point_t[], need_cnt: number, max_cnt: number, from_sub: point_t[], to_sub: point_t[]): boolean; + /** + * Classifies every correspondence as inlier/outlier by thresholding the + * kernel's squared reprojection error of `model`. + * + * @param kernel The motion-model kernel (provides `error`). + * @param model Model to evaluate. + * @param from Source points. @param to Destination points. + * @param count Number of correspondences. + * @param thresh Inlier error threshold in pixels (squared internally). + * @param err Scratch array receiving per-point squared errors. + * @param mask Output 0/1 inlier mask (length `count`). + * @returns The number of inliers. + */ find_inliers(kernel: homography2d, model: matrix_t, from: point_t[], to: point_t[], count: number, thresh: number, err: Int32Array | Float32Array, mask: number[]): number; + /** + * RANSAC estimation: repeatedly fits the kernel's model to random + * minimal samples, keeps the hypothesis with the most inliers (adapting + * the iteration count from the observed inlier ratio), and finally + * refits the model on all inliers of the best hypothesis. + * + * @param params Estimation parameters ({@link ransac_params_t}). + * @param kernel Motion-model kernel (`homography2d` / `affine2d`). + * @param from Source points. @param to Destination points. + * @param count Number of correspondences. + * @param model Output 3×3 model matrix. + * @param mask Output 0/1 inlier mask (`count`×1 matrix), optional. + * @param max_iters Iteration cap. Default 1000. + * @returns `true` when a model with enough inliers was found. + */ ransac(params: ransac_params_t, kernel: any, from: point_t[], to: point_t[], count: number, model: matrix_t, mask: matrix_t, max_iters: number): boolean; + /** + * Least-median-of-squares estimation: like {@link ransac} but scores each + * hypothesis by the MEDIAN squared error (no inlier threshold needed — + * robust up to 50% outliers), then derives an inlier threshold from the + * winning median's robust standard deviation and refits on the inliers. + * + * @param params Estimation parameters (`thresh` is ignored). + * @param kernel Motion-model kernel (`homography2d` / `affine2d`). + * @param from Source points. @param to Destination points. + * @param count Number of correspondences. + * @param model Output 3×3 model matrix. + * @param mask Output 0/1 inlier mask (`count`×1 matrix), optional. + * @param max_iters Iteration cap. Default 1000. + * @returns `true` when a model was found. + */ lmeds(params: ransac_params_t, kernel: any, from: point_t[], to: point_t[], count: number, model: matrix_t, mask: matrix_t, max_iters: number): boolean; } diff --git a/types/src/motion_estimator/ransac_params_t.d.ts b/types/src/motion_estimator/ransac_params_t.d.ts index 292b9b0..aea564b 100644 --- a/types/src/motion_estimator/ransac_params_t.d.ts +++ b/types/src/motion_estimator/ransac_params_t.d.ts @@ -1,8 +1,32 @@ +/** + * Parameter block for `motion_estimator.ransac` / `motion_estimator.lmeds`. + * + * Mirrors `jsfeat.ransac_params_t` from the original library. + */ export declare class ransac_params_t { + /** Minimal sample size per model hypothesis (e.g. 4 for homography2d, 3 for affine2d). */ size: number; + /** Inlier reprojection-error threshold in pixels (unused by LMEDS). */ thresh: number; + /** Assumed outlier ratio (0–1) used to derive the iteration count. */ eps: number; + /** Desired probability (0–1) of finding an outlier-free sample. */ prob: number; + /** + * @param size Minimal sample size per hypothesis. Default 0. + * @param thresh Inlier error threshold in pixels. Default 0.5. + * @param eps Assumed outlier ratio. Default 0.5. + * @param prob Desired success probability. Default 0.99. + */ constructor(size?: number, thresh?: number, eps?: number, prob?: number); + /** + * Recomputes the RANSAC iteration count from the standard formula + * `log(1 - prob) / log(1 - (1 - eps)^size)`, capped at `max_iters`. + * Called by the estimator whenever a better inlier ratio is found. + * + * @param _eps Current outlier-ratio estimate. + * @param max_iters Upper bound on the number of iterations. + * @returns The updated iteration count (integer). + */ update_iters(_eps: number, max_iters: number): number; } diff --git a/types/src/motion_model/motion_model.d.ts b/types/src/motion_model/motion_model.d.ts index 3a20d88..f1b823e 100644 --- a/types/src/motion_model/motion_model.d.ts +++ b/types/src/motion_model/motion_model.d.ts @@ -1,27 +1,125 @@ import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; import { point_t } from '../point_t/point_t'; +/** + * Shared base of the motion-model kernels ({@link affine2d}, + * {@link homography2d}): scratch matrices plus the point-normalization and + * degeneracy helpers both kernels use. In original jsfeat these classes live + * under the `jsfeat.motion_model` namespace. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class motion_model extends jsfeatNext { + /** 3×3 normalization transform for the source points. */ T0: matrix_t; + /** 3×3 normalization transform for the destination points. */ T1: matrix_t; + /** 6×6 normal-equations matrix scratch (`Aᵀ·A`). */ AtA: matrix_t; + /** 6×1 normal-equations right-hand side scratch (`Aᵀ·B`). */ AtB: matrix_t; constructor(); + /** @returns `x²`. */ sqr(x: number): number; + /** + * Computes isotropic (Hartley) normalization transforms for both point + * sets: each is translated to its centroid and scaled so the mean + * distance from the origin is √2 — the standard conditioning step before + * solving for a transform. + * + * @param from Source points. @param to Destination points. + * @param T0 Output 3×3 transform (row-major array) for `from`. + * @param T1 Output 3×3 transform (row-major array) for `to`. + * @param count Number of points. + */ iso_normalize_points(from: point_t[], to: point_t[], T0: number[], T1: number[], count: number): void; + /** + * Checks whether the last point of a minimal sample lies on a line + * through any two previously selected points (a degenerate + * configuration for transform estimation). + * + * @param points The sampled points. @param count Sample size. + * @returns `true` when a collinear triple exists. + */ have_collinear_points(points: point_t[], count: number): boolean; } +/** + * Affine (6-DOF) motion-model kernel for {@link motion_estimator}: estimates + * the 2×3 affine transform (stored in a 3×3 matrix with `[0,0,1]` bottom + * row) by least squares over normalized points. Minimal sample size: 3. + */ export declare class affine2d extends motion_model { constructor(); + /** + * Estimates the affine transform mapping `from` → `to` by solving the + * normal equations (`lu_solve`) over isotropically normalized points, + * then denormalizes into `model`. + * + * @param from Source points. @param to Destination points. + * @param model Output 3×3 matrix (last row set to `[0, 0, 1]`). + * @param count Number of correspondences (≥ 3). + * @returns 1 (one model produced). + */ run(from: point_t[], to: point_t[], model: matrix_t, count: number): number; + /** + * Per-point squared reprojection error of the affine model: + * `err[i] = |to[i] - A·from[i]|²`. (Ported from original jsfeat's + * affine2d; jsfeatNext was missing it, which made RANSAC/LMEDS with an + * affine2d kernel throw — see issue #51.) + * + * @param from Source points. @param to Destination points. + * @param model 3×3 affine model (first 6 entries used). + * @param err Output per-point squared errors. + * @param count Number of correspondences. + */ error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void; + /** + * Affine sampling has no degenerate-quad check — every minimal sample is + * accepted (matches original jsfeat). + * + * @returns Always `true`. + */ check_subset(from: point_t[], to: point_t[], count: number): boolean; } +/** + * Homography (8-DOF perspective) motion-model kernel for + * {@link motion_estimator}: estimates the 3×3 homography by the normalized + * DLT method (smallest eigenvector of `LᵀL` via `linalg.eigenVV`). + * Minimal sample size: 4. + */ export declare class homography2d extends motion_model { + /** 9×9 scratch for the DLT normal matrix `LᵀL`. */ mLtL: matrix_t; + /** 9×9 scratch for its eigenvectors. */ Evec: matrix_t; constructor(); + /** + * Estimates the homography mapping `from` -> `to` by normalized DLT: + * builds the 9x9 normal matrix over normalized points, takes the + * eigenvector of the smallest eigenvalue as the model, denormalizes and + * scales so `model[8] === 1`. + * + * @param from Source points. @param to Destination points. + * @param model Output 3x3 homography. + * @param count Number of correspondences (>= 4). + * @returns 1 on success, 0 on a degenerate (zero-spread) configuration. + */ run(from: point_t[], to: point_t[], model: matrix_t, count: number): number; + /** + * Per-point squared reprojection error of the homography: + * `err[i] = |to[i] - project(model, from[i])|^2`. + * + * @param from Source points. @param to Destination points. + * @param model 3x3 homography to evaluate. + * @param err Output per-point squared errors. + * @param count Number of correspondences. + */ error(from: point_t[], to: point_t[], model: matrix_t, err: Int32Array | Float32Array, count: number): void; + /** + * Rejects minimal samples whose four points are not consistently + * oriented (mixed triangle-orientation signs between the source and + * destination quads), which would produce a flipped homography. + * + * @returns `true` when the 4-point sample is usable. + */ check_subset(from: point_t[], to: point_t[], count: number): boolean; } diff --git a/types/src/node_utils/_pool_node_t.d.ts b/types/src/node_utils/_pool_node_t.d.ts index 5f1847f..1871e16 100644 --- a/types/src/node_utils/_pool_node_t.d.ts +++ b/types/src/node_utils/_pool_node_t.d.ts @@ -1,16 +1,42 @@ import { IData_T } from './data_t'; +/** Public shape of {@link _pool_node_t}. */ export interface IPool_Node_T { + /** Replaces the node's storage with a larger buffer. */ resize: (size_in_bytes: number) => void; } +/** + * One node of the {@link cache} buffer pool: a linked-list entry wrapping a + * {@link data_t} and mirroring its typed-array views directly on the node, + * so borrowers can use `node.f32`, `node.i32`, etc. without indirection. + */ export default class _pool_node_t implements IPool_Node_T { + /** Next node in the pool's linked list (`null` at the tail). */ next: any; + /** The wrapped storage object. */ data?: IData_T; + /** Byte size of the current storage (aligned to a multiple of 8). */ size: number; + /** The underlying `ArrayBuffer` (mirror of `data.buffer`). */ buffer: any; + /** Unsigned 8-bit view (mirror of `data.u8`). */ u8: Uint8Array; + /** Signed 32-bit integer view (mirror of `data.i32`). */ i32: Int32Array; + /** 32-bit float view (mirror of `data.f32`). */ f32: Float32Array; + /** 64-bit float view (mirror of `data.f64`). */ f64: Float64Array; + /** + * @param size_in_bytes Initial byte size of the node's storage. + */ constructor(size_in_bytes: number); + /** + * Discards the current storage and allocates a fresh, larger one, + * refreshing every typed-array view. Called by `cache.get_buffer` when a + * borrower requests more space than the node currently holds. Previous + * contents are NOT preserved. + * + * @param size_in_bytes New byte size (aligned up to a multiple of 8). + */ resize(size_in_bytes: number): void; } diff --git a/types/src/node_utils/data_t.d.ts b/types/src/node_utils/data_t.d.ts index 25ed4a3..b5434b0 100644 --- a/types/src/node_utils/data_t.d.ts +++ b/types/src/node_utils/data_t.d.ts @@ -1,17 +1,43 @@ +/** Public shape of {@link data_t}: raw storage with multi-type views. */ export interface IData_T { + /** Byte size of the buffer (aligned to a multiple of 8). */ size: number; + /** The underlying `ArrayBuffer`. */ buffer: ArrayBuffer; + /** Unsigned 8-bit view over {@link buffer}. */ u8: Uint8Array; + /** Signed 32-bit integer view over {@link buffer}. */ i32: Int32Array; + /** 32-bit float view over {@link buffer}. */ f32: Float32Array; + /** 64-bit float view over {@link buffer}. */ f64: Float64Array; } +/** + * Raw byte storage exposing typed-array views of every element type the + * library uses. `matrix_t` and the cache pool build on it: allocating one + * buffer and reading it as `u8`/`i32`/`f32`/`f64` lets algorithms reinterpret + * scratch memory without extra allocations. + * + * The byte size is aligned up to a multiple of 8 so the `f64` view is valid. + */ export declare class data_t implements IData_T { + /** Byte size of the buffer (aligned to a multiple of 8). */ size: number; + /** The underlying `ArrayBuffer`. */ buffer: ArrayBuffer; + /** Unsigned 8-bit view over {@link buffer}. */ u8: Uint8Array; + /** Signed 32-bit integer view over {@link buffer}. */ i32: Int32Array; + /** 32-bit float view over {@link buffer}. */ f32: Float32Array; + /** 64-bit float view over {@link buffer}. */ f64: Float64Array; + /** + * @param size_in_bytes Requested byte size; rounded up to a multiple of 8. + * @param buffer Optional existing buffer to wrap instead of + * allocating (its length becomes {@link size}). + */ constructor(size_in_bytes: number, buffer?: any); } diff --git a/types/src/optical_flow_lk/optical_flow_lk.d.ts b/types/src/optical_flow_lk/optical_flow_lk.d.ts index 570a973..e09a90a 100644 --- a/types/src/optical_flow_lk/optical_flow_lk.d.ts +++ b/types/src/optical_flow_lk/optical_flow_lk.d.ts @@ -1,7 +1,37 @@ import { default as jsfeatNext } from '../core/core'; import { pyramid_t } from '../pyramid_t/pyramid_t'; +/** + * Pyramidal Lucas–Kanade sparse optical flow: tracks a set of points from a + * previous frame to the current one by iteratively minimizing the local + * intensity difference, coarse-to-fine across image pyramids (the classic + * Bouguet formulation, using Scharr derivatives). + * + * Mirrors `jsfeat.optical_flow_lk` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class optical_flow_lk extends jsfeatNext { + /** Bound `imgproc.scharr_derivatives`, used to build the gradient maps. */ scharr_deriv: any; constructor(); + /** + * Tracks `count` points between two image pyramids (both already built + * with `pyramid_t.build`). For each point the flow is estimated at the + * coarsest level and refined down to level 0. + * + * @param prev_pyr Pyramid of the previous frame. + * @param curr_pyr Pyramid of the current frame. + * @param prev_xy Input point coordinates, interleaved `[x0,y0,x1,y1,…]`. + * @param curr_xy Output tracked coordinates (same layout). Seed it with + * a prediction or a copy of `prev_xy`. + * @param count Number of points to track. + * @param win_size Side of the square tracking window (e.g. 15 or 21). + * @param max_iter Max refinement iterations per level. Default 30. + * @param status Output per-point flags: 1 = tracked, 0 = lost. + * Allocated internally when omitted. + * @param eps Convergence threshold on the update step. Default 0.01. + * @param min_eigen_threshold Minimum normalized eigenvalue of the + * spatial-gradient matrix; below it a point is dropped + * (textureless window). Default 0.0001. + */ track(prev_pyr: pyramid_t, curr_pyr: pyramid_t, prev_xy: Float32Array, curr_xy: Float32Array, count: number, win_size: number, max_iter: number, status: Uint8Array, eps: number, min_eigen_threshold: number): void; } diff --git a/types/src/orb/bit_pattern_31.d.ts b/types/src/orb/bit_pattern_31.d.ts index 5844f30..53a675b 100644 --- a/types/src/orb/bit_pattern_31.d.ts +++ b/types/src/orb/bit_pattern_31.d.ts @@ -1 +1,8 @@ +/** + * The learned ORB sampling pattern: 256 pixel-pair comparisons inside a + * 31×31 patch, stored flat as `[x1, y1, x2, y2, …]` (1024 numbers). Each + * pair contributes one bit of the 256-bit binary descriptor produced by + * `orb.describe`. Taken verbatim from the original ORB paper / OpenCV + * implementation (the inline comments carry the training statistics). + */ export declare const bit_pattern_31: number[]; diff --git a/types/src/orb/orb.d.ts b/types/src/orb/orb.d.ts index 5f39d57..61663d1 100644 --- a/types/src/orb/orb.d.ts +++ b/types/src/orb/orb.d.ts @@ -2,11 +2,36 @@ import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; import { keypoint_t } from '../keypoint_t/keypoint_t'; import { imgproc } from '../imgproc/imgproc'; +/** + * ORB binary descriptor extractor (Oriented FAST and Rotated BRIEF): for + * each keypoint a rotation-rectified 32×32 patch is sampled and 256 + * pixel-pair comparisons from the learned {@link bit_pattern_31} pattern are + * packed into a 32-byte binary descriptor. Descriptors are matched with + * Hamming distance. + * + * Mirrors `jsfeat.orb` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class orb extends jsfeatNext { + /** The learned 256-pair sampling pattern (flat `[x1,y1,x2,y2,…]`). */ bit_pattern_31_: Int32Array; + /** Scratch 3×3 matrix for the per-keypoint rectification transform. */ H: matrix_t; + /** Scratch 32×32 patch the keypoint neighborhood is warped into. */ patch_img: matrix_t; + /** Image-processing helper used for the affine patch warp. */ imgproc: imgproc; constructor(); + /** + * Computes 256-bit (32-byte) binary descriptors for `count` keypoints. + * Each keypoint's `angle` is used to rotation-rectify its patch, making + * the descriptor rotation-invariant. + * + * @param src Source grayscale image the keypoints live in. + * @param corners Keypoints to describe (uses `x`, `y`, `angle`). + * @param count Number of keypoints to process. + * @param descriptors Destination matrix, resized to 32×`count` `U8` — + * one 32-byte descriptor per row. + */ describe(src: matrix_t, corners: keypoint_t[], count: number, descriptors: matrix_t): void; } diff --git a/types/src/orb/rectify_patch.d.ts b/types/src/orb/rectify_patch.d.ts index ba133a9..32af322 100644 --- a/types/src/orb/rectify_patch.d.ts +++ b/types/src/orb/rectify_patch.d.ts @@ -1,3 +1,18 @@ import { matrix_t } from '../matrix_t/matrix_t'; import { imgproc } from '../imgproc/imgproc'; +/** + * Extracts a rotation-rectified square patch around a keypoint: builds a 2×3 + * affine transform that rotates by `angle` around `(px, py)` and centers a + * `psize`×`psize` window, then warps the source image through it. Used by + * `orb.describe` to make the BRIEF-style descriptor rotation-invariant. + * + * @param src Source grayscale image. + * @param dst Destination patch (resized to `psize`×`psize` by the warp). + * @param angle Keypoint orientation in radians. + * @param px Keypoint x coordinate in `src`. + * @param py Keypoint y coordinate in `src`. + * @param psize Patch side length in pixels (ORB uses 32). + * @param H 3×3 scratch matrix receiving the affine transform (first 6 entries used). + * @param imgProcessor The `imgproc` instance whose `warp_affine` performs the sampling. + */ export declare function rectify_patch(src: matrix_t, dst: matrix_t, angle: number, px: number, py: number, psize: number, H: matrix_t, imgProcessor: imgproc): void; diff --git a/types/src/point_t/point_t.d.ts b/types/src/point_t/point_t.d.ts index 131c795..ab12b56 100644 --- a/types/src/point_t/point_t.d.ts +++ b/types/src/point_t/point_t.d.ts @@ -1,15 +1,32 @@ +/** Public shape of {@link point_t}. */ interface IPoint_t { + /** X (column) coordinate in pixels. */ x: number; + /** Y (row) coordinate in pixels. */ y: number; + /** Pyramid level the point belongs to. */ level: number; + /** Detector response / corner strength. */ score: number; + /** Feature orientation in radians (-1 when not computed). */ angle: number; } +/** + * A lightweight 2D feature point. Unlike {@link keypoint_t} the fields are + * not initialized by the constructor — detector code (e.g. + * `fast_corners.detect`) assigns them directly on pre-allocated arrays of + * points, so no per-point construction cost is paid in hot loops. + */ export declare class point_t implements IPoint_t { + /** X (column) coordinate in pixels. */ x: number; + /** Y (row) coordinate in pixels. */ y: number; + /** Pyramid level the point belongs to. */ level: number; + /** Detector response / corner strength. */ score: number; + /** Feature orientation in radians (-1 when not computed). */ angle: number; constructor(); } diff --git a/types/src/pyramid_t/pyramid_t.d.ts b/types/src/pyramid_t/pyramid_t.d.ts index 510ccff..805af48 100644 --- a/types/src/pyramid_t/pyramid_t.d.ts +++ b/types/src/pyramid_t/pyramid_t.d.ts @@ -1,10 +1,37 @@ import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; +/** + * An image pyramid: level 0 holds the full-resolution image and each + * subsequent level halves the previous one via `imgproc.pyrdown`. Consumed + * by `optical_flow_lk.track` for coarse-to-fine tracking. + * + * Mirrors `jsfeat.pyramid_t` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class pyramid_t extends jsfeatNext { + /** Number of pyramid levels. */ levels: number; + /** The level images: `data[i]` is a {@link matrix_t} of size `w>>i` × `h>>i`. */ data: any; + /** Bound `imgproc.pyrdown` used to build the levels. */ private pyrdown; constructor(levels: number); + /** + * Allocates the per-level matrices for a base image of + * `start_w`×`start_h` (level `i` gets `start_w>>i` × `start_h>>i`). + * + * @param start_w Level-0 width. @param start_h Level-0 height. + * @param data_type Packed type signature for the level images (e.g. `U8_t | C1_t`). + */ allocate(start_w: number, start_h: number, data_type: number): void; + /** + * Fills the pyramid from `input`: optionally copies it into level 0, + * then repeatedly downsamples to populate the remaining levels. + * {@link allocate} must have been called first. + * + * @param input Level-0 source image. + * @param skip_first_level When `true` (the default) level 0 is assumed + * to already hold the input and is not copied. + */ build(input: matrix_t, skip_first_level: boolean): void; } diff --git a/types/src/transform/transform.d.ts b/types/src/transform/transform.d.ts index 655b50b..8024ac1 100644 --- a/types/src/transform/transform.d.ts +++ b/types/src/transform/transform.d.ts @@ -1,7 +1,39 @@ import { matrix_t } from '../matrix_t/matrix_t'; +/** + * 2D geometric transform construction and inversion. + * + * NOTE (parity): the original jsfeat `transform` module was never included + * in any distributed jsfeat build, and its functions take RAW ARRAYS — + * jsfeatNext's methods take {@link matrix_t} instead (same math, different + * calling convention; see the parity audit, Axis 2). + */ export declare class transform { constructor(); + /** + * Computes the 3×3 perspective transform (homography) that maps four + * source points onto four destination points, via the closed-form + * `R = Hl · Hr⁻¹` construction (both quads are first mapped to the unit + * square). Writes the 9 coefficients into `model`. + * + * @param model 3×3 destination matrix. + * @param src_x0…dst_y3 The four `(src, dst)` point pairs, interleaved as + * `src_x0, src_y0, dst_x0, dst_y0, …` for points 0–3. + */ perspective_4point_transform(model: matrix_t, src_x0: number, src_y0: number, dst_x0: number, dst_y0: number, src_x1: number, src_y1: number, dst_x1: number, dst_y1: number, src_x2: number, src_y2: number, dst_x2: number, dst_y2: number, src_x3: number, src_y3: number, dst_x3: number, dst_y3: number): void; + /** + * Inverts a 2×3 affine transform in closed form (via the 2×2 linear + * part's determinant). Only the first 6 entries of `src`/`dst` are used. + * + * @param src Source affine transform (2×3 coefficients). + * @param dst Destination for the inverse (2×3 coefficients). + */ invert_affine_transform(src: matrix_t, dst: matrix_t): void; + /** + * Inverts a 3×3 perspective transform (homography) by the + * adjugate/determinant closed form. The input must be non-singular. + * + * @param src Source 3×3 transform. + * @param dst Destination for the inverse (3×3). + */ invert_perspective_transform(src: matrix_t, dst: matrix_t): void; } diff --git a/types/src/yape/yape.d.ts b/types/src/yape/yape.d.ts index 1564f54..250d421 100644 --- a/types/src/yape/yape.d.ts +++ b/types/src/yape/yape.d.ts @@ -1,9 +1,40 @@ import { matrix_t } from '../matrix_t/matrix_t'; import { keypoint_t } from '../keypoint_t/keypoint_t'; +/** + * YAPE ("Yet Another Point Extractor") interest-point detector: scores each + * pixel by comparing it against a precomputed circle of samples at the given + * radius, then keeps directionally consistent local maxima. + * + * Requires {@link init} to be called once with the image dimensions before + * the first {@link detect}. Mirrors `jsfeat.yape` from the original library. + */ export declare class yape { + /** Per-pyramid-level precomputed circle offsets and score maps. */ private level_tables; + /** Intensity tolerance: samples within ±tau of the center are "similar". */ private tau; constructor(); + /** + * Precomputes the per-level sampling tables (circle offsets and score + * buffers). Must be called before {@link detect}, and again whenever the + * image size changes. + * + * @param width Image width at level 0. + * @param height Image height at level 0. + * @param radius Sampling-circle radius, clamped to [3, 7]. + * @param pyramid_levels Number of levels to prepare. Default 1. + */ init(width: number, height: number, radius: number, pyramid_levels?: number): void; + /** + * Detects interest points in a grayscale image: scores every pixel via + * the circle test, then emits points that pass the third-check and + * local-maxima suppression, writing them into the pre-allocated `points` + * array (each entry gets `x`, `y` and `score`). + * + * @param src Source grayscale image (size must match {@link init}). + * @param points Pre-allocated keypoint pool to fill. + * @param border Pixels to skip along each edge. Default 4. + * @returns The number of points written into `points`. + */ detect(src: matrix_t, points: keypoint_t[], border?: number): number; } diff --git a/types/src/yape/yape_utils.d.ts b/types/src/yape/yape_utils.d.ts index 492c66c..7d038c9 100644 --- a/types/src/yape/yape_utils.d.ts +++ b/types/src/yape/yape_utils.d.ts @@ -1,13 +1,64 @@ +/** + * Precomputes the flat pixel offsets of a Bresenham-style circle of radius + * `R` for an image with row stride `step`, walking the circle once around. + * The first two offsets are duplicated at the end for wrap-around access. + * + * @param step Image row stride (width). + * @param dirs Output offset table (must hold the circle + 2 entries). + * @param R Circle radius in pixels. + * @returns The number of unique circle offsets written. + */ export declare function precompute_directions(step: number, dirs: Int32Array, R: number): number; +/** + * Counts how many of the 8 neighbors of a score-map pixel are non-zero — + * YAPE's "third check" requiring a candidate to be supported by at least 3 + * responding neighbors. + * + * @param Sb Score map. @param off Pixel index. @param step Row stride. + * @returns The number of non-zero neighbors (0–8). + */ export declare function third_check(Sb: Int32Array | number[], off: number, step: number): number; +/** + * Signed local-extremum test over a square neighborhood: for positive `v` + * no neighbor may exceed it; for negative `v` no neighbor may be smaller. + * + * @param p Score map. @param off Pixel index. + * @param v Score value at `off`. + * @param step Row stride of the neighborhood scan. + * @param neighborhood Half-size of the square window. + * @returns `true` when the pixel is a local extremum of its sign. + */ export declare function is_local_maxima(p: Int32Array, off: number, v: number, step: number, neighborhood: number): boolean; +/** + * Scores one candidate pixel with the YAPE circle test: walks the sampling + * circle as a small state machine tracking runs of brighter/darker/similar + * samples (relative to the `[Im, Ip]` tolerance band) and writes the + * accumulated signed score — or 0 when the pattern disqualifies the pixel — + * into `Scores[x]`. + * + * @param I Image data. @param x Candidate pixel index. + * @param Scores Output score map. + * @param Im Lower tolerance bound (`center - tau`). + * @param Ip Upper tolerance bound (`center + tau`). + * @param dirs Precomputed circle offsets (from {@link precompute_directions}). + * @param opposite Index offset of the diametrically opposite sample. + * @param dirs_nb Number of circle samples. + */ export declare function perform_one_point(I: { [x: string]: number; }, x: number, Scores: Int32Array, Im: number, Ip: number, dirs: any[] | Int32Array, opposite: number, dirs_nb: number): void; +/** + * Per-pyramid-level lookup table for the YAPE detector: the precomputed + * sampling circle plus a full-frame score buffer for the level's dimensions. + */ export declare class lev_table_t { + /** Flat pixel offsets of the sampling circle (with wrap-around entries). */ dirs: Int32Array; + /** Number of unique circle offsets in {@link dirs}. */ dirs_count: number; + /** Per-pixel signed score map (`w · h`). */ scores: Int32Array; + /** Circle radius the table was built for. */ radius: number; constructor(w: number, h: number, r: number); } diff --git a/types/src/yape06/yape06.d.ts b/types/src/yape06/yape06.d.ts index 6e546bd..13ab5b9 100644 --- a/types/src/yape06/yape06.d.ts +++ b/types/src/yape06/yape06.d.ts @@ -1,9 +1,30 @@ import { default as jsfeatNext } from '../core/core'; import { matrix_t } from '../matrix_t/matrix_t'; import { keypoint_t } from '../keypoint_t/keypoint_t'; +/** + * YAPE06 interest-point detector: thresholds a Laplacian response map, then + * rejects edge-like responses via the minimum eigenvalue of the local + * Hessian, followed by 3×3 non-maximum suppression. + * + * Mirrors `jsfeat.yape06` from the original library. + * (Moved out of the src/jsfeatNext.ts monolith in issue #47.) + */ export declare class yape06 extends jsfeatNext { + /** Minimum |Laplacian| response for a candidate point. Default 30. */ laplacian_threshold: number; + /** Minimum Hessian min-eigenvalue (cornerness) for a candidate. Default 25. */ min_eigen_value_threshold: number; constructor(); + /** + * Detects interest points in a grayscale image. Results are written into + * the pre-allocated `points` array (each entry gets `x`, `y`, `score`). + * Tune sensitivity through {@link laplacian_threshold} and + * {@link min_eigen_value_threshold}. + * + * @param src Source grayscale image (`U8C1`). + * @param points Pre-allocated keypoint pool to fill. + * @param border Pixels to skip along each edge. Default 5. + * @returns The number of points written into `points`. + */ detect(src: matrix_t, points: keypoint_t[], border: number): number; } diff --git a/types/src/yape06/yape06_utils.d.ts b/types/src/yape06/yape06_utils.d.ts index e79b1de..75b8c15 100644 --- a/types/src/yape06/yape06_utils.d.ts +++ b/types/src/yape06/yape06_utils.d.ts @@ -1,2 +1,28 @@ +/** + * Computes a discrete Laplacian response map over a region of interest: + * `dst[p] = -4·src[p] + src[p±Dxx] + src[p±Dyy]`. Out-of-bounds samples + * write 0. Used by `yape06.detect` as the first interest-point filter. + * + * @param src Source grayscale data. + * @param dst Destination Laplacian map (same layout as `src`). + * @param w Image width (row stride). + * @param Dxx Horizontal sample offset (scaled by the detector radius). + * @param Dyy Vertical sample offset (`radius * w`). + * @param sx Region start x. @param sy Region start y. + * @param ex Region end x (exclusive). @param ey Region end y (exclusive). + */ export declare function compute_laplacian(src: Int32Array | Float32Array, dst: Int32Array | Float32Array, w: number, Dxx: number, Dyy: number, sx: number, sy: number, ex: number, ey: number): void; +/** + * Estimates the minimum eigenvalue magnitude of the local Hessian at a + * candidate point, from the discrete second derivatives Ixx/Iyy/Ixy. + * `yape06.detect` thresholds this to reject edge-like (non-corner) responses. + * + * @param src Source grayscale data. + * @param off Index of the candidate pixel. + * @param tr Laplacian (trace) response at the pixel. + * @param Dxx Horizontal second-derivative offset. + * @param Dyy Vertical second-derivative offset. + * @param Dxy First diagonal offset. @param Dyx Second diagonal offset. + * @returns The smaller absolute eigenvalue of the local Hessian. + */ export declare function hessian_min_eigen_value(src: number[], off: number, tr: number, Dxx: number, Dyy: number, Dxy: number, Dyx: number): number;