403Webshell
Server IP : 217.154.3.148  /  Your IP : 216.73.216.90
Web Server : nginx/1.30.3
System : Linux blissful-wright.217-154-3-148.plesk.page 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64
User : gracious-boyd_hfhh1h8vo9n ( 10000)
PHP Version : 8.3.31
Disable Function : opcache_get_status
MySQL : OFF  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /var/www/vhosts/gracious-boyd.217-154-3-148.plesk.page/nextjs/scripts/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/vhosts/gracious-boyd.217-154-3-148.plesk.page/nextjs/scripts/benchmark-analyzers.js
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const sharp = require('sharp');
const FFT = require('fft.js');

const DEFAULT_MAX_SIDE = 1200;

function clamp(v, lo, hi) {
  return Math.max(lo, Math.min(hi, v));
}

function median(values) {
  const arr = values.filter(Number.isFinite).slice().sort((a, b) => a - b);
  if (!arr.length) return 0;
  const mid = Math.floor(arr.length / 2);
  return arr.length % 2 ? arr[mid] : 0.5 * (arr[mid - 1] + arr[mid]);
}

async function loadImage(filePath, maxSide = DEFAULT_MAX_SIDE) {
  const input = sharp(filePath).removeAlpha();
  const meta = await input.metadata();
  const srcW = meta.width || 0;
  const srcH = meta.height || 0;
  if (!(srcW > 0 && srcH > 0)) {
    throw new Error(`invalid image dimensions for ${filePath}`);
  }
  let work = input;
  let W = srcW;
  let H = srcH;
  if (Math.max(W, H) > maxSide) {
    const scale = maxSide / Math.max(W, H);
    W = Math.max(1, Math.round(W * scale));
    H = Math.max(1, Math.round(H * scale));
    work = work.resize(W, H, { fit: 'inside' });
  }
  const { data, info } = await work.raw().ensureAlpha().toBuffer({ resolveWithObject: true });
  const rgba = new Uint8Array(data);
  const gray = toGrayFloat32(rgba, info.width, info.height);
  const mag = sobelMag(gray, info.width, info.height);
  return {
    rgba,
    gray,
    mag,
    workWidth: info.width,
    workHeight: info.height,
    srcWidth: srcW,
    srcHeight: srcH,
    scaleX: srcW / info.width,
    scaleY: srcH / info.height,
  };
}

function toGrayFloat32(rgba, w, h) {
  const out = new Float32Array(w * h);
  for (let i = 0, j = 0; i < rgba.length; i += 4, j += 1) {
    out[j] = 0.299 * rgba[i] + 0.587 * rgba[i + 1] + 0.114 * rgba[i + 2];
  }
  return out;
}

function sobelMag(gray, w, h) {
  const out = new Float32Array(w * h);
  const gxk = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
  const gyk = [-1, -2, -1, 0, 0, 0, 1, 2, 1];
  for (let y = 1; y < h - 1; y++) {
    for (let x = 1; x < w - 1; x++) {
      let gx = 0;
      let gy = 0;
      let k = 0;
      for (let dy = -1; dy <= 1; dy++) {
        for (let dx = -1; dx <= 1; dx++, k += 1) {
          const v = gray[(y + dy) * w + (x + dx)];
          gx += gxk[k] * v;
          gy += gyk[k] * v;
        }
      }
      out[y * w + x] = Math.hypot(gx, gy);
    }
  }
  return out;
}

function projectColumns(mag, w, h) {
  const out = new Float32Array(w);
  for (let x = 0; x < w; x++) {
    let acc = 0;
    for (let y = 0; y < h; y++) acc += mag[y * w + x];
    out[x] = acc;
  }
  return out;
}

function projectRows(mag, w, h) {
  const out = new Float32Array(h);
  for (let y = 0; y < h; y++) {
    let acc = 0;
    for (let x = 0; x < w; x++) acc += mag[y * w + x];
    out[y] = acc;
  }
  return out;
}

function autoCorr1D(sig) {
  const n = sig.length;
  const out = new Float32Array(n);
  let mean = 0;
  for (let i = 0; i < n; i++) mean += sig[i];
  mean /= n;
  for (let i = 0; i < n; i++) sig[i] = sig[i] - mean;
  for (let lag = 1; lag < n; lag++) {
    let s = 0;
    for (let i = 0; i + lag < n; i++) s += sig[i] * sig[i + lag];
    out[lag] = s;
  }
  out[0] = 0;
  return out;
}

