Improve auto pose refinement controls

This commit is contained in:
Codex
2026-05-30 10:52:11 +08:00
parent bdd0db2a4c
commit c1fbdd8bac
3 changed files with 179 additions and 47 deletions

View File

@@ -99,6 +99,8 @@ const state = {
dicomPreviewTimer: 0, dicomPreviewTimer: 0,
autoResult: null, autoResult: null,
lastAutoPose: null, lastAutoPose: null,
autoDeltaBaseline: null,
autoFineRunning: false,
dirty: false, dirty: false,
dirtyReason: "", dirtyReason: "",
savedPoseSignature: "", savedPoseSignature: "",
@@ -223,6 +225,7 @@ function markDirty(reason = "pose") {
state.dirty = true; state.dirty = true;
state.dirtyReason = reason; state.dirtyReason = reason;
setSaveState("未保存", "warn"); setSaveState("未保存", "warn");
if (reason === "pose") updateAutoPoseResult();
} }
function poseSignature(pose = state.pose) { function poseSignature(pose = state.pose) {
@@ -243,6 +246,7 @@ function resetDirty() {
state.dirtyReason = ""; state.dirtyReason = "";
state.savedPoseSignature = poseSignature(state.pose); state.savedPoseSignature = poseSignature(state.pose);
setSaveState("已保存", "ok"); setSaveState("已保存", "ok");
resetAutoDeltaBaseline();
} }
async function api(path, options = {}) { async function api(path, options = {}) {
@@ -530,8 +534,7 @@ function modelPoseMatrix() {
return state.modelGroup.matrix.clone(); return state.modelGroup.matrix.clone();
} }
function visibleModelBox() { function visibleModelBox(records = visibleModelRecords()) {
const records = visibleModelRecords();
if (!records.length) return null; if (!records.length) return null;
const box = new THREE.Box3(); const box = new THREE.Box3();
const matrix = modelPoseMatrix(); const matrix = modelPoseMatrix();
@@ -543,6 +546,14 @@ function visibleModelBox() {
return box; return box;
} }
function autoReferenceRecords(settings = null) {
const visibleRecords = visibleModelRecords();
const referenceIds = new Set((settings?.referenceIds || []).map((id) => Number(id)));
if (!referenceIds.size) return visibleRecords;
const referenceRecords = visibleRecords.filter((record) => referenceIds.has(Number(record.file?.id)));
return referenceRecords.length ? referenceRecords : visibleRecords;
}
function bodyPartTags(row) { function bodyPartTags(row) {
const labels = Array.isArray(row?.body_part_labels) ? row.body_part_labels : []; const labels = Array.isArray(row?.body_part_labels) ? row.body_part_labels : [];
const keys = Array.isArray(row?.body_part_keys) ? row.body_part_keys : []; const keys = Array.isArray(row?.body_part_keys) ? row.body_part_keys : [];
@@ -637,6 +648,7 @@ async function selectCase(ctNumber, algorithmModel = "未指定模型") {
state.modelDetail = registration.model_reference?.model_detail || state.modelDetail || "ultra"; state.modelDetail = registration.model_reference?.model_detail || state.modelDetail || "ultra";
state.autoResult = null; state.autoResult = null;
state.lastAutoPose = null; state.lastAutoPose = null;
state.autoDeltaBaseline = null;
state.sliceIndex = 0; state.sliceIndex = 0;
$("notes").value = registration.notes || ""; $("notes").value = registration.notes || "";
@@ -2460,6 +2472,9 @@ function readAutoSettings() {
const value = Number($(id)?.value); const value = Number($(id)?.value);
return Number.isFinite(value) ? value : fallback; return Number.isFinite(value) ? value : fallback;
}; };
const referenceIds = [...document.querySelectorAll("#autoBoneOptions input:checked")]
.map((input) => Number(input.value))
.filter(Number.isFinite);
return { return {
adjustable: { adjustable: {
translateX: $("autoX")?.checked, translateX: $("autoX")?.checked,
@@ -2467,6 +2482,7 @@ function readAutoSettings() {
translateZ: $("autoZ")?.checked, translateZ: $("autoZ")?.checked,
scale: $("autoScale")?.checked, scale: $("autoScale")?.checked,
}, },
referenceIds,
sampleSlices: Math.max(3, Math.min(60, Math.round(numericValue("autoSampleSlicesNumber", 9)))), sampleSlices: Math.max(3, Math.min(60, Math.round(numericValue("autoSampleSlicesNumber", 9)))),
boneReward: numericValue("autoBoneReward", 1), boneReward: numericValue("autoBoneReward", 1),
outsidePenalty: numericValue("autoOutsidePenalty", 0.1), outsidePenalty: numericValue("autoOutsidePenalty", 0.1),
@@ -2479,6 +2495,9 @@ function readAutoSettings() {
function poseDelta(from, to) { function poseDelta(from, to) {
return { return {
rotateX: Number(((to.rotateX || 0) - (from.rotateX || 0)).toFixed(3)),
rotateY: Number(((to.rotateY || 0) - (from.rotateY || 0)).toFixed(3)),
rotateZ: Number(((to.rotateZ || 0) - (from.rotateZ || 0)).toFixed(3)),
translateX: Number(((to.translateX || 0) - (from.translateX || 0)).toFixed(3)), translateX: Number(((to.translateX || 0) - (from.translateX || 0)).toFixed(3)),
translateY: Number(((to.translateY || 0) - (from.translateY || 0)).toFixed(3)), translateY: Number(((to.translateY || 0) - (from.translateY || 0)).toFixed(3)),
translateZ: Number(((to.translateZ || 0) - (from.translateZ || 0)).toFixed(3)), translateZ: Number(((to.translateZ || 0) - (from.translateZ || 0)).toFixed(3)),
@@ -2502,11 +2521,14 @@ function autoPoseResultItem(label, deltaText, currentText, changed = true) {
`; `;
} }
function updateAutoPoseResult(before = state.lastAutoPose?.before || state.pose, after = state.pose) { function updateAutoPoseResult(before = state.autoDeltaBaseline || state.lastAutoPose?.before || state.pose, after = state.pose) {
const delta = poseDelta(before, after); const delta = poseDelta(before, after);
const el = $("autoPoseResult"); const el = $("autoPoseResult");
if (!el) return; if (!el) return;
el.innerHTML = [ el.innerHTML = [
autoPoseResultItem("旋转 X", `Δ ${signedValue(delta.rotateX, 1)}°`, `当前 ${Number(after.rotateX || 0).toFixed(1)}°`, Math.abs(delta.rotateX) > 0.0005),
autoPoseResultItem("旋转 Y", `Δ ${signedValue(delta.rotateY, 1)}°`, `当前 ${Number(after.rotateY || 0).toFixed(1)}°`, Math.abs(delta.rotateY) > 0.0005),
autoPoseResultItem("旋转 Z", `Δ ${signedValue(delta.rotateZ, 1)}°`, `当前 ${Number(after.rotateZ || 0).toFixed(1)}°`, Math.abs(delta.rotateZ) > 0.0005),
autoPoseResultItem("平移 X", `Δ ${signedValue(delta.translateX)}`, `当前 ${Number(after.translateX || 0).toFixed(3)}`, Math.abs(delta.translateX) > 0.0005), 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("平移 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("平移 Z", `Δ ${signedValue(delta.translateZ)}`, `当前 ${Number(after.translateZ || 0).toFixed(3)}`, Math.abs(delta.translateZ) > 0.0005),
@@ -2514,6 +2536,31 @@ function updateAutoPoseResult(before = state.lastAutoPose?.before || state.pose,
].join(""); ].join("");
} }
function resetAutoDeltaBaseline() {
state.autoDeltaBaseline = { ...state.pose };
updateAutoPoseResult(state.pose, state.pose);
}
function setAutoProgress(visible, value = 0, text = "") {
const wrap = $("autoProgress");
if (!wrap) return;
wrap.classList.toggle("hidden", !visible);
const safeValue = Math.max(0, Math.min(100, Number(value) || 0));
$("autoProgressFill").style.width = `${safeValue}%`;
$("autoProgressText").textContent = text || `${Math.round(safeValue)}%`;
}
function setAutoControlsBusy(busy) {
["autoCoarseBtn", "autoFineBtn", "saveIterateBtn"].forEach((id) => {
const button = $(id);
if (button) button.disabled = busy;
});
}
function waitForUiFrame() {
return new Promise((resolve) => window.requestAnimationFrame(() => resolve()));
}
function restoreAutoPose() { function restoreAutoPose() {
if (!state.lastAutoPose?.before) { if (!state.lastAutoPose?.before) {
$("autoResult").textContent = "暂无可退回的自动调整位姿。"; $("autoResult").textContent = "暂无可退回的自动调整位姿。";
@@ -2523,7 +2570,7 @@ function restoreAutoPose() {
renderPoseControls(); renderPoseControls();
applyPose(); applyPose();
fitCamera(); fitCamera();
updateAutoPoseResult(state.lastAutoPose.after, state.pose); resetAutoDeltaBaseline();
$("autoResult").textContent = "已退回到自动调整前的位姿。"; $("autoResult").textContent = "已退回到自动调整前的位姿。";
markDirty(); markDirty();
} }
@@ -2531,9 +2578,11 @@ function restoreAutoPose() {
function candidateScore(pose, settings = null) { function candidateScore(pose, settings = null) {
if (!state.dicomSceneBox || !state.modelGroup || !visibleModelRecords().length) return -Infinity; if (!state.dicomSceneBox || !state.modelGroup || !visibleModelRecords().length) return -Infinity;
const config = settings || readAutoSettings(); const config = settings || readAutoSettings();
const records = autoReferenceRecords(config);
if (!records.length) return -Infinity;
const current = { ...state.pose }; const current = { ...state.pose };
applyPose(pose); applyPose(pose);
const modelBox = visibleModelBox(); const modelBox = visibleModelBox(records);
applyPose(current); applyPose(current);
if (!modelBox) return -Infinity; if (!modelBox) return -Infinity;
const overlap = modelBox.clone().intersect(state.dicomSceneBox); const overlap = modelBox.clone().intersect(state.dicomSceneBox);
@@ -2556,7 +2605,8 @@ function candidateScore(pose, settings = null) {
} }
function estimateCoarsePose(settings) { function estimateCoarsePose(settings) {
const modelBox = visibleModelBox(); const records = autoReferenceRecords(settings);
const modelBox = visibleModelBox(records);
if (!modelBox || !state.dicomSceneBox) return null; if (!modelBox || !state.dicomSceneBox) return null;
const adjustable = settings.adjustable || {}; const adjustable = settings.adjustable || {};
const dicomSize = new THREE.Vector3(); const dicomSize = new THREE.Vector3();
@@ -2577,7 +2627,7 @@ function estimateCoarsePose(settings) {
} }
const previous = { ...state.pose }; const previous = { ...state.pose };
applyPose(candidate); applyPose(candidate);
const nextBox = visibleModelBox(); const nextBox = visibleModelBox(records);
applyPose(previous); applyPose(previous);
if (!nextBox) return candidate; if (!nextBox) return candidate;
const modelCenter = new THREE.Vector3(); const modelCenter = new THREE.Vector3();
@@ -2594,6 +2644,8 @@ function runAutoCoarse() {
$("autoResult").textContent = "请先选择 DICOM 序列和至少一个 STL。"; $("autoResult").textContent = "请先选择 DICOM 序列和至少一个 STL。";
return; return;
} }
if (state.autoFineRunning) return;
setAutoProgress(false);
const before = { ...state.pose }; const before = { ...state.pose };
const settings = readAutoSettings(); const settings = readAutoSettings();
const estimated = estimateCoarsePose(settings); const estimated = estimateCoarsePose(settings);
@@ -2608,6 +2660,7 @@ function runAutoCoarse() {
fitCamera(); fitCamera();
state.autoResult = { score: candidateScore(state.pose, settings), pose: { ...state.pose }, evaluated: 1, settings }; state.autoResult = { score: candidateScore(state.pose, settings), pose: { ...state.pose }, evaluated: 1, settings };
state.lastAutoPose = { before, after: { ...state.pose } }; state.lastAutoPose = { before, after: { ...state.pose } };
state.autoDeltaBaseline = { ...before };
setAutoState("粗配准完成", "ok"); setAutoState("粗配准完成", "ok");
const delta = poseDelta(before, state.pose); 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)}`; $("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)}`;
@@ -2615,49 +2668,81 @@ function runAutoCoarse() {
markDirty(); markDirty();
} }
function runAutoFine() { async function runAutoFine() {
if (!state.activeCase || !currentSeries() || !visibleModelRecords().length) { if (!state.activeCase || !currentSeries() || !visibleModelRecords().length) {
$("autoResult").textContent = "请先选择 DICOM 序列和至少一个 STL。"; $("autoResult").textContent = "请先选择 DICOM 序列和至少一个 STL。";
return; return;
} }
setAutoState("运行中", "warn"); if (state.autoFineRunning) return;
$("autoResult").textContent = "正在进行几何重叠微调...";
const before = { ...state.pose }; const before = { ...state.pose };
const settings = readAutoSettings(); const settings = readAutoSettings();
const adjustable = settings.adjustable; const adjustable = settings.adjustable;
state.autoFineRunning = true;
setAutoControlsBusy(true);
setAutoState("运行中", "warn");
setAutoProgress(true, 2, "2%");
if (state.windowMode !== "bone") setWindowMode("bone", true);
$("autoResult").textContent = "已切换骨窗显示,正在按参考 STL 与 DICOM 三维盒体重叠做微调...";
await waitForUiFrame();
try {
let best = { pose: { ...state.pose }, score: candidateScore(state.pose, settings), mode: "初始位姿" }; let best = { pose: { ...state.pose }, score: candidateScore(state.pose, settings), mode: "初始位姿" };
let evaluated = 1; let evaluated = 1;
const coarsePose = estimateCoarsePose(settings);
if (coarsePose) {
const coarseScore = candidateScore(coarsePose, settings);
evaluated += 1;
if (coarseScore > best.score) best = { pose: coarsePose, score: coarseScore, mode: "粗配准种子" };
}
const rounds = Array.from({ length: settings.iterations }, (_, index) => { const rounds = Array.from({ length: settings.iterations }, (_, index) => {
const t = Math.max(0.18, 1 - index / Math.max(settings.iterations, 1)); const t = Math.max(0.18, 1 - index / Math.max(settings.iterations, 1));
return { move: 0.34 * t, scale: 0.12 * t }; return { move: 0.34 * t, scale: 0.12 * t };
}); });
for (const round of rounds) { for (const [index, round] of rounds.entries()) {
const candidates = []; const candidates = [];
if (adjustable.translateX) candidates.push(["translateX", round.move], ["translateX", -round.move]); if (adjustable.translateX) candidates.push(["translateX", round.move], ["translateX", -round.move]);
if (adjustable.translateY) candidates.push(["translateY", round.move], ["translateY", -round.move]); if (adjustable.translateY) candidates.push(["translateY", round.move], ["translateY", -round.move]);
if (adjustable.translateZ) candidates.push(["translateZ", round.move], ["translateZ", -round.move]); if (adjustable.translateZ) candidates.push(["translateZ", round.move], ["translateZ", -round.move]);
if (adjustable.scale) candidates.push(["scale", round.scale], ["scale", -round.scale]); if (adjustable.scale) candidates.push(["scale", round.scale], ["scale", -round.scale]);
for (const [key, delta] of candidates.slice(0, settings.candidates)) { for (const [key, delta] of candidates.slice(0, settings.candidates)) {
const pose = { ...best.pose, [key]: key === "scale" ? Math.max(0.2, Math.min(3, best.pose.scale + delta)) : best.pose[key] + delta }; const baseValue = Number(best.pose[key] ?? DEFAULT_POSE[key]);
const nextValue = key === "scale" ? Math.max(0.2, Math.min(3, baseValue + delta)) : baseValue + delta;
const pose = { ...best.pose, [key]: Number(nextValue.toFixed(key === "scale" ? 3 : 4)) };
const score = candidateScore(pose, settings); const score = candidateScore(pose, settings);
evaluated += 1; evaluated += 1;
if (score > best.score) best = { pose, score, mode: `${key} ${delta > 0 ? "+" : "-"}` }; if (score > best.score) best = { pose, score, mode: `${key} ${delta > 0 ? "+" : "-"}` };
} }
const progress = 8 + ((index + 1) / Math.max(rounds.length, 1)) * 88;
setAutoProgress(true, progress, `${Math.round(progress)}%`);
await waitForUiFrame();
} }
state.pose = { ...best.pose }; state.pose = { ...best.pose };
state.autoResult = { ...best, evaluated, settings }; state.autoResult = { ...best, evaluated, settings: { ...settings, scoring: "reference-stl-box-overlap", window: "bone-display" } };
state.lastAutoPose = { before, after: { ...state.pose } }; state.lastAutoPose = { before, after: { ...state.pose } };
state.autoDeltaBaseline = { ...before };
renderPoseControls(); renderPoseControls();
applyPose(); applyPose();
fitCamera(); fitCamera();
setAutoState("微调完成", "ok"); const changed = poseSignature(best.pose) !== poseSignature(before);
$("autoResult").textContent = `最佳候选:${best.mode}score ${best.score.toFixed(4)};评估 ${evaluated} 个候选;采样 ${settings.sampleSlices} 张。`; setAutoState(changed ? "微调完成" : "微调无变化", changed ? "ok" : "warn");
const scoreText = Number.isFinite(best.score) ? best.score.toFixed(4) : "不可用";
$("autoResult").textContent = changed
? `最佳候选:${best.mode}score ${scoreText};评估 ${evaluated} 个候选;骨窗显示 + 参考 STL 盒体评分。`
: `已完成评估但最佳仍为初始位姿score ${scoreText};评估 ${evaluated} 个候选。当前微调使用骨窗显示和参考 STL 盒体评分,若图像上仍错位,需要先粗配准或手动调整旋转后再微调。`;
updateAutoPoseResult(before, state.pose); updateAutoPoseResult(before, state.pose);
setAutoProgress(true, 100, "100%");
markDirty(); markDirty();
} catch (error) {
setAutoState("微调失败", "error");
$("autoResult").textContent = error.message || "自动微调失败。";
setAutoProgress(false);
} finally {
state.autoFineRunning = false;
setAutoControlsBusy(false);
}
} }
async function saveRegistration(nextStatus = null) { async function saveRegistration(nextStatus = null) {
if (!state.activeCase || !currentSeries() || state.saving) return; if (!state.activeCase || !currentSeries() || state.saving) return false;
state.saving = true; state.saving = true;
$("saveBtn").disabled = true; $("saveBtn").disabled = true;
$("statusBtn").disabled = true; $("statusBtn").disabled = true;
@@ -2722,8 +2807,10 @@ async function saveRegistration(nextStatus = null) {
renderCases(); renderCases();
renderSeries(); renderSeries();
renderActiveHeader(); renderActiveHeader();
return true;
} catch (error) { } catch (error) {
setSaveState(error.message, "error"); setSaveState(error.message, "error");
return false;
} finally { } finally {
state.saving = false; state.saving = false;
$("saveBtn").disabled = false; $("saveBtn").disabled = false;
@@ -2822,8 +2909,10 @@ function wireEvents() {
$("autoFineBtn").addEventListener("click", runAutoFine); $("autoFineBtn").addEventListener("click", runAutoFine);
$("savePoseBtn").addEventListener("click", () => saveRegistration()); $("savePoseBtn").addEventListener("click", () => saveRegistration());
$("saveIterateBtn").addEventListener("click", async () => { $("saveIterateBtn").addEventListener("click", async () => {
await saveRegistration(); const saved = await saveRegistration();
runAutoFine(); if (!saved) return;
await runAutoFine();
resetAutoDeltaBaseline();
}); });
$("restorePoseBtn").addEventListener("click", restoreAutoPose); $("restorePoseBtn").addEventListener("click", restoreAutoPose);
const syncSampleSlices = (source) => { const syncSampleSlices = (source) => {

View File

@@ -236,23 +236,30 @@
<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"> <div class="pose-actions modal-actions auto-run-actions">
<button id="autoCoarseBtn" type="button">自动粗配准</button> <button id="autoCoarseBtn" type="button">自动粗配准</button>
<button id="autoFineBtn" type="button">自动微调</button> <button id="autoFineBtn" type="button">自动微调</button>
</div> </div>
<div class="pose-actions modal-actions save-pose-actions">
<button id="savePoseBtn" type="button">保存位姿</button>
<button id="saveIterateBtn" type="button">保存并迭代</button>
<button id="restorePoseBtn" type="button">退回位姿</button>
</div>
<div id="autoPoseResult" class="auto-pose-result"> <div id="autoPoseResult" class="auto-pose-result">
<span>旋转 X 0</span>
<span>旋转 Y 0</span>
<span>旋转 Z 0</span>
<span>平移 X 0</span> <span>平移 X 0</span>
<span>平移 Y 0</span> <span>平移 Y 0</span>
<span>平移 Z 0</span> <span>平移 Z 0</span>
<span>缩放 1</span> <span>缩放 1</span>
</div> </div>
<div class="pose-actions modal-actions">
<button id="savePoseBtn" type="button">保存位姿</button>
<button id="saveIterateBtn" type="button">保存并迭代</button>
<button id="restorePoseBtn" type="button">退回位姿</button>
</div>
</div> </div>
<div id="autoResult" class="auto-result">选择 DICOM 序列和 STL 后可运行。</div> <div id="autoResult" class="auto-result">选择 DICOM 序列和 STL 后可运行。</div>
<div id="autoProgress" class="auto-progress hidden">
<div><i id="autoProgressFill"></i></div>
<span id="autoProgressText">0%</span>
</div>
</div> </div>
<textarea id="notes" class="notes" rows="2" placeholder="配准备注"></textarea> <textarea id="notes" class="notes" rows="2" placeholder="配准备注"></textarea>
</section> </section>

View File

@@ -1872,6 +1872,42 @@ input[type="range"] {
padding: 8px; padding: 8px;
} }
.auto-progress {
display: grid;
grid-template-columns: minmax(0, 1fr) 42px;
align-items: center;
gap: 10px;
min-height: 28px;
border: 1px solid rgba(59, 130, 246, 0.3);
border-radius: 8px;
background: rgba(7, 10, 15, 0.72);
padding: 7px 8px;
}
.auto-progress div {
height: 7px;
overflow: hidden;
border-radius: 99px;
background: rgba(148, 163, 184, 0.18);
}
.auto-progress i {
display: block;
width: 0%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #22d3ee, #3b82f6);
transition: width 160ms ease;
}
.auto-progress span {
color: #c7d2fe;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 11px;
font-weight: 950;
text-align: right;
}
.compact-title { .compact-title {
min-height: 28px; min-height: 28px;
margin-top: 4px; margin-top: 4px;
@@ -2076,7 +2112,7 @@ input[type="range"] {
} }
.tool-pane .auto-pose-result { .tool-pane .auto-pose-result {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
.compact-empty { .compact-empty {
@@ -2084,7 +2120,7 @@ input[type="range"] {
} }
.auto-pose-result span { .auto-pose-result span {
min-height: 52px; min-height: 50px;
display: grid; display: grid;
grid-template-rows: auto auto auto; grid-template-rows: auto auto auto;
align-items: center; align-items: center;