| 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/masks/ |
Upload File : |
/**
* Component analysis and seed-driven filtering for mask refinement
*/
type Component = {
pixels: number[];
area: number;
minX: number;
maxX: number;
minY: number;
maxY: number;
};
/**
* Find connected components in a binary mask (4-connectivity)
*/
export function connectedComponents(
mask: Uint8Array,
width: number,
height: number,
minArea = 1
): Component[] {
const visited = new Uint8Array(mask.length);
const queue = new Int32Array(mask.length);
const components: Component[] = [];
for (let i = 0; i < mask.length; i++) {
if (!mask[i] || visited[i]) continue;
const pixels: number[] = [];
let head = 0;
let tail = 0;
queue[tail++] = i;
visited[i] = 1;
let minX = width - 1;
let maxX = 0;
let minY = height - 1;
let maxY = 0;
while (head < tail) {
const current = queue[head++];
pixels.push(current);
const x = current % width;
const y = Math.floor(current / width);
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
const tryPush = (idx: number) => {
if (!mask[idx] || visited[idx]) return;
visited[idx] = 1;
queue[tail++] = idx;
};
if (x > 0) tryPush(current - 1);
if (x + 1 < width) tryPush(current + 1);
if (y > 0) tryPush(current - width);
if (y + 1 < height) tryPush(current + width);
}
if (pixels.length >= minArea) {
components.push({ pixels, area: pixels.length, minX, maxX, minY, maxY });
}
}
return components;
}
/**
* Calculate intersection over union between a component and a seed mask
*/
export function componentIoU(comp: Component, seedMask: Uint8Array): number {
let intersection = 0;
let compArea = 0;
let seedArea = 0;
for (const idx of comp.pixels) {
compArea++;
if (seedMask[idx]) intersection++;
}
// Count seed pixels in bounding box
const bbox = {
x1: comp.minX,
y1: comp.minY,
x2: comp.maxX,
y2: comp.maxY,
};
const width = seedMask.length; // will be corrected by caller
for (let y = bbox.y1; y <= bbox.y2; y++) {
for (let x = bbox.x1; x <= bbox.x2; x++) {
const idx = y * width + x;
if (seedMask[idx]) seedArea++;
}
}
const union = compArea + seedArea - intersection;
return union > 0 ? intersection / union : 0;
}
/**
* Keep only components that have sufficient overlap with seed mask
*/
export function keepSeedDrivenComponents(
mask: Uint8Array,
seedMask: Uint8Array,
width: number,
height: number,
minIoU = 0.15,
minArea = 1200
): Uint8Array {
const components = connectedComponents(mask, width, height, minArea);
const output = new Uint8Array(mask.length);
for (const comp of components) {
if (comp.area < minArea) continue;
// Quick overlap check first
let hasOverlap = false;
for (const idx of comp.pixels) {
if (seedMask[idx]) {
hasOverlap = true;
break;
}
}
if (!hasOverlap) continue;
// Calculate IoU
let intersection = 0;
for (const idx of comp.pixels) {
if (seedMask[idx]) intersection++;
}
// Simple IoU approximation: intersection / component area
// (since seed mask may be much larger, we use component as denominator)
const iou = intersection / comp.area;
if (iou >= minIoU) {
for (const idx of comp.pixels) {
output[idx] = 255;
}
}
}
return output;
}
/**
* Distance to nearest lattice line (grout)
*/
export function distanceToNearestLattice(
width: number,
height: number,
vlines: number[],
hlines: number[],
bandPx: number
): Float32Array {
const size = width * height;
const dist = new Float32Array(size);
dist.fill(Infinity);
// Seed radius: keep the core thin so occluder pixels just outside the grout
// band still receive a > 0 distance, but widen slightly for very thick grout.
const seedRadius = Math.max(0, Math.floor(Math.max(bandPx - 1, 0) / 6));
const queue = new Int32Array(size);
let head = 0;
let tail = 0;
const enqueue = (idx: number) => {
if (idx < 0 || idx >= size) return;
if (dist[idx] !== Infinity) return;
dist[idx] = 0;
queue[tail++] = idx;
};
const enqueueColumn = (x: number) => {
const clampedX = Math.max(0, Math.min(width - 1, Math.round(x)));
for (let dx = -seedRadius; dx <= seedRadius; dx++) {
const xx = clampedX + dx;
if (xx < 0 || xx >= width) continue;
for (let y = 0; y < height; y++) {
enqueue(y * width + xx);
}
}
};
const enqueueRow = (y: number) => {
const clampedY = Math.max(0, Math.min(height - 1, Math.round(y)));
for (let dy = -seedRadius; dy <= seedRadius; dy++) {
const yy = clampedY + dy;
if (yy < 0 || yy >= height) continue;
const rowOffset = yy * width;
for (let x = 0; x < width; x++) {
enqueue(rowOffset + x);
}
}
};
for (const v of vlines) enqueueColumn(v);
for (const h of hlines) enqueueRow(h);
// In the unlikely case no lines were enqueued (invalid grid), fall back to
// filling the queue with border pixels so the transform still terminates.
if (tail === 0) {
for (let x = 0; x < width; x++) {
enqueue(x);
enqueue((height - 1) * width + x);
}
for (let y = 0; y < height; y++) {
enqueue(y * width);
enqueue(y * width + (width - 1));
}
}
while (head < tail) {
const idx = queue[head++];
const base = dist[idx] + 1;
const x = idx % width;
const y = (idx / width) | 0;
if (x > 0) {
const left = idx - 1;
if (base < dist[left]) {
dist[left] = base;
queue[tail++] = left;
}
}
if (x + 1 < width) {
const right = idx + 1;
if (base < dist[right]) {
dist[right] = base;
queue[tail++] = right;
}
}
if (y > 0) {
const up = idx - width;
if (base < dist[up]) {
dist[up] = base;
queue[tail++] = up;
}
}
if (y + 1 < height) {
const down = idx + width;
if (base < dist[down]) {
dist[down] = base;
queue[tail++] = down;
}
}
}
const fallbackDistance = width + height;
for (let i = 0; i < size; i++) {
if (dist[i] === Infinity) dist[i] = fallbackDistance;
}
return dist;
}
/**
* Suppress leaks deep in tile interior using lattice awareness with hysteresis
*/
export function suppressTileLeak(
mask: Uint8Array,
probMap: Uint8Array,
width: number,
height: number,
vlines: number[],
hlines: number[],
bandPx: number,
threshold = 148 // ~0.58 * 255 (base threshold)
): Uint8Array {
const enabled = (process.env.OCCLUDER_SUPPRESS_TILE_LEAK ?? "1") !== "0";
if (!enabled) {
return Uint8Array.from(mask);
}
const output = Uint8Array.from(mask);
const dist = distanceToNearestLattice(width, height, vlines, hlines, bandPx);
const band = Math.max(4, bandPx);
// Hysteresis: strict near grout, lenient deep inside
const tauNear = threshold; // ~0.58 * 255 = 148
const tauDeep = Math.max(112, threshold - 20); // ~0.44 * 255, more tolerant
for (let i = 0; i < mask.length; i++) {
if (!mask[i]) continue;
const d = dist[i];
// Near grout: strict threshold
if (d <= band * 1.2) {
if (probMap[i] < tauNear) {
output[i] = 0;
}
}
// Deep in tile interior: lenient threshold
else if (d >= band * 3) {
if (probMap[i] < tauDeep) {
output[i] = 0;
}
}
// Intermediate zone: use base threshold
else {
if (probMap[i] < threshold) {
output[i] = 0;
}
}
}
return output;
}
/**
* Calculate overlap ratio between mask and seeds
*/
export function overlapWithSeeds(mask: Uint8Array, seeds: Uint8Array): number {
let maskCount = 0;
let overlapCount = 0;
for (let i = 0; i < mask.length; i++) {
if (mask[i]) {
maskCount++;
if (seeds[i]) overlapCount++;
}
}
return maskCount > 0 ? overlapCount / maskCount : 0;
}