Show automatic pose adjustment deltas

This commit is contained in:
Codex
2026-05-29 17:16:02 +08:00
parent 692c50e22c
commit 86e25d0a51
2 changed files with 100 additions and 9 deletions

View File

@@ -943,6 +943,7 @@ function buildPoseControls() {
markDirty();
});
});
updateAutoPoseResult(state.pose, state.pose);
}
function wireHoldNudge(button) {
@@ -2370,15 +2371,31 @@ function poseDelta(from, to) {
};
}
function signedValue(value, decimals = 3) {
const numeric = Number(value || 0);
if (Math.abs(numeric) < 10 ** -decimals) return "0";
return `${numeric > 0 ? "+" : ""}${numeric.toFixed(decimals)}`;
}
function autoPoseResultItem(label, deltaText, currentText, changed = true) {
return `
<span class="${changed ? "changed" : "muted"}">
<b>${escapeHtml(label)}</b>
<strong>${escapeHtml(deltaText)}</strong>
<small>${escapeHtml(currentText)}</small>
</span>
`;
}
function updateAutoPoseResult(before = state.lastAutoPose?.before || state.pose, after = state.pose) {
const delta = poseDelta(before, after);
const el = $("autoPoseResult");
if (!el) return;
el.innerHTML = [
`<span>平移 X ${delta.translateX}</span>`,
`<span>平移 Y ${delta.translateY}</span>`,
`<span>平移 Z ${delta.translateZ}</span>`,
`<span>缩放 ${delta.scale}</span>`,
autoPoseResultItem("平移 X", `Δ ${signedValue(delta.translateX)}`, `当前 ${Number(after.translateX || 0).toFixed(3)}`, Math.abs(delta.translateX) > 0.0005),
autoPoseResultItem("平移 Y", `Δ ${signedValue(delta.translateY)}`, `当前 ${Number(after.translateY || 0).toFixed(3)}`, Math.abs(delta.translateY) > 0.0005),
autoPoseResultItem("平移 Z", `Δ ${signedValue(delta.translateZ)}`, `当前 ${Number(after.translateZ || 0).toFixed(3)}`, Math.abs(delta.translateZ) > 0.0005),
autoPoseResultItem("缩放", `×${Number(delta.scale || 1).toFixed(3)}`, `当前 ${Number(after.scale || 1).toFixed(3)}`, Math.abs((delta.scale || 1) - 1) > 0.0005),
].join("");
}
@@ -2423,6 +2440,40 @@ function candidateScore(pose, settings = null) {
return (overlapVolume / modelVolume) * Number(config.boneReward || 1) - centerPenalty - scalePenalty - movePenalty - Number(config.outsidePenalty || 0) * 0.01;
}
function estimateCoarsePose(settings) {
const modelBox = visibleModelBox();
if (!modelBox || !state.dicomSceneBox) return null;
const adjustable = settings.adjustable || {};
const dicomSize = new THREE.Vector3();
const modelSize = new THREE.Vector3();
const dicomCenter = new THREE.Vector3();
state.dicomSceneBox.getSize(dicomSize);
state.dicomSceneBox.getCenter(dicomCenter);
modelBox.getSize(modelSize);
const candidate = { ...state.pose };
if (adjustable.scale) {
const ratios = ["x", "y", "z"]
.map((axis) => dicomSize[axis] / Math.max(modelSize[axis], 0.001))
.filter((value) => Number.isFinite(value) && value > 0);
if (ratios.length) {
const fitRatio = Math.min(...ratios) * 0.96;
candidate.scale = Number(clamp((candidate.scale || 1) * fitRatio, 0.2, 3).toFixed(3));
}
}
const previous = { ...state.pose };
applyPose(candidate);
const nextBox = visibleModelBox();
applyPose(previous);
if (!nextBox) return candidate;
const modelCenter = new THREE.Vector3();
nextBox.getCenter(modelCenter);
const offset = dicomCenter.clone().sub(modelCenter);
if (adjustable.translateX) candidate.translateX = Number(((candidate.translateX || 0) + offset.x).toFixed(3));
if (adjustable.translateY) candidate.translateY = Number(((candidate.translateY || 0) + offset.y).toFixed(3));
if (adjustable.translateZ) candidate.translateZ = Number(((candidate.translateZ || 0) + offset.z).toFixed(3));
return candidate;
}
function runAutoCoarse() {
if (!state.activeCase || !currentSeries() || !visibleModelRecords().length) {
$("autoResult").textContent = "请先选择 DICOM 序列和至少一个 STL。";
@@ -2430,14 +2481,21 @@ function runAutoCoarse() {
}
const before = { ...state.pose };
const settings = readAutoSettings();
state.pose = { ...state.pose, translateX: 0, translateY: 0, translateZ: 0, scale: 1 };
const estimated = estimateCoarsePose(settings);
if (!estimated) {
$("autoResult").textContent = "当前缺少 DICOM 或 STL 盒体信息,无法计算粗配准。";
setAutoState("粗配准失败", "error");
return;
}
state.pose = estimated;
renderPoseControls();
applyPose();
fitCamera();
state.autoResult = { score: candidateScore(state.pose, settings), pose: { ...state.pose }, evaluated: 1, settings };
state.lastAutoPose = { before, after: { ...state.pose } };
setAutoState("粗配准完成", "ok");
$("autoResult").textContent = `已按 DICOM 物理尺寸复位平移/缩放score ${state.autoResult.score.toFixed(4)};采样 ${settings.sampleSlices} 张。`;
const delta = poseDelta(before, state.pose);
$("autoResult").textContent = `已按 DICOM/STL 盒体估算中心与缩放ΔX ${signedValue(delta.translateX)}ΔY ${signedValue(delta.translateY)}ΔZ ${signedValue(delta.translateZ)},缩放 ×${delta.scale.toFixed(3)}score ${state.autoResult.score.toFixed(4)}`;
updateAutoPoseResult(before, state.pose);
markDirty();
}