403Webshell
Server IP : 217.154.3.148  /  Your IP : 216.73.216.90
Web Server : Apache
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/lib/analyzers/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/vhosts/gracious-boyd.217-154-3-148.plesk.page/nextjs/lib/analyzers/fft_period.ts
/**
 * Robuste FFT-Periodenschätzung für Fliesen:
 * - Welch-Averaging (überlappende Segmente) -> weniger Rauschen/Verdeckungen
 * - Hann-Fenster + Zero-Padding zur nächsten 2er-Potenz (fft.js-Anforderung)
 * - Subpixel-Peak (parabolische Interpolation)
 * - Phase über Kamm-Summe (Comb-Score)
 * - Fugenbreite via FWHM an Peakpositionen der Projektion
 */
import FFT from 'fft.js';

type FftOk = {
  ok: true;
  periodX: number; periodY: number;
  phaseX: number;  phaseY: number;
  groutPx: number;
  vlines: number[]; hlines: number[];
};
type FftErr = { ok:false; reason:string };
type FftOpts = {
  debug?: boolean;
  weights?: Float32Array | null;
};

function clamp(v:number, a:number, b:number){ return Math.max(a, Math.min(b, v)); }
function nextPow2(n:number){ return n<=1 ? 2 : 1<<Math.ceil(Math.log2(n)); }

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

function realFFTmag(inpReal: Float64Array){
  let n = inpReal.length|0;
  // Defensive: ensure FFT size is a power of two and >= 2
  const isPow2 = (x:number)=> x>1 && (x & (x-1)) === 0;
  if (!isPow2(n)) {
    const n2 = nextPow2(Math.max(2, n));
    const buf = new Float64Array(n2);
    buf.set(inpReal.subarray(0, Math.min(n2, n)));
    inpReal = buf;
    n = n2;
  }
  const fft = new FFT(n);
  const out = fft.createComplexArray() as unknown as Float64Array;
  fft.realTransform(out, inpReal);
  fft.completeSpectrum(out);
  const mags = new Float64Array(n/2 + 1);
  for (let k=0;k<=n/2;k++){
    const re = out[2*k] || 0, im = out[2*k+1] || 0;
    mags[k] = Math.hypot(re, im);
  }
  return mags;
}

/** Welch: mittelt Spektren aus überlappenden Segmenten gleicher Länge. */
function welchAveragedMag(proj: Float32Array, 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);                       // für FFT
  const hop = Math.max(1, Math.floor(segLen * (1-overlap)));
  const numStarts = Math.max(1, Math.floor((N - segLen) / hop) + 1);

  const avg = new Float64Array(segLen/2 + 1);
  const buf = new Float64Array(segLen);

  let used = 0;
  for (let s=0; s<numStarts; s++){
    const start = s*hop;
    if (start+segLen > N) break;
    hannFill(buf, proj, start, segLen);
    const mag = realFFTmag(buf);
    for (let k=0;k<mag.length;k++) avg[k]+=mag[k];
    used++;
  }
  if (used===0){
    // Fallback: einmal über gesamtes Signal
    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 };
}

/** Subpixel: Parabel um (k-1,k,k+1) -> Versatz delta in [-0.5,0.5] */
function subpixelPeak(mags: Float64Array, k:number){
  const a = mags[k-1] ?? 0, b = mags[k] ?? 0, c = mags[k+1] ?? 0;
  const denom = (a - 2*b + c);
  if (denom === 0) return 0;
  const delta = 0.5 * (a - c) / denom;
  return clamp(delta, -0.5, 0.5);
}

function combScore(signal: Float32Array, P:number, off:number){
  const N = signal.length;
  const step = Math.max(2, Math.round(P));
  let s=0, cnt=0;
  for (let k=off; k<N; k+=step){
    const i = Math.round(k);
    const i0 = clamp(i-1,0,N-1), i1=i, i2=clamp(i+1,0,N-1);
    s += Math.max(signal[i0], signal[i1], signal[i2]);
    cnt++;
  }
  return cnt ? s/cnt : 0;
}
function bestPhase(signal: Float32Array, P:number){
  const Pi = Math.max(2, Math.round(P));
  let bestOff=0, bestS=-Infinity;
  for (let off=0; off<Pi; off++){
    const s = combScore(signal, P, off);
    if (s>bestS){ bestS=s; bestOff=off; }
  }
  return bestOff;
}

/** FWHM um Peaks herum -> typische Fugenbreite (Median aus Stichproben) */
function fwhmAt(signal: Float32Array, center: number){
  const N = signal.length;
  const ic = clamp(Math.round(center), 0, N-1);
  const peak = signal[ic];
  if (!(peak>0)) return 1;

  const half = peak/2;
  let L=ic, R=ic;
  while (L>0 && signal[L] > half) L--;
  while (R<N-1 && signal[R] > half) R++;
  return Math.max(1, R - L);
}
function groutFromProjection(signal: Float32Array, P: number, phase: number, samples=8){
  const N = signal.length;
  const step = Math.max(2, Math.round(P));
  const widths:number[] = [];
  for (let n=0; n<samples; n++){
    const x = phase + n*step;
    if (x>=N) break;
    widths.push(fwhmAt(signal, x));
  }
  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 findPeriodWithWelch(proj: Float32Array, 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 as const, reason:'no valid k-range', P:NaN, k:NaN };

  let k0 = kMin, 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 as const, reason:'no peak', P:NaN, k:NaN };

  // Subpixel verfeinern
  const delta = subpixelPeak(mags, clamp(k0, kMin+1, kMax-1));
  const kRef = k0 + delta;
  const P = nFFT / kRef;
  return { ok:true as const, P, k: kRef };
}

/** Gradienten-Projektionen (|gx| für vertikale Linien, |gy| für horizontale). */
function gradientProjections(gray: Float32Array, w:number, h:number, weights?: Float32Array | null){
  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 weight = weights ? weights[i] ?? 1 : 1;
      const gx = (gray[i+1]-gray[i-1]) * weight;
      const gy = (gray[i+w]-gray[i-w]) * weight;
      projX[x]+=Math.abs(gx);
      projY[y]+=Math.abs(gy);
    }
  }
  // leichte Glättung (3-Tap)
  const smooth = (a: Float32Array) => {
    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) };
}

export function analyzeFFTPeriod(
  gray: Float32Array,
  w: number,
  h: number,
  opts?: FftOpts
): FftOk|FftErr {
  try{
    if (!Number.isFinite(w) || !Number.isFinite(h) || w<8 || h<8) {
      return { ok:false, reason:'image too small/invalid' };
    }

    const { projX, projY } = gradientProjections(gray, w, h, opts?.weights ?? null);

    const minP=8, 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 = bestPhase(projX, fx.P);
    const phaseY = bestPhase(projY, fy.P);

    // Fugenbreite aus FWHM der Peakform (Median)
    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:number[]=[]; for (let x=phaseX; x<w; x+=stepX) vlines.push(Math.round(x));
    const hlines:number[]=[]; 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
    };
  }catch(e:any){
    return { ok:false, reason: e?.message || String(e) };
  }
}

Youez - 2016 - github.com/yon3zu
LinuXploit