2026-05-24-15-37-16 修正mask线条桥接与DICOM复位入口

This commit is contained in:
2026-05-24 15:47:59 +08:00
parent f279770a0e
commit e9f0823281
7 changed files with 515 additions and 86 deletions

View File

@@ -1045,6 +1045,68 @@ function addExportSegmentToRows(rows: number[][], width: number, height: number,
}
}
function groupExportSegmentsByConnectivity(segments: PlaneSegmentRecord[], tolerance = 1.35) {
if (segments.length <= 1) {
return segments.length ? [segments] : [];
}
const parents = segments.map((_, index) => index);
const find = (index: number): number => {
if (parents[index] !== index) {
parents[index] = find(parents[index]);
}
return parents[index];
};
const union = (left: number, right: number) => {
const leftRoot = find(left);
const rightRoot = find(right);
if (leftRoot !== rightRoot) {
parents[rightRoot] = leftRoot;
}
};
const buckets = new Map<string, Array<{ x: number; y: number; index: number }>>();
const cellSize = Math.max(tolerance, 0.1);
const toleranceSquared = tolerance * tolerance;
const cellKey = (x: number, y: number) => `${x},${y}`;
segments.forEach((segment, index) => {
[segment.a, segment.b].forEach((point) => {
if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {
return;
}
const cellX = Math.floor(point.x / cellSize);
const cellY = Math.floor(point.y / cellSize);
for (let dx = -1; dx <= 1; dx += 1) {
for (let dy = -1; dy <= 1; dy += 1) {
const candidates = buckets.get(cellKey(cellX + dx, cellY + dy));
candidates?.forEach((candidate) => {
const distanceSquared = (candidate.x - point.x) ** 2 + (candidate.y - point.y) ** 2;
if (distanceSquared <= toleranceSquared) {
union(index, candidate.index);
}
});
}
}
const key = cellKey(cellX, cellY);
const bucket = buckets.get(key) ?? [];
bucket.push({ x: point.x, y: point.y, index });
buckets.set(key, bucket);
});
});
const groups = new Map<number, PlaneSegmentRecord[]>();
segments.forEach((segment, index) => {
const root = find(index);
const group = groups.get(root) ?? [];
group.push(segment);
groups.set(root, group);
});
return [...groups.values()].sort((left, right) => right.length - left.length);
}
function fillExportInternalHoles(mask: Uint8Array, width: number, height: number) {
const outside = new Uint8Array(width * height);
const stack: number[] = [];
@@ -1096,6 +1158,53 @@ function fillExportInternalHoles(mask: Uint8Array, width: number, height: number
return patchedPixels;
}
function closeExportMaskGaps(mask: Uint8Array, width: number, height: number, maxGap = 2) {
const toFill = new Set<number>();
const hasPixel = (x: number, y: number) => mask[y * width + x] > 0;
const mark = (x: number, y: number) => {
if (x >= 0 && x < width && y >= 0 && y < height && !hasPixel(x, y)) {
toFill.add(y * width + x);
}
};
for (let y = 0; y < height; y += 1) {
let lastFilled = -1;
for (let x = 0; x < width; x += 1) {
if (!hasPixel(x, y)) {
continue;
}
const gap = x - lastFilled - 1;
if (lastFilled >= 0 && gap > 0 && gap <= maxGap) {
for (let fillX = lastFilled + 1; fillX < x; fillX += 1) {
mark(fillX, y);
}
}
lastFilled = x;
}
}
for (let x = 0; x < width; x += 1) {
let lastFilled = -1;
for (let y = 0; y < height; y += 1) {
if (!hasPixel(x, y)) {
continue;
}
const gap = y - lastFilled - 1;
if (lastFilled >= 0 && gap > 0 && gap <= maxGap) {
for (let fillY = lastFilled + 1; fillY < y; fillY += 1) {
mark(x, fillY);
}
}
lastFilled = y;
}
}
toFill.forEach((index) => {
mask[index] = 1;
});
return toFill.size;
}
function fillExportRows(data: Buffer, width: number, height: number, slice: number, rows: number[][], label: number) {
const mask = new Uint8Array(width * height);
let filledPixels = 0;
@@ -1137,6 +1246,7 @@ function fillExportRows(data: Buffer, width: number, height: number, slice: numb
return 0;
}
filledPixels += closeExportMaskGaps(mask, width, height);
filledPixels += fillExportInternalHoles(mask, width, height);
const sliceOffset = slice * width * height;
for (let index = 0; index < mask.length; index += 1) {
@@ -1269,14 +1379,13 @@ function createSegmentationData(
}
const label = clampNumber(Math.round(style.partId || index + 1), 1, 255);
const slicesByIndex = new Map<number, { rows: number[][]; segments: PlaneSegmentRecord[] }>();
const slicesByIndex = new Map<number, { segments: PlaneSegmentRecord[] }>();
const entryForSlice = (slice: number) => {
const existing = slicesByIndex.get(slice);
if (existing) {
return existing;
}
const entry = {
rows: Array.from({ length: volume.height }, () => [] as number[]),
segments: [] as PlaneSegmentRecord[],
};
slicesByIndex.set(slice, entry);
@@ -1330,16 +1439,19 @@ function createSegmentationData(
b: mapPoint(segment.b),
};
const entry = entryForSlice(slice);
addExportSegmentToRows(entry.rows, volume.width, volume.height, mappedSegment);
entry.segments.push(mappedSegment);
}
});
slicesByIndex.forEach(({ rows, segments }, slice) => {
const filledPixels = fillExportRows(data, volume.width, volume.height, slice, rows, label);
if (filledPixels < Math.max(12, Math.round(segments.length * 0.45)) && segments.length >= 3) {
fillExportFallbackClosedRegion(data, volume.width, volume.height, slice, segments, label);
}
slicesByIndex.forEach(({ segments }, slice) => {
groupExportSegmentsByConnectivity(segments).forEach((group) => {
const rows = Array.from({ length: volume.height }, () => [] as number[]);
group.forEach((segment) => addExportSegmentToRows(rows, volume.width, volume.height, segment));
const filledPixels = fillExportRows(data, volume.width, volume.height, slice, rows, label);
if (filledPixels < Math.max(12, Math.round(group.length * 0.45)) && group.length >= 3) {
fillExportFallbackClosedRegion(data, volume.width, volume.height, slice, group, label);
}
});
});
});