function bestPeriod(ac, minP, maxP) {
  minP = Math.max(3, minP | 0);
  maxP = Math.max(minP + 1, maxP | 0);
  let best = 0;
  let bestVal = -Infinity;
  for (let p = minP; p <= maxP && p < ac.length; p++) {
    const v = ac[p];
    if (v > bestVal) {
      bestVal = v;
      best = p;
    }
  }
  return best;
}

function phaseForPeriod(proj, period) {
  if (period <= 0) return 0;
  let bestOff = 0;
  let bestScore = -Infinity;
  for (let off = 0; off < period; off++) {
    let score = 0;
    for (let i = off; i < proj.length; i += period) score += proj[i];
    if (score > bestScore) {
      bestScore = score;
      bestOff = off;
    }
  }
  return bestOff;
}

function analyzeProjectionPipeline(prep) {
  const { mag, workWidth: w, workHeight: h, scaleX, scaleY } = prep;
  const projX = projectColumns(mag, w, h);
  const projY = projectRows(mag, w, h);
  const acX = autoCorr1D(projX.slice());
  const acY = autoCorr1D(projY.slice());
  const perX = bestPeriod(acX, Math.max(12, Math.floor(w / 100)), Math.max(24, Math.floor(w / 3)));
  const perY = bestPeriod(acY, Math.max(12, Math.floor(h / 100)), Math.max(24, Math.floor(h / 3)));
  const phaseX = phaseForPeriod(projX, perX);
  const phaseY = phaseForPeriod(projY, perY);
  const grout = Math.max(1, Math.round(0.025 * ((perX + perY) / 2)));
  const vlines = [];
  for (let x = phaseX; x < w; x += perX || w) vlines.push(Math.round(x));
  const hlines = [];
  for (let y = phaseY; y < h; y += perY || h) hlines.push(Math.round(y));
  return {
    tile_w_px: Math.round(perX * scaleX),
    tile_h_px: Math.round(perY * scaleY),
    grout_px: Math.round(grout * Math.sqrt(scaleX * scaleY)),
    grid: {
      vlines: vlines.map((x) => Math.round(x * scaleX)),
      hlines: hlines.map((y) => Math.round(y * scaleY)),
    },
    meta: {
      perX,
      perY,
      phaseX,
      phaseY,
    },
  };
}

function gradientFromGray(gray, w, h) {
  const gx = new Float32Array(w * h);
  const gy = new Float32Array(w * h);
  for (let y = 1; y < h - 1; y++) {
    for (let x = 1; x < w - 1; x++) {
      const i = y * w + x;
      const p00 = gray[(y - 1) * w + (x - 1)];
      const p01 = gray[(y - 1) * w + x];
      const p02 = gray[(y - 1) * w + (x + 1)];
      const p10 = gray[y * w + (x - 1)];
      const p12 = gray[y * w + (x + 1)];
      const p20 = gray[(y + 1) * w + (x - 1)];
      const p21 = gray[(y + 1) * w + x];
      const p22 = gray[(y + 1) * w + (x + 1)];
      gx[i] = -p00 - 2 * p10 - p20 + p02 + 2 * p12 + p22;
      gy[i] = p00 + 2 * p01 + p02 - p20 - 2 * p21 - p22;
    }
  }
  return { gx, gy };
}

function confidenceByLines(vlines, hlines, gx, gy, w, h) {
  const col = new Float64Array(w);
  const row = new Float64Array(h);
  for (let y = 1; y < h - 1; y++) {
    let base = y * w;
    for (let x = 1; x < w - 1; x++) {
      const i = base + x;
      col[x] += Math.abs(gx[i]);
      row[y] += Math.abs(gy[i]);
    }
  }
  const medCol = median(Array.from(col));
  const medRow = median(Array.from(row));
  function pick(idxs, arr) {
    if (!idxs || !idxs.length) return 0;
    const samples = [];
    for (const k of idxs) {
      const x = clamp(Math.round(k), 0, arr.length - 1);
      const a0 = arr[x - 1] ?? arr[x];
      const a1 = arr[x];
      const a2 = arr[x + 1] ?? arr[x];
      samples.push((a0 + 2 * a1 + a2) / 4);
    }
    return median(samples);
  }
  const peakCol = pick(vlines, col);
  const peakRow = pick(hlines, row);
  const sV = peakCol > 0 && medCol > 0 ? peakCol / medCol : 0;
  const sH = peakRow > 0 && medRow > 0 ? peakRow / medRow : 0;
  const map = (s) => clamp((s - 1) / 3, 0, 1);
  return clamp(0.5 * (map(sV) + map(sH)), 0, 1);
}

