| 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/segmentation/ |
Upload File : |
import sharp from "sharp";
import { connectedComponents } from "@/lib/masks/components";
import { OccluderSegOptions, OccluderSegResult, inferOccluderMask } from "./occluder";
export type OccluderMethod = "u2net_refine" | "sam_hq" | "hybrid" | "distilled_unsup";
const DEFAULT_METHOD: OccluderMethod = "u2net_refine";
const SAM_HQ_DEFAULT_MODEL = "syscv-community/sam-hq-vit-base";
const SAM_HQ_DEFAULT_DEVICE = "cpu";
const SAM_HQ_DEFAULT_BAND_RADIUS = "10";
const SAM_HQ_DEFAULT_BOX_MARGIN = "32";
const SAM_HQ_DEFAULT_EDGE_WEIGHT = "0.7";
const SAM_HQ_DEFAULT_EDGE_RADIUS = "2";
const PATCH_REFINE_DEFAULT = "0";
function resolveMethod(input?: string | null): OccluderMethod {
const value = (input ?? "").trim().toLowerCase();
if (value === "sam_hq" || value === "sam-hq" || value === "sam_hq_prompted") return "sam_hq";
if (value === "hybrid") return "hybrid";
if (value === "distilled_unsup" || value === "distilled-unsup" || value === "distilled") return "distilled_unsup";
return "u2net_refine";
}
function normalizeBackend(input?: string | null): string {
const value = (input ?? "").trim().toLowerCase();
if (value === "rfdet_m3" || value === "comfy_rfdet_m3") return "rfdet_m3";
return "";
}
function buildSamHqEnv(baseEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const env: NodeJS.ProcessEnv = { ...(baseEnv ?? {}) };
env.OCCLUDER_SAM_HQ = "1";
if (!env.OCCLUDER_SAM_HQ_MODEL && !process.env.OCCLUDER_SAM_HQ_MODEL) {
env.OCCLUDER_SAM_HQ_MODEL = SAM_HQ_DEFAULT_MODEL;
}
if (!env.OCCLUDER_SAM_HQ_DEVICE && !process.env.OCCLUDER_SAM_HQ_DEVICE) {
env.OCCLUDER_SAM_HQ_DEVICE = SAM_HQ_DEFAULT_DEVICE;
}
if (!env.OCCLUDER_SAM_BAND_RADIUS) {
env.OCCLUDER_SAM_BAND_RADIUS = SAM_HQ_DEFAULT_BAND_RADIUS;
}
if (!env.OCCLUDER_SAM_BOX_MARGIN) {
env.OCCLUDER_SAM_BOX_MARGIN = SAM_HQ_DEFAULT_BOX_MARGIN;
}
if (!env.OCCLUDER_SAM_EDGE_WEIGHT) {
env.OCCLUDER_SAM_EDGE_WEIGHT = SAM_HQ_DEFAULT_EDGE_WEIGHT;
}
if (!env.OCCLUDER_SAM_EDGE_RADIUS) {
env.OCCLUDER_SAM_EDGE_RADIUS = SAM_HQ_DEFAULT_EDGE_RADIUS;
}
return env;
}
function coverageOf(mask: Uint8Array): number {
if (!mask || mask.length === 0) return 0;
let solid = 0;
for (let i = 0; i < mask.length; i += 1) {
if (mask[i] > 127) solid += 1;
}
return solid / mask.length;
}
function parseNumberEnv(name: string, fallback: number): number {
const raw = Number(process.env[name] ?? "");
return Number.isFinite(raw) ? raw : fallback;
}
function parseBool(value: string | undefined | null, fallback: boolean): boolean {
const normalized = (value ?? "").trim().toLowerCase();
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") return true;
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") return false;
return fallback;
}
function scoreCoverage(value: number, low = 0.15, high = 0.55): number {
if (value >= low && value <= high) return 0;
if (value < low) return low - value;
return value - high;
}
function meanBinaryTransitionsPerRow(mask: Uint8Array, width: number, height: number, threshold = 127): number {
if (!(width > 1 && height > 0)) return 0;
let sum = 0;
for (let y = 0; y < height; y += 1) {
const row = y * width;
let prev = mask[row] > threshold ? 1 : 0;
let transitions = 0;
for (let x = 1; x < width; x += 1) {
const next = mask[row + x] > threshold ? 1 : 0;
if (next !== prev) transitions += 1;
prev = next;
}
sum += transitions;
}
return sum / height;
}
function meanBinaryTransitionsPerCol(mask: Uint8Array, width: number, height: number, threshold = 127): number {
if (!(width > 0 && height > 1)) return 0;
let sum = 0;
for (let x = 0; x < width; x += 1) {
let prev = mask[x] > threshold ? 1 : 0;
let transitions = 0;
for (let y = 1; y < height; y += 1) {
const next = mask[y * width + x] > threshold ? 1 : 0;
if (next !== prev) transitions += 1;
prev = next;
}
sum += transitions;
}
return sum / width;
}
function orMasks(a: Uint8Array, b: Uint8Array): Uint8Array {
const len = Math.min(a.length, b.length);
const out = new Uint8Array(len);
for (let i = 0; i < len; i += 1) out[i] = (a[i] > 127 || b[i] > 127) ? 255 : 0;
return out;
}
function andMasks(a: Uint8Array, b: Uint8Array): Uint8Array {
const len = Math.min(a.length, b.length);
const out = new Uint8Array(len);
for (let i = 0; i < len; i += 1) out[i] = (a[i] > 127 && b[i] > 127) ? 255 : 0;
return out;
}
function dilateMask(mask: Uint8Array, width: number, height: number, radius: number): Uint8Array {
const r = Math.max(0, Math.round(radius));
if (r <= 0) return Uint8Array.from(mask);
const out = new Uint8Array(mask.length);
for (let y = 0; y < height; y += 1) {
for (let x = 0; x < width; x += 1) {
let solid = false;
for (let dy = -r; dy <= r && !solid; dy += 1) {
const ny = y + dy;
if (ny < 0 || ny >= height) continue;
const row = ny * width;
for (let dx = -r; dx <= r; dx += 1) {
const nx = x + dx;
if (nx < 0 || nx >= width) continue;
if (mask[row + nx] > 127) {
solid = true;
break;
}
}
}
out[y * width + x] = solid ? 255 : 0;
}
}
return out;
}
function erodeMask(mask: Uint8Array, width: number, height: number, radius: number): Uint8Array {
const r = Math.max(0, Math.round(radius));
if (r <= 0) return Uint8Array.from(mask);
const out = new Uint8Array(mask.length);
for (let y = 0; y < height; y += 1) {
for (let x = 0; x < width; x += 1) {
let all = true;
for (let dy = -r; dy <= r && all; dy += 1) {
const ny = y + dy;
if (ny < 0 || ny >= height) {
all = false;
break;
}
const row = ny * width;
for (let dx = -r; dx <= r; dx += 1) {
const nx = x + dx;
if (nx < 0 || nx >= width || mask[row + nx] <= 127) {
all = false;
break;
}
}
}
out[y * width + x] = all ? 255 : 0;
}
}
return out;
}
function boundaryMask(mask: Uint8Array, width: number, height: number): Uint8Array {
const dil = dilateMask(mask, width, height, 1);
const ero = erodeMask(mask, width, height, 1);
const out = new Uint8Array(mask.length);
for (let i = 0; i < out.length; i += 1) out[i] = (dil[i] > 127 && ero[i] <= 127) ? 255 : 0;
return out;
}
function quantile(values: Float32Array, q: number, stride = 4): number {
if (!values.length) return 0;
const picked: number[] = [];
for (let i = 0; i < values.length; i += Math.max(1, stride)) picked.push(values[i] ?? 0);
if (!picked.length) return 0;
picked.sort((a, b) => a - b);
const idx = Math.max(0, Math.min(picked.length - 1, Math.floor((picked.length - 1) * Math.max(0, Math.min(1, q)))));
return picked[idx] ?? 0;
}
async function decodeGray(raw: Buffer, width: number, height: number): Promise<Float32Array> {
const out = await sharp(raw, { failOnError: false })
.removeAlpha()
.greyscale()
.resize(width, height, { fit: "fill", kernel: "lanczos3" })
.raw()
.toBuffer();
const gray = new Float32Array(out.length);
for (let i = 0; i < out.length; i += 1) gray[i] = out[i] ?? 0;
return gray;
}
type ProxyCrop = {
rank: number;
bbox: { x: number; y: number; w: number; h: number };
score: number;
reasonTags: string[];
};
function buildProxyPatchPlan(
baseMask: Uint8Array,
width: number,
height: number,
gray: Float32Array,
tileMask: Uint8Array,
): { focusMask: Uint8Array; crops: ProxyCrop[]; stats: Record<string, number> } {
const total = Math.max(1, width * height);
const tileEdge = boundaryMask(tileMask, width, height);
const baseEdge = boundaryMask(baseMask, width, height);
const baseEdgeWide = dilateMask(baseEdge, width, height, 4);
const tileEdgeWide = dilateMask(tileEdge, width, height, 2);
const outsideRing = andMasks(dilateMask(baseMask, width, height, 10), invertMask(baseMask));
const gx = new Float32Array(total);
const gy = new Float32Array(total);
const mag = new Float32Array(total);
for (let y = 1; y < height - 1; y += 1) {
for (let x = 1; x < width - 1; x += 1) {
const idx = y * width + x;
const gxh = (gray[idx + 1] ?? 0) - (gray[idx - 1] ?? 0);
const gyh = (gray[idx + width] ?? 0) - (gray[idx - width] ?? 0);
gx[idx] = gxh;
gy[idx] = gyh;
mag[idx] = Math.hypot(gxh, gyh);
}
}
const magQ = quantile(mag, 0.82);
const spill = new Uint8Array(total);
const texture = new Uint8Array(total);
for (let i = 0; i < total; i += 1) {
const inBase = baseMask[i] > 127;
const atBaseEdge = baseEdgeWide[i] > 127;
const atTileEdge = tileEdgeWide[i] > 127;
const outRing = outsideRing[i] > 127;
const m = mag[i] ?? 0;
if (inBase && atBaseEdge && atTileEdge) spill[i] = 255;
if (outRing && atTileEdge && m >= magQ) texture[i] = 255;
}
const thinMask = new Uint8Array(total);
const spillish = andMasks(baseMask, tileEdgeWide);
const spillComps = connectedComponents(spillish, width, height, 24);
for (const comp of spillComps) {
let minX = width;
let minY = height;
let maxX = 0;
let maxY = 0;
for (const idx of comp.pixels) {
const y = Math.floor(idx / width);
const x = idx - y * width;
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
const bw = Math.max(1, maxX - minX + 1);
const bh = Math.max(1, maxY - minY + 1);
const fill = comp.area / Math.max(1, bw * bh);
if (Math.min(bw, bh) <= 4 || fill < 0.22 || comp.area < 180) {
for (const idx of comp.pixels) thinMask[idx] = 255;
}
}
const heat = new Float32Array(total);
for (let i = 0; i < total; i += 1) {
const s = spill[i] > 127 ? 1 : 0;
const t = texture[i] > 127 ? 1 : 0;
const thin = thinMask[i] > 127 ? 1 : 0;
heat[i] = 0.52 * s + 0.30 * t + 0.18 * thin;
}
const heatThreshold = quantile(heat, 0.84, 2);
const hot = new Uint8Array(total);
for (let i = 0; i < total; i += 1) hot[i] = heat[i] >= Math.max(0.12, heatThreshold) ? 255 : 0;
const hotDil = dilateMask(hot, width, height, 1);
const comps = connectedComponents(hotDil, width, height, 36);
const ranked = comps
.map((comp) => {
let minX = width;
let minY = height;
let maxX = 0;
let maxY = 0;
let spillHits = 0;
let textureHits = 0;
let thinHits = 0;
let score = 0;
for (const idx of comp.pixels) {
const y = Math.floor(idx / width);
const x = idx - y * width;
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
if (spill[idx] > 127) spillHits += 1;
if (texture[idx] > 127) textureHits += 1;
if (thinMask[idx] > 127) thinHits += 1;
score += heat[idx] ?? 0;
}
const avgScore = score / Math.max(1, comp.area);
const tags: string[] = [];
if (spillHits / Math.max(1, comp.area) > 0.12) tags.push("tile_edge_overlap");
if (textureHits / Math.max(1, comp.area) > 0.10) tags.push("texture_mismatch");
if (thinHits / Math.max(1, comp.area) > 0.08) tags.push("thin_strip_or_island");
if (!tags.length) tags.push("mixed_proxy");
return {
comp,
score: avgScore * Math.sqrt(comp.area),
bbox: { x: minX, y: minY, w: Math.max(1, maxX - minX + 1), h: Math.max(1, maxY - minY + 1) },
tags,
};
})
.sort((a, b) => b.score - a.score);
const topN = Math.max(1, Math.min(16, Math.round(parseNumberEnv("OCCLUDER_PATCH_TOP_N", 10))));
const selected = ranked.slice(0, topN);
const focus = new Uint8Array(total);
const crops: ProxyCrop[] = [];
selected.forEach((entry, idx) => {
for (const px of entry.comp.pixels) focus[px] = 255;
crops.push({
rank: idx + 1,
bbox: entry.bbox,
score: Number(entry.score.toFixed(4)),
reasonTags: entry.tags,
});
});
const focusDil = dilateMask(focus, width, height, 3);
const spillCov = coverageOf(spill);
const textureCov = coverageOf(texture);
const thinCov = coverageOf(thinMask);
return {
focusMask: focusDil,
crops,
stats: {
heatThreshold: Number(heatThreshold.toFixed(4)),
selectedCount: crops.length,
spillCoverage: Number(spillCov.toFixed(6)),
textureCoverage: Number(textureCov.toFixed(6)),
thinCoverage: Number(thinCov.toFixed(6)),
},
};
}
function invertMask(mask: Uint8Array): Uint8Array {
const out = new Uint8Array(mask.length);
for (let i = 0; i < mask.length; i += 1) out[i] = mask[i] > 127 ? 0 : 255;
return out;
}
function mergePatchCandidate(
base: Uint8Array,
patch: Uint8Array,
focus: Uint8Array,
tileMask: Uint8Array | null,
width: number,
height: number,
): Uint8Array {
const baseB = base;
const patchB = patch;
const focusDil = dilateMask(focus, width, height, 8);
const edgeBand = dilateMask(boundaryMask(baseB, width, height), width, height, 7);
const core = erodeMask(baseB, width, height, 3);
const tileEdge = tileMask ? dilateMask(boundaryMask(tileMask, width, height), width, height, 2) : null;
const out = Uint8Array.from(baseB);
for (let i = 0; i < out.length; i += 1) {
const inBase = baseB[i] > 127;
const inPatch = patchB[i] > 127;
const inFocus = focusDil[i] > 127;
const inEdge = edgeBand[i] > 127;
const inCore = core[i] > 127;
const tileLeakRisk = tileEdge ? (tileEdge[i] > 127 && !inFocus) : false;
if (!inBase && inPatch && (inFocus || inEdge) && !tileLeakRisk) out[i] = 255;
if (inBase && !inPatch && inFocus && !inCore) out[i] = 0;
}
return out;
}
async function alignTileMask(
tileMask: Uint8Array | null | undefined,
width: number | null | undefined,
height: number | null | undefined,
targetW: number,
targetH: number,
): Promise<Uint8Array | null> {
if (!tileMask || !width || !height || width <= 0 || height <= 0) return null;
if (tileMask.length !== width * height) return null;
if (width === targetW && height === targetH) return tileMask;
const resized = await sharp(Buffer.from(tileMask), { raw: { width, height, channels: 1 } })
.resize(targetW, targetH, { kernel: "nearest", fit: "fill" })
.threshold(128)
.raw()
.toBuffer();
return new Uint8Array(resized);
}
function analyzeMaskQuality(
mask: Uint8Array,
width: number,
height: number,
tileMask?: Uint8Array | null,
): { score: number; coverage: number; transitionDensity: number; tileLeak: number; largestCompRatio: number } {
const total = Math.max(1, width * height);
let area = 0;
let tileHit = 0;
const hasTile = Boolean(tileMask && tileMask.length === mask.length);
for (let i = 0; i < mask.length; i += 1) {
if (mask[i] > 127) {
area += 1;
if (hasTile && (tileMask![i] > 127)) tileHit += 1;
}
}
const coverage = area / total;
const rowT = meanBinaryTransitionsPerRow(mask, width, height);
const colT = meanBinaryTransitionsPerCol(mask, width, height);
const transitionDensity = ((rowT / Math.max(1, width)) + (colT / Math.max(1, height))) * 0.5;
const tileLeak = area > 0 ? tileHit / area : 0;
const comps = connectedComponents(mask, width, height, 1);
let largest = 0;
for (const comp of comps) largest = Math.max(largest, comp.area);
const largestCompRatio = area > 0 ? largest / area : 0;
const minCoverage = parseNumberEnv("OCCLUDER_DISTILL_MIN_COVERAGE", 0.08);
const maxCoverage = parseNumberEnv("OCCLUDER_DISTILL_MAX_COVERAGE", 0.62);
const maxTransitionDensity = parseNumberEnv("OCCLUDER_DISTILL_MAX_TRANS_DENSITY", 0.18);
const maxTileLeak = parseNumberEnv("OCCLUDER_DISTILL_MAX_TILE_LEAK", 0.78);
let score = 100;
score -= 150 * Math.max(0, coverage - maxCoverage);
score -= 120 * Math.max(0, minCoverage - coverage);
score -= 120 * Math.max(0, transitionDensity - maxTransitionDensity);
if (hasTile) score -= 55 * Math.max(0, tileLeak - maxTileLeak);
score -= 28 * Math.max(0, 0.18 - largestCompRatio);
score = Math.max(0, Math.min(100, score));
return {
score: Number(score.toFixed(4)),
coverage: Number(coverage.toFixed(6)),
transitionDensity: Number(transitionDensity.toFixed(6)),
tileLeak: Number(tileLeak.toFixed(6)),
largestCompRatio: Number(largestCompRatio.toFixed(6)),
};
}
function mergeDistilledMasks(
base: Uint8Array,
sam: Uint8Array,
width: number,
height: number,
tileMask?: Uint8Array | null,
): Uint8Array {
const total = width * height;
const union = orMasks(base, sam);
const core = dilateMask(andMasks(base, sam), width, height, 4);
const bottomStart = Math.floor(height * (1 - parseNumberEnv("OCCLUDER_DISTILL_BOTTOM_STRIP_FRAC", 0.18)));
const minArea = Math.max(64, Math.floor(total * parseNumberEnv("OCCLUDER_DISTILL_MIN_COMP_FRAC", 0.00012)));
const bigArea = Math.max(minArea * 3, Math.floor(total * parseNumberEnv("OCCLUDER_DISTILL_BIG_COMP_FRAC", 0.015)));
const out = new Uint8Array(union.length);
const comps = connectedComponents(union, width, height, minArea);
for (const comp of comps) {
let touchCore = false;
let touchBottom = false;
let tileHits = 0;
for (const idx of comp.pixels) {
if (core[idx] > 127) touchCore = true;
const y = Math.floor(idx / width);
if (y >= bottomStart) touchBottom = true;
if (tileMask && tileMask.length === union.length && tileMask[idx] > 127) tileHits += 1;
if (touchCore && touchBottom) break;
}
const tileRatio = tileMask && tileMask.length === union.length ? tileHits / Math.max(1, comp.area) : 0;
const keep = touchCore || touchBottom || comp.area >= bigArea || (comp.area >= minArea * 2 && tileRatio < 0.92);
if (!keep) continue;
for (const idx of comp.pixels) out[idx] = 255;
}
return out;
}
export async function inferOccluderBest(
raw: Buffer,
options: OccluderSegOptions = {},
): Promise<OccluderSegResult> {
const envMethod = options.env?.OCCLUDER_METHOD ?? process.env.OCCLUDER_METHOD;
const backend = normalizeBackend(
options.env?.OCCLUDER_BACKEND ??
options.env?.OCCLUDER_PRESET ??
process.env.OCCLUDER_BACKEND ??
process.env.OCCLUDER_PRESET,
);
let method = resolveMethod(envMethod);
if (backend === "rfdet_m3" && method === "u2net_refine") {
method = "distilled_unsup";
}
if (method === "sam_hq") {
// NOTE: current pipeline only supports SAM prompted mode via occluder_segment.py.
const env = buildSamHqEnv(options.env);
return inferOccluderMask(raw, { ...options, samPrompted: true, env });
}
if (method === "hybrid") {
const base = await inferOccluderMask(raw, { ...options, samPrompted: false });
if (!options.tileMask || !options.tileMaskWidth || !options.tileMaskHeight) {
return base;
}
const baseCoverage = coverageOf(base.mask);
if (baseCoverage >= 0.12 && baseCoverage <= 0.65) {
return base;
}
const sam = await inferOccluderMask(raw, { ...options, samPrompted: true });
const samCoverage = coverageOf(sam.mask);
const baseScore = scoreCoverage(baseCoverage);
const samScore = scoreCoverage(samCoverage);
return samScore <= baseScore ? sam : base;
}
if (method === "distilled_unsup") {
const base = await inferOccluderMask(raw, { ...options, samPrompted: false });
const alignedTileMask = await alignTileMask(
options.tileMask ?? null,
options.tileMaskWidth ?? null,
options.tileMaskHeight ?? null,
base.width,
base.height,
);
const candidates: Array<{ name: string; result: OccluderSegResult }> = [{ name: "base", result: base }];
let samError: string | null = null;
if (alignedTileMask) {
try {
const sam = await inferOccluderMask(raw, {
...options,
samPrompted: true,
tileMask: alignedTileMask,
tileMaskWidth: base.width,
tileMaskHeight: base.height,
});
candidates.push({ name: "sam_prompted", result: sam });
const mergedMask = mergeDistilledMasks(base.mask, sam.mask, base.width, base.height, alignedTileMask);
candidates.push({
name: "distilled_merge",
result: {
mask: mergedMask,
width: base.width,
height: base.height,
source: "distilled_unsup_merge",
meta: {
baseSource: base.source,
samSource: sam.source,
},
},
});
} catch (err) {
samError = err instanceof Error ? err.message : String(err);
}
}
const analyzed = candidates.map((entry) => ({
...entry,
proxy: analyzeMaskQuality(entry.result.mask, entry.result.width, entry.result.height, alignedTileMask),
}));
analyzed.sort((a, b) => b.proxy.score - a.proxy.score);
let winner = analyzed[0]!;
const minProxy = parseNumberEnv("OCCLUDER_DISTILL_MIN_PROXY", 42);
if (winner.proxy.score < minProxy) {
const baseEval = analyzed.find((x) => x.name === "base");
if (baseEval) winner = baseEval;
}
const patchRefineEnabled = parseBool(
options.env?.OCCLUDER_PATCH_REFINE ??
process.env.OCCLUDER_PATCH_REFINE ??
PATCH_REFINE_DEFAULT,
false,
);
let patchRefineMeta: Record<string, unknown> | null = null;
if (patchRefineEnabled && alignedTileMask && winner.result.width > 0 && winner.result.height > 0) {
try {
const gray = await decodeGray(raw, winner.result.width, winner.result.height);
const proxyPlan = buildProxyPatchPlan(
winner.result.mask,
winner.result.width,
winner.result.height,
gray,
alignedTileMask,
);
if (proxyPlan.crops.length > 0 && coverageOf(proxyPlan.focusMask) > 0.001) {
const patchEnv = buildSamHqEnv({
...(options.env ?? {}),
OCCLUDER_SAM_POS_INSIDE: "1",
OCCLUDER_SAM_POS_BAND_ONLY: "0",
OCCLUDER_SAM_TOP_K: options.env?.OCCLUDER_SAM_TOP_K ?? "2",
});
const patchResult = await inferOccluderMask(raw, {
...options,
samPrompted: true,
tileMask: proxyPlan.focusMask,
tileMaskWidth: winner.result.width,
tileMaskHeight: winner.result.height,
env: patchEnv,
});
const mergedPatch = mergePatchCandidate(
winner.result.mask,
patchResult.mask,
proxyPlan.focusMask,
alignedTileMask,
winner.result.width,
winner.result.height,
);
const mergedProxy = analyzeMaskQuality(mergedPatch, winner.result.width, winner.result.height, alignedTileMask);
const guardDelta = mergedProxy.score - winner.proxy.score;
const allowPatch = guardDelta >= -1.5;
patchRefineMeta = {
enabled: true,
accepted: allowPatch,
scoreDelta: Number(guardDelta.toFixed(4)),
proxyStats: proxyPlan.stats,
proxyCrops: proxyPlan.crops,
patchSource: patchResult.source,
};
if (allowPatch) {
winner = {
name: "patch_refined",
result: {
...winner.result,
mask: mergedPatch,
source: `${winner.result.source}+patch_refined`,
meta: {
...(winner.result.meta ?? {}),
patchRefine: patchRefineMeta,
},
},
proxy: mergedProxy,
};
}
} else {
patchRefineMeta = {
enabled: true,
accepted: false,
reason: "proxy_targets_empty",
proxyStats: proxyPlan.stats,
};
}
} catch (err) {
patchRefineMeta = {
enabled: true,
accepted: false,
error: err instanceof Error ? err.message : String(err),
};
}
}
return {
...winner.result,
source: `${winner.result.source}+distilled_unsup`,
meta: {
...(winner.result.meta ?? {}),
distilled: {
method: "distilled_unsup",
backend: backend || null,
selected: winner.name,
minProxy,
proxy: winner.proxy,
candidates: analyzed.map((x) => ({
name: x.name,
source: x.result.source,
proxy: x.proxy,
})),
samPromptedAttempted: Boolean(alignedTileMask),
samPromptedError: samError ?? undefined,
patchRefine: patchRefineMeta ?? undefined,
},
},
};
}
return inferOccluderMask(raw, { ...options, samPrompted: false });
}
export function getOccluderMethod(): OccluderMethod {
const backend = normalizeBackend(process.env.OCCLUDER_BACKEND ?? process.env.OCCLUDER_PRESET);
if (backend === "rfdet_m3" && !process.env.OCCLUDER_METHOD) {
return "distilled_unsup";
}
return resolveMethod(process.env.OCCLUDER_METHOD) ?? DEFAULT_METHOD;
}