Align registration status and crisp slice masks
This commit is contained in:
@@ -1813,7 +1813,7 @@ function paintMaskPixel(maskData, width, height, x, y, rgb, alpha) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function solidStrokeRadius(width, height) {
|
function solidStrokeRadius(width, height) {
|
||||||
return Math.max(2.2, Math.min(5.5, Math.max(width, height) * 0.006));
|
return Math.max(1, Math.min(2.4, Math.max(width, height) * 0.002));
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillSegmentCapsulesIntoMask(maskData, width, height, segments, rgb, alpha, radius) {
|
function fillSegmentCapsulesIntoMask(maskData, width, height, segments, rgb, alpha, radius) {
|
||||||
@@ -1972,12 +1972,13 @@ function drawFallbackClosedRegion(context, width, height, segments, color, opaci
|
|||||||
function fillSegmentsAsSolidMask(context, width, height, segments, color, opacity = 0.72) {
|
function fillSegmentsAsSolidMask(context, width, height, segments, color, opacity = 0.72) {
|
||||||
if (!segments.length) return 0;
|
if (!segments.length) return 0;
|
||||||
const rgb = parseCssColor(color);
|
const rgb = parseCssColor(color);
|
||||||
const alpha = Math.round(clamp(opacity, 0.1, 1) * 190);
|
const alpha = Math.round(clamp(opacity, 0.1, 1) * 220);
|
||||||
const maskCanvas = document.createElement("canvas");
|
const maskCanvas = document.createElement("canvas");
|
||||||
maskCanvas.width = width;
|
maskCanvas.width = width;
|
||||||
maskCanvas.height = height;
|
maskCanvas.height = height;
|
||||||
const maskContext = maskCanvas.getContext("2d");
|
const maskContext = maskCanvas.getContext("2d");
|
||||||
if (!maskContext) return 0;
|
if (!maskContext) return 0;
|
||||||
|
maskContext.imageSmoothingEnabled = false;
|
||||||
const maskData = maskContext.createImageData(width, height);
|
const maskData = maskContext.createImageData(width, height);
|
||||||
const radius = solidStrokeRadius(width, height);
|
const radius = solidStrokeRadius(width, height);
|
||||||
const groups = groupPlaneSegmentsByConnectivity(segments, radius * 1.15);
|
const groups = groupPlaneSegmentsByConnectivity(segments, radius * 1.15);
|
||||||
@@ -2018,40 +2019,12 @@ function fillSegmentsAsSolidMask(context, width, height, segments, color, opacit
|
|||||||
filledPixels += groupPixels;
|
filledPixels += groupPixels;
|
||||||
});
|
});
|
||||||
|
|
||||||
filledPixels += closeSmallMaskGaps(maskData, width, height, rgb, alpha, 3);
|
filledPixels += closeSmallMaskGaps(maskData, width, height, rgb, alpha, 1);
|
||||||
filledPixels += fillInternalMaskHoles(maskData, width, height, rgb, alpha);
|
filledPixels += fillInternalMaskHoles(maskData, width, height, rgb, alpha);
|
||||||
maskContext.putImageData(maskData, 0, 0);
|
maskContext.putImageData(maskData, 0, 0);
|
||||||
|
context.imageSmoothingEnabled = false;
|
||||||
context.drawImage(maskCanvas, 0, 0);
|
context.drawImage(maskCanvas, 0, 0);
|
||||||
|
|
||||||
if (filledPixels === 0) {
|
|
||||||
context.save();
|
|
||||||
context.globalAlpha = clamp(opacity, 0.1, 1) * 0.42;
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = Math.max(0.8, Math.max(width, height) * 0.0012);
|
|
||||||
context.lineCap = "round";
|
|
||||||
context.lineJoin = "round";
|
|
||||||
context.beginPath();
|
|
||||||
segments.forEach((segment) => {
|
|
||||||
context.moveTo(segment.a.x, segment.a.y);
|
|
||||||
context.lineTo(segment.b.x, segment.b.y);
|
|
||||||
});
|
|
||||||
context.stroke();
|
|
||||||
context.restore();
|
|
||||||
}
|
|
||||||
context.save();
|
|
||||||
context.globalAlpha = clamp(opacity, 0.1, 1) * 0.65;
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.lineWidth = Math.max(0.8, Math.max(width, height) * 0.0012);
|
|
||||||
context.lineCap = "round";
|
|
||||||
context.lineJoin = "round";
|
|
||||||
context.beginPath();
|
|
||||||
segments.forEach((segment) => {
|
|
||||||
context.moveTo(segment.a.x, segment.a.y);
|
|
||||||
context.lineTo(segment.b.x, segment.b.y);
|
|
||||||
});
|
|
||||||
context.stroke();
|
|
||||||
context.restore();
|
|
||||||
|
|
||||||
return filledPixels;
|
return filledPixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2079,6 +2052,7 @@ async function drawMappingView(version = state.mappingVersion) {
|
|||||||
if (canvas.width !== width) canvas.width = width;
|
if (canvas.width !== width) canvas.width = width;
|
||||||
if (canvas.height !== height) canvas.height = height;
|
if (canvas.height !== height) canvas.height = height;
|
||||||
const context = canvas.getContext("2d");
|
const context = canvas.getContext("2d");
|
||||||
|
context.imageSmoothingEnabled = false;
|
||||||
context.clearRect(0, 0, width, height);
|
context.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
const targetZ = sliceToSceneZ(state.sliceIndex, volumeScene);
|
const targetZ = sliceToSceneZ(state.sliceIndex, volumeScene);
|
||||||
@@ -2101,7 +2075,7 @@ async function drawMappingView(version = state.mappingVersion) {
|
|||||||
const position = record.mesh.geometry.attributes.position;
|
const position = record.mesh.geometry.attributes.position;
|
||||||
if (!position) continue;
|
if (!position) continue;
|
||||||
const triangleCount = Math.floor(position.count / 3);
|
const triangleCount = Math.floor(position.count / 3);
|
||||||
const step = Math.max(1, Math.ceil(triangleCount / 260000));
|
const step = 1;
|
||||||
const segments = [];
|
const segments = [];
|
||||||
for (let tri = 0; tri < triangleCount; tri += step) {
|
for (let tri = 0; tri < triangleCount; tri += step) {
|
||||||
tempA.fromBufferAttribute(position, tri * 3).applyMatrix4(matrix);
|
tempA.fromBufferAttribute(position, tri * 3).applyMatrix4(matrix);
|
||||||
@@ -3166,8 +3140,8 @@ function wireEvents() {
|
|||||||
$("autoSettingsPanel").scrollIntoView({ block: "nearest", behavior: "smooth" });
|
$("autoSettingsPanel").scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||||
});
|
});
|
||||||
$("suggestBoneBtn").addEventListener("click", suggestBoneSelection);
|
$("suggestBoneBtn").addEventListener("click", suggestBoneSelection);
|
||||||
$("autoCoarseBtn").addEventListener("click", runAutoCoarse);
|
$("autoCoarseBtn")?.addEventListener("click", runAutoCoarse);
|
||||||
$("autoFineBtn").addEventListener("click", runAutoFine);
|
$("autoFineBtn")?.addEventListener("click", runAutoFine);
|
||||||
$("savePoseBtn").addEventListener("click", () => saveRegistration());
|
$("savePoseBtn").addEventListener("click", () => saveRegistration());
|
||||||
$("saveIterateBtn").addEventListener("click", saveAndIteratePose);
|
$("saveIterateBtn").addEventListener("click", saveAndIteratePose);
|
||||||
$("restorePoseBtn").addEventListener("click", restoreAutoPose);
|
$("restorePoseBtn").addEventListener("click", restoreAutoPose);
|
||||||
|
|||||||
@@ -236,9 +236,8 @@
|
|||||||
<label>迭代轮次<input id="autoIterations" type="number" min="1" max="100" value="30" /></label>
|
<label>迭代轮次<input id="autoIterations" type="number" min="1" max="100" value="30" /></label>
|
||||||
<label>每轮候选<input id="autoCandidates" type="number" min="6" max="96" value="36" /></label>
|
<label>每轮候选<input id="autoCandidates" type="number" min="6" max="96" value="36" /></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="pose-actions modal-actions auto-run-actions">
|
<div class="registration-operation-hint">
|
||||||
<button id="autoCoarseBtn" type="button">自动粗配准</button>
|
下方为配准操作区:保存当前位姿、保存并迭代自动微调,或退回上次自动调整前位姿。
|
||||||
<button id="autoFineBtn" type="button">自动微调</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pose-actions modal-actions save-pose-actions">
|
<div class="pose-actions modal-actions save-pose-actions">
|
||||||
<button id="savePoseBtn" type="button">保存位姿</button>
|
<button id="savePoseBtn" type="button">保存位姿</button>
|
||||||
|
|||||||
@@ -2039,6 +2039,18 @@ input[type="range"] {
|
|||||||
margin: 12px 0 0;
|
margin: 12px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.registration-operation-hint {
|
||||||
|
border: 1px solid rgba(25, 214, 195, 0.26);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(20, 184, 166, 0.08);
|
||||||
|
color: #a7f3d0;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1.45;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.auto-config-grid {
|
.auto-config-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(240px, 0.85fr) minmax(300px, 1.15fr);
|
grid-template-columns: minmax(240px, 0.85fr) minmax(300px, 1.15fr);
|
||||||
|
|||||||
@@ -529,10 +529,16 @@ def relation_cte() -> str:
|
|||||||
r AS (
|
r AS (
|
||||||
SELECT
|
SELECT
|
||||||
upper(ct_number) AS ct_key,
|
upper(ct_number) AS ct_key,
|
||||||
count(*)::int AS registration_count,
|
count(*) FILTER (WHERE COALESCE(registration_status, 'unregistered') = 'registered')::int AS registration_count,
|
||||||
count(DISTINCT series_instance_uid) FILTER (WHERE series_instance_uid <> '')::int AS registered_series,
|
count(DISTINCT series_instance_uid) FILTER (
|
||||||
count(*) FILTER (WHERE locked)::int AS locked_count,
|
WHERE COALESCE(registration_status, 'unregistered') = 'registered'
|
||||||
max(updated_at) AS registration_updated_at
|
AND series_instance_uid <> ''
|
||||||
|
)::int AS registered_series,
|
||||||
|
count(*) FILTER (
|
||||||
|
WHERE COALESCE(registration_status, 'unregistered') = 'registered'
|
||||||
|
AND COALESCE(locked, false)
|
||||||
|
)::int AS locked_count,
|
||||||
|
max(updated_at) FILTER (WHERE COALESCE(registration_status, 'unregistered') = 'registered') AS registration_updated_at
|
||||||
FROM public.{REGISTRATION_TABLE_SQL}
|
FROM public.{REGISTRATION_TABLE_SQL}
|
||||||
GROUP BY upper(ct_number)
|
GROUP BY upper(ct_number)
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user