2026-05-24-18-59-49 修正薄壳构件实体化映射

This commit is contained in:
2026-05-24 19:10:02 +08:00
parent 1dcfc2a4c1
commit 21b04ecffd
8 changed files with 337 additions and 20 deletions

View File

@@ -142,7 +142,7 @@ const dicomVolumeCache = new Map<string, {
}>();
const modelPreviewCache = new Map<string, unknown>();
const defaultModuleColors = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6', '#14b8a6', '#f97316', '#64748b', '#ec4899'];
const maxPreviewTriangles = 500000;
const maxPreviewTriangles = 800000;
const defaultModelPose: ModelPoseValue = {
rotateX: 0,
rotateY: 0,
@@ -1288,7 +1288,76 @@ function closeExportMaskGaps(mask: Uint8Array, width: number, height: number, ma
return toFill.size;
}
function fillExportRows(data: Buffer, width: number, height: number, slice: number, rows: number[][], label: number) {
function exportSolidStrokeRadius(width: number, height: number) {
return Math.max(2.2, Math.min(5.5, Math.max(width, height) * 0.006));
}
function paintExportMaskPixel(mask: Uint8Array, width: number, height: number, x: number, y: number) {
if (x < 0 || x >= width || y < 0 || y >= height) {
return 0;
}
const index = y * width + x;
if (mask[index]) {
return 0;
}
mask[index] = 1;
return 1;
}
function fillExportSegmentCapsules(
mask: Uint8Array,
width: number,
height: number,
segments: PlaneSegmentRecord[],
radius: number,
) {
let paintedPixels = 0;
const radiusSquared = radius * radius;
segments.forEach(({ a, b }) => {
if (!Number.isFinite(a.x) || !Number.isFinite(a.y) || !Number.isFinite(b.x) || !Number.isFinite(b.y)) {
return;
}
const dx = b.x - a.x;
const dy = b.y - a.y;
const lengthSquared = dx * dx + dy * dy;
const minX = clampNumber(Math.floor(Math.min(a.x, b.x) - radius), 0, width - 1);
const maxX = clampNumber(Math.ceil(Math.max(a.x, b.x) + radius), 0, width - 1);
const minY = clampNumber(Math.floor(Math.min(a.y, b.y) - radius), 0, height - 1);
const maxY = clampNumber(Math.ceil(Math.max(a.y, b.y) + radius), 0, height - 1);
for (let y = minY; y <= maxY; y += 1) {
for (let x = minX; x <= maxX; x += 1) {
const px = x + 0.5;
const py = y + 0.5;
const t = lengthSquared <= 1e-6
? 0
: clampNumber(((px - a.x) * dx + (py - a.y) * dy) / lengthSquared, 0, 1);
const closestX = a.x + dx * t;
const closestY = a.y + dy * t;
const distanceSquared = (px - closestX) ** 2 + (py - closestY) ** 2;
if (distanceSquared <= radiusSquared) {
paintedPixels += paintExportMaskPixel(mask, width, height, x, y);
}
}
}
});
return paintedPixels;
}
function fillExportRows(
data: Buffer,
width: number,
height: number,
slice: number,
rows: number[][],
label: number,
solidSegments: PlaneSegmentRecord[] = [],
) {
const mask = new Uint8Array(width * height);
let filledPixels = 0;
@@ -1325,11 +1394,15 @@ function fillExportRows(data: Buffer, width: number, height: number, slice: numb
}
});
if (solidSegments.length) {
filledPixels += fillExportSegmentCapsules(mask, width, height, solidSegments, exportSolidStrokeRadius(width, height));
}
if (filledPixels === 0) {
return 0;
}
filledPixels += closeExportMaskGaps(mask, width, height);
filledPixels += closeExportMaskGaps(mask, width, height, 3);
filledPixels += fillExportInternalHoles(mask, width, height);
const sliceOffset = slice * width * height;
for (let index = 0; index < mask.length; index += 1) {
@@ -1527,11 +1600,11 @@ function createSegmentationData(
});
slicesByIndex.forEach(({ segments }, slice) => {
groupExportSegmentsByConnectivity(segments).forEach((group) => {
groupExportSegmentsByConnectivity(segments, exportSolidStrokeRadius(volume.width, volume.height) * 1.15).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) {
const filledPixels = fillExportRows(data, volume.width, volume.height, slice, rows, label, group);
if (filledPixels < Math.max(20, Math.round(group.length * 0.5)) && group.length >= 3) {
fillExportFallbackClosedRegion(data, volume.width, volume.height, slice, group, label);
}
});