Show automatic pose adjustment deltas
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user