function hannFill(dst, src, start, len) {
  for (let i = 0; i < len; i++) {
    const s = src[start + i] ?? 0;
    const w = 0.5 * (1 - Math.cos((2 * Math.PI * i) / Math.max(1, len - 1)));
    dst[i] = s * w;
  }
  for (let i = len; i < dst.length; i++) dst[i] = 0;
}

function nextPow2(n) {
  return n <= 1 ? 2 : 1 << Math.ceil(Math.log2(n));
}

function realFFTmag(inp) {
  let n = inp.length | 0;
  const isPow2 = (x) => x > 1 && (x & (x - 1)) === 0;
  if (!isPow2(n)) {
    const n2 = nextPow2(Math.max(2, n));
    const buf = new Float64Array(n2);
    buf.set(inp.subarray(0, Math.min(n2, n)));
    inp = buf;
    n = n2;
  }
  const fft = new FFT(n);
  const out = fft.createComplexArray();
  fft.realTransform(out, inp);
  fft.completeSpectrum(out);
  const mags = new Float64Array(n / 2 + 1);
  for (let k = 0; k <= n / 2; k++) {
    const re = out[2 * k] || 0;
    const im = out[2 * k + 1] || 0;
    mags[k] = Math.hypot(re, im);
  }
  return mags;
}

function welchAveragedMag(proj, targetSegs = 4, overlap = 0.5) {
  const N = proj.length;
  const segLenRaw = Math.max(16, Math.floor(N / Math.max(1, targetSegs)));
  const segLen = nextPow2(segLenRaw);
  const hop = Math.max(1, Math.floor(segLen * (1 - overlap)));
  const avg = new Float64Array(segLen / 2 + 1);
  const buf = new Float64Array(segLen);
  let used = 0;
  for (let s = 0; s * hop + segLen <= N; s++) {
    const start = s * hop;
    hannFill(buf, proj, start, segLen);
    const mag = realFFTmag(buf);
    for (let k = 0; k < mag.length; k++) avg[k] += mag[k];
    used += 1;
  }
  if (!used) {
    const nFFT = nextPow2(N);
    const b2 = new Float64Array(nFFT);
    hannFill(b2, proj, 0, N);
    return { mags: realFFTmag(b2), nFFT };
  }
  for (let k = 0; k < avg.length; k++) avg[k] /= used;
  return { mags: avg, nFFT: segLen };
}

function subpixelPeak(mags, k) {
  const a = mags[k - 1] ?? 0;
  const b = mags[k] ?? 0;
  const c = mags[k + 1] ?? 0;
  const denom = a - 2 * b + c;
  if (denom === 0) return 0;
  return clamp(0.5 * (a - c) / denom, -0.5, 0.5);
}

function findPeriodWithWelch(proj, minP = 8, maxP = 1024) {
  const { mags, nFFT } = welchAveragedMag(proj, 4, 0.5);
  const kMin = Math.max(1, Math.floor(nFFT / (maxP || 1024)));
  const kMax = Math.min(Math.floor(nFFT / 2) - 1, Math.ceil(nFFT / Math.max(minP, 2)));
  if (!(kMax > kMin)) {
    return { ok: false, reason: 'no valid k-range', P: NaN };
  }
  let k0 = kMin;
  let v0 = -Infinity;
  for (let k = kMin; k <= kMax; k++) {
    const v = mags[k];
    if (v > v0) {
      v0 = v;
      k0 = k;
    }
  }
  if (!(v0 > 0)) return { ok: false, reason: 'no peak', P: NaN };
  const delta = subpixelPeak(mags, clamp(k0, kMin + 1, kMax - 1));
  const kRef = k0 + delta;
  return { ok: true, P: nFFT / kRef };
}

function gradientProjections(gray, w, h) {
  const projX = new Float32Array(w);
  const projY = new Float32Array(h);
  for (let y = 1; y < h - 1; y++) {
    const o = y * w;
    for (let x = 1; x < w - 1; x++) {
      const i = o + x;
      const gx = gray[i + 1] - gray[i - 1];
      const gy = gray[i + w] - gray[i - w];
      projX[x] += Math.abs(gx);
      projY[y] += Math.abs(gy);
    }
  }
  function smooth(a) {
    const N = a.length;
    if (N < 3) return a.slice();
    const out = new Float32Array(N);
    out[0] = (a[0] * 2 + a[1]) / 3;
    for (let i = 1; i < N - 1; i++) out[i] = (a[i - 1] + a[i] * 2 + a[i + 1]) / 4;
    out[N - 1] = (a[N - 2] + a[N - 1] * 2) / 3;
    return out;
  }
  return { projX: smooth(projX), projY: smooth(projY) };
}

