2026-05-24-15-37-16 修正mask线条桥接与DICOM复位入口
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user