function bestPhaseComb(signal, P) {
  const Pi = Math.max(2, Math.round(P));
  let bestOff = 0;
  let bestS = -Infinity;
  for (let off = 0; off < Pi; off++) {
    let s = 0;
    for (let k = off; k < signal.length; k += Pi) {
      const i = Math.round(k);
      const i0 = clamp(i - 1, 0, signal.length - 1);
      const i2 = clamp(i + 1, 0, signal.length - 1);
      s += Math.max(signal[i0], signal[i], signal[i2]);
    }
    if (s > bestS) {
      bestS = s;
      bestOff = off;
    }
  }
  return bestOff;
}

function groutFromProjection(signal, P, phase, samples = 8) {
  const step = Math.max(2, Math.round(P));
  const widths = [];
  for (let n = 0; n < samples; n++) {
    const x = phase + n * step;
    if (x >= signal.length) break;
    const idx = clamp(Math.round(x), 0, signal.length - 1);
    const peak = signal[idx];
    if (!(peak > 0)) continue;
    const half = peak / 2;
    let L = idx;
    let R = idx;
    while (L > 0 && signal[L] > half) L -= 1;
    while (R < signal.length - 1 && signal[R] > half) R += 1;
    widths.push(R - L);
  }
  if (!widths.length) return Math.max(1, Math.round(0.04 * P));
  widths.sort((a, b) => a - b);
  return clamp(Math.round(widths[Math.floor(widths.length / 2)]), 1, Math.round(P / 2));
}

function analyzeFFTPeriod(gray, w, h) {
  if (!(w > 8 && h > 8)) return { ok: false, reason: 'image too small' };
  const { projX, projY } = gradientProjections(gray, w, h);
  const minP = 8;
  const maxP = Math.max(32, Math.floor(Math.max(w, h) / 2));
  const fx = findPeriodWithWelch(projX, minP, maxP);
  const fy = findPeriodWithWelch(projY, minP, maxP);
  if (!fx.ok || !fy.ok) {
    return { ok: false, reason: `welch fail x:${fx.ok ? 'ok' : fx.reason} y:${fy.ok ? 'ok' : fy.reason}` };
  }
  const phaseX = bestPhaseComb(projX, fx.P);
  const phaseY = bestPhaseComb(projY, fy.P);
  const groutX = groutFromProjection(projX, fx.P, phaseX);
  const groutY = groutFromProjection(projY, fy.P, phaseY);
  const groutPx = Math.max(1, Math.round((groutX + groutY) / 2));
  const stepX = Math.max(2, Math.round(fx.P));
  const stepY = Math.max(2, Math.round(fy.P));
  const vlines = [];
  for (let x = phaseX; x < w; x += stepX) vlines.push(Math.round(x));
  const hlines = [];
  for (let y = phaseY; y < h; y += stepY) hlines.push(Math.round(y));
  return {
    ok: true,
    periodX: fx.P,
    periodY: fy.P,
    phaseX,
    phaseY,
    groutPx,
    vlines,
    hlines,
  };
}

function sobelFull(gray, w, h) {
  const gx = new Float32Array(w * h);
  const gy = new Float32Array(w * h);
  const mag = new Float32Array(w * h);
  for (let y = 1; y < h - 1; y++) {
    for (let x = 1; x < w - 1; x++) {
      const i = y * w + x;
      const p00 = gray[(y - 1) * w + (x - 1)];
      const p01 = gray[(y - 1) * w + x];
      const p02 = gray[(y - 1) * w + (x + 1)];
      const p10 = gray[y * w + (x - 1)];
      const p12 = gray[y * w + (x + 1)];
      const p20 = gray[(y + 1) * w + (x - 1)];
      const p21 = gray[(y + 1) * w + x];
      const p22 = gray[(y + 1) * w + (x + 1)];
      const sx = -p00 - 2 * p10 - p20 + p02 + 2 * p12 + p22;
      const sy = p00 + 2 * p01 + p02 - p20 - 2 * p21 - p22;
      gx[i] = sx;
      gy[i] = sy;
      mag[i] = Math.hypot(sx, sy);
    }
  }
  return { gx, gy, mag };
}

function percentile(array, q) {
  const vals = [];
  const step = Math.max(1, Math.floor(array.length / 50000));
  for (let i = 0; i < array.length; i += step) {
    const v = array[i];
    if (Number.isFinite(v)) vals.push(v);
  }
  if (!vals.length) return 0;
  vals.sort((a, b) => a - b);
  const p = clamp(q, 0, 1) * (vals.length - 1);
  const lo = Math.floor(p);
  const hi = Math.ceil(p);
  if (lo === hi) return vals[lo];
  const t = p - lo;
  return vals[lo] * (1 - t) + vals[hi] * t;
}

function smooth1d(arr, win = 5) {
  if (win <= 1) return Float64Array.from(arr);
  const n = arr.length;
  const out = new Float64Array(n);
  const r = Math.floor(win / 2);
  for (let i = 0; i < n; i++) {
    let s = 0;
    let c = 0;
    for (let k = -r; k <= r; k++) {
      const j = i + k;
      if (j < 0 || j >= n) continue;
      s += arr[j];
      c += 1;
    }
    out[i] = c ? s / c : arr[i];
  }
  return out;
}

function pickTheta(points, w, h, degStart, degEnd, degStep) {
  const maxR = Math.hypot(w, h);
  const thetas = [];
  for (let d = degStart; d <= degEnd; d += degStep) thetas.push((d * Math.PI) / 180);
  const acc = new Float64Array(thetas.length);
  for (const [x, y, wt] of points) {
    for (let t = 0; t < thetas.length; t++) {
      const th = thetas[t];
      const rho = x * Math.cos(th) + y * Math.sin(th);
      const bin = Math.round(rho + maxR);
      if (bin >= 0 && bin <= Math.ceil(2 * maxR)) acc[t] += wt;
    }
  }
  const accS = smooth1d(acc, 5);
  let bestI = 0;
  let bestV = -Infinity;
  for (let i = 0; i < accS.length; i++) {
    if (accS[i] > bestV) {
      bestV = accS[i];
      bestI = i;
    }
  }
  return thetas[bestI] ?? ((degStart + degEnd) * Math.PI) / 360;
}

function rhoHistogram(points, theta, w, h) {
  const maxR = Math.hypot(w, h);
  const bins = new Float64Array(Math.ceil(2 * maxR) + 1);
  for (const [x, y, wt] of points) {
    const rho = x * Math.cos(theta) + y * Math.sin(theta);
    const b = Math.round(rho + maxR);
    if (b >= 0 && b < bins.length) bins[b] += wt;
  }
  return { bins: smooth1d(bins, 7), offset: maxR };
}

function findPeaks(hist, minDist = 8, minRel = 0.1) {
  const maxVal = hist.length ? Math.max(...hist) : 0;
  const thr = maxVal * minRel;
  const peaks = [];
  for (let i = 1; i < hist.length - 1; i++) {
    if (hist[i] > thr && hist[i] > hist[i - 1] && hist[i] > hist[i + 1]) peaks.push(i);
  }
  peaks.sort((a, b) => hist[b] - hist[a]);
  const keep = [];
  for (const p of peaks) {
    if (keep.every((k) => Math.abs(k - p) >= minDist)) keep.push(p);
  }
  keep.sort((a, b) => a - b);
  return keep;
}

function robustPeriodFromPeaks(peaks) {
  if (peaks.length < 2) return NaN;
  const diffs = [];
  for (let i = 1; i < peaks.length; i++) {
    const d = peaks[i] - peaks[i - 1];
    if (d > 2) diffs.push(d);
  }
  if (!diffs.length) return NaN;
  const bins = new Map();
  const maxD = Math.max(...diffs);
  const minD = Math.min(...diffs);
  for (const d of diffs) bins.set(d, (bins.get(d) || 0) + 1);
  let bestD = minD;
  let bestC = -1;
  for (let d = minD; d <= maxD; d++) {
    const c = bins.get(d) || 0;
    if (c > bestC) {
      bestC = c;
      bestD = d;
    }
  }
  const near = diffs.filter((d) => Math.abs(d - bestD) <= 2);
  return median(near.length ? near : [bestD]);
}

function phaseFromPeaks(peaks, period) {
  if (!(period > 0)) return 0;
  const r = peaks.map((p) => ((p % period) + period) % period);
  return Math.round(median(r) || 0);
}

function projectionsFromGrad(gx, gy, w, h) {
  const projX = new Float32Array(w);
  const projY = new Float32Array(h);
  for (let y = 1; y < h - 1; y++) {
    const row = y * w;
    for (let x = 1; x < w - 1; x++) {
      const i = row + x;
      projX[x] += Math.abs(gx[i]);
      projY[y] += Math.abs(gy[i]);
    }
  }
  const smooth = (a) => {
    const out = new Float32Array(a.length);
    for (let i = 0; i < a.length; i++) {
      const a0 = a[i - 1] ?? a[i];
      const a1 = a[i];
      const a2 = a[i + 1] ?? a[i];
      out[i] = (a0 + 2 * a1 + a2) / 4;
    }
    return out;
  };
  return { projX: smooth(projX), projY: smooth(projY) };
}

function groutFromProjectionSmooth(signal, period, phase, samples = 8) {
  const step = Math.max(2, Math.round(period));
  const widths = [];
  for (let n = 0; n < samples; n++) {
    const x = phase + n * step;
    if (x >= signal.length) break;
    const idx = clamp(Math.round(x), 0, signal.length - 1);
    const peak = signal[idx];
    if (!(peak > 0)) continue;
    const half = peak / 2;
    let L = idx;
    let R = idx;
    while (L > 0 && signal[L] > half) L -= 1;
    while (R < signal.length - 1 && signal[R] > half) R += 1;
    widths.push(R - L);
  }
  if (!widths.length) return Math.max(1, Math.round(0.04 * period));
  widths.sort((a, b) => a - b);
  return clamp(Math.round(widths[Math.floor(widths.length / 2)]), 1, Math.round(period / 2));
}

function analyzeHoughGrid(gray, w, h) {
  if (!(w > 16 && h > 16)) return { ok: false, reason: 'image too small/invalid' };
  const { gx, gy, mag } = sobelFull(gray, w, h);
  const thr = percentile(mag, 0.85);
  const pts = [];
  const stride = Math.max(1, Math.floor(Math.max(w, h) / 800));
  for (let y = 1; y < h - 1; y += stride) {
    for (let x = 1; x < w - 1; x += stride) {
      const i = y * w + x;
      const m = mag[i];
      if (m > thr) pts.push([x, y, m]);
    }
  }
  if (pts.length < 200) return { ok: false, reason: 'too few edges' };
  const thetaV = pickTheta(pts, w, h, -15, 15, 1);
  const thetaH = pickTheta(pts, w, h, 75, 105, 1);
  const { bins: rhoV, offset: offV } = rhoHistogram(pts, thetaV, w, h);
  const { bins: rhoH, offset: offH } = rhoHistogram(pts, thetaH, w, h);
  const peaksV = findPeaks(rhoV, 8, 0.15);
  const peaksH = findPeaks(rhoH, 8, 0.15);
  if (peaksV.length < 2 || peaksH.length < 2) return { ok: false, reason: 'no grid families' };
  const periodX = robustPeriodFromPeaks(peaksV);
  const periodY = robustPeriodFromPeaks(peaksH);
  if (!(periodX > 2) || !(periodY > 2)) return { ok: false, reason: 'no robust period' };
  const phaseX = phaseFromPeaks(peaksV, periodX);
  const phaseY = phaseFromPeaks(peaksH, periodY);
  const vlines = [];
  for (let x = phaseX; x < w; x += Math.round(periodX)) vlines.push(Math.round(x));
  const hlines = [];
  for (let y = phaseY; y < h; y += Math.round(periodY)) hlines.push(Math.round(y));
  if (!vlines.length) {
    for (const p of peaksV) {
      const x = Math.round(((p - offV)) / Math.cos(thetaV));
      if (x >= 0 && x < w) vlines.push(x);
    }
    vlines.sort((a, b) => a - b);
  }
  if (!hlines.length) {
    for (const p of peaksH) {
      const y = Math.round(((p - offH)) / Math.sin(thetaH));
      if (y >= 0 && y < h) hlines.push(y);
    }
    hlines.sort((a, b) => a - b);
  }
  const { projX, projY } = projectionsFromGrad(gx, gy, w, h);
  const groutX = groutFromProjectionSmooth(projX, periodX, phaseX);
  const groutY = groutFromProjectionSmooth(projY, periodY, phaseY);
  const groutPx = Math.max(1, Math.round((groutX + groutY) / 2));
  return {
    ok: true,
    periodX,
    periodY,
    phaseX,
    phaseY,
    groutPx,
    thetaV,
    thetaH,
    vlines,
    hlines,
  };
}

function runPeriodicAnalysis(gray, w, h) {
  const grads = gradientFromGray(gray, w, h);
  const fft = analyzeFFTPeriod(gray, w, h);
  let best = null;
  if (fft.ok) {
    const grout = fft.groutPx || 1;
    const tile_w_px = Math.max(1, Math.round(fft.periodX - grout));
    const tile_h_px = Math.max(1, Math.round(fft.periodY - grout));
    const conf = confidenceByLines(fft.vlines, fft.hlines, grads.gx, grads.gy, w, h);
    best = {
      ok: true,
      tile_w_px,
      tile_h_px,
      grout_px: Math.max(1, Math.round(grout)),
      vlines: fft.vlines,
      hlines: fft.hlines,
      source: 'cv@fft-welch',
      periodX: fft.periodX,
      periodY: fft.periodY,
      phaseX: fft.phaseX,
      phaseY: fft.phaseY,
      confidence: conf,
      thetaV: 0,
      thetaH: Math.PI / 2,
    };
  }
  let hbest = null;
  if (!best || best.confidence < 0.35) {
    const hg = analyzeHoughGrid(gray, w, h);
    if (hg.ok) {
      const grout = Math.max(1, Math.round(hg.groutPx));
      const tile_w_px = Math.max(1, Math.round(hg.periodX - grout));
      const tile_h_px = Math.max(1, Math.round(hg.periodY - grout));
      const conf = confidenceByLines(hg.vlines, hg.hlines, grads.gx, grads.gy, w, h);
      hbest = {
        ok: true,
        tile_w_px,
        tile_h_px,
        grout_px: grout,
        vlines: hg.vlines,
        hlines: hg.hlines,
        source: 'cv@hough-grid',
        periodX: hg.periodX,
        periodY: hg.periodY,
        phaseX: hg.phaseX,
        phaseY: hg.phaseY,
        confidence: conf,
        thetaV: hg.thetaV,
        thetaH: hg.thetaH,
      };
    }
  }
  if (best && hbest) return best.confidence >= hbest.confidence ? best : hbest;
  if (best) return best;
  if (hbest) return hbest;
  return { ok: false, error: 'fft-fail; hough-fail' };
}

function refineGrid1D(lines, extent) {
  const xs = [...new Set(lines.filter((v) => Number.isFinite(v) && v >= 0 && v <= extent))].sort((a, b) => a - b);
  if (xs.length < 2) {
    return { lines: xs, T: 0, a: xs[0] || 0, conf: 0 };
  }
  const diffs = [];
  for (let i = 0; i < xs.length; i++) {
    for (let j = i + 1; j < Math.min(xs.length, i + 10); j++) diffs.push(xs[j] - xs[i]);
  }
  const fallbackT = xs.length > 1 ? xs[1] - xs[0] : 0;
  let T = histPeak(diffs, 1, 6, Math.max(12, Math.floor(extent / 2)));
  if (!(T > 0)) T = median(diffs) || fallbackT;
  const a = phaseFromModulo(xs, T);
  const grid = generateGrid(a, T, extent);
  const tol = Math.max(2, Math.round(0.1 * T));
  let inliers = 0;
  for (const x of xs) {
    if (grid.some((g) => Math.abs(g - x) <= tol)) inliers += 1;
  }
  return { lines: grid, T, a, conf: xs.length ? inliers / xs.length : 0 };
}

function histPeak(values, bin = 1, minT = 6, maxT = 1024) {
  const hist = new Map();
  for (const v of values) {
    if (!Number.isFinite(v) || v < minT || v > maxT) continue;
    const bucket = Math.round(v / bin) * bin;
    hist.set(bucket, (hist.get(bucket) || 0) + 1);
  }
  let best = minT;
  let count = -1;
  for (const [k, v] of hist) {
    if (v > count) {
      best = k;
      count = v;
    }
  }
  return best;
}

function phaseFromModulo(xs, T) {
  if (T <= 0) return 0;
  const remainders = xs.map((x) => ((x % T) + T) % T);
  return median(remainders);
}

function generateGrid(a, T, extent) {
  const result = [];
  if (T <= 0 || extent <= 0) return result;
  let k = Math.floor((0 - a) / T) - 1;
  for (let i = 0; i < 10000; i++) {
    const x = a + k * T;
    if (x > extent) break;
    if (x >= 0) result.push(Math.round(x));
    k += 1;
    if (result.length > 1) {
      const last = result[result.length - 1];
      const prev = result[result.length - 2];
      if (last - prev > 2 * extent) break;
    }
  }
  return Array.from(new Set(result)).sort((x, y) => x - y);
}

function refineGridRansac(w, h, vlines, hlines) {
  const vertical = refineGrid1D(vlines, w);
  const horizontal = refineGrid1D(hlines, h);
  return {
    vlines: vertical.lines,
    hlines: horizontal.lines,
    meta: {
      v: { T: vertical.T, a: vertical.a, conf: vertical.conf },
      h: { T: horizontal.T, a: horizontal.a, conf: horizontal.conf },
    },
  };
}

async function benchmarkImage(filePath, opts) {
  const prep = await loadImage(filePath, opts.maxSide);
  const current = analyzeProjectionPipeline(prep);
  const periodic = runPeriodicAnalysis(prep.gray, prep.workWidth, prep.workHeight);
  let periodicScaled = null;
  if (periodic.ok) {
    periodicScaled = {
      tile_w_px: Math.round(periodic.tile_w_px * prep.scaleX),
      tile_h_px: Math.round(periodic.tile_h_px * prep.scaleY),
      grout_px: Math.round(periodic.grout_px * Math.sqrt(prep.scaleX * prep.scaleY)),
      periodX: periodic.periodX * prep.scaleX,
      periodY: periodic.periodY * prep.scaleY,
      confidence: periodic.confidence,
      source: periodic.source,
      grid: {
        vlines: periodic.vlines.map((x) => Math.round(x * prep.scaleX)),
        hlines: periodic.hlines.map((y) => Math.round(y * prep.scaleY)),
      },
    };
  }
  let refinedCurrent = current.grid;
  let refinedPeriodic = periodicScaled ? periodicScaled.grid : null;
  if (opts.useRansac) {
    const rc = refineGridRansac(prep.srcWidth, prep.srcHeight, current.grid.vlines, current.grid.hlines);
    refinedCurrent = { vlines: rc.vlines, hlines: rc.hlines, meta: rc.meta };
    if (periodicScaled) {
      const rp = refineGridRansac(prep.srcWidth, prep.srcHeight, periodicScaled.grid.vlines, periodicScaled.grid.hlines);
      refinedPeriodic = { vlines: rp.vlines, hlines: rp.hlines, meta: rp.meta };
    }
  }
  return {
    image: path.basename(filePath),
    path: filePath,
    size: { width: prep.srcWidth, height: prep.srcHeight },
    current: {
      tile_w_px: current.tile_w_px,
      tile_h_px: current.tile_h_px,
      grout_px: current.grout_px,
      periodX: current.meta.perX,
      periodY: current.meta.perY,
      grid: refinedCurrent,
    },
    periodic: periodicScaled
      ? {
          ...periodicScaled,
          grid: refinedPeriodic,
        }
      : { ok: false },
  };
}

function parseArgs(argv) {
  const args = { folder: '../testimg', maxSide: DEFAULT_MAX_SIDE, useRansac: true };
  for (let i = 2; i < argv.length; i++) {
    const arg = argv[i];
    if (arg === '--folder' && argv[i + 1]) {
      args.folder = argv[++i];
    } else if (arg === '--max-side' && argv[i + 1]) {
      args.maxSide = Number(argv[++i]) || DEFAULT_MAX_SIDE;
    } else if (arg === '--no-ransac') {
      args.useRansac = false;
    } else if (arg === '--help' || arg === '-h') {
      args.help = true;
    }
  }
  return args;
}

async function main() {
  const args = parseArgs(process.argv);
  if (args.help) {
    console.log('Usage: node scripts/benchmark-analyzers.js [--folder path] [--max-side N] [--no-ransac]');
    process.exit(0);
  }
  const folder = path.resolve(process.cwd(), args.folder);
  const files = fs.readdirSync(folder)
    .filter((f) => /\.(png|jpg|jpeg)$/i.test(f))
    .map((f) => path.join(folder, f));
  if (!files.length) {
    console.error(`No images found in ${folder}`);
    process.exit(1);
  }
  const results = [];
  for (const file of files) {
    try {
      const res = await benchmarkImage(file, args);
      results.push(res);
    } catch (err) {
      console.error(`Failed on ${file}:`, err.message);
    }
  }
  console.log(JSON.stringify({ params: args, results }, null, 2));
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Youez - 2016 - github.com/yon3zu
LinuXploit