board rotation - fix 0

This commit is contained in:
chk
2026-06-10 18:08:18 +02:00
parent 9105dc5eac
commit b1950ffa5a
5 changed files with 104 additions and 25 deletions

View File

@@ -354,6 +354,27 @@ async function loadBoardTable() {
// ── Board ───────────────────────────────────────────────────────────────────── // ── Board ─────────────────────────────────────────────────────────────────────
/** Befüllt alle Set-Dropdowns aus /api/robot/board-sets */
async function populateBoardSetDropdowns() {
let sets = [];
try {
const r = await fetch('/api/robot/board-sets');
if (r.ok) sets = (await r.json()).sets ?? [];
} catch { /* kein Server / noch keine Sets → leere Dropdowns */ }
// Hilfsfunktion: <select> mit Optionen füllen
function fill(selId, placeholder, selected = '') {
const sel = document.getElementById(selId);
if (!sel) return;
sel.innerHTML = `<option value="">${placeholder}</option>` +
sets.map(s => `<option value="${s}"${s === selected ? ' selected' : ''}>${s}</option>`).join('');
}
fill('board-ref-set', 'alle');
fill('act-align-fixed', ' bleibt ');
fill('act-align-move', ' verschoben ');
}
function initBoard() { function initBoard() {
const logBoard = document.getElementById('log-board'); const logBoard = document.getElementById('log-board');
@@ -363,15 +384,23 @@ function initBoard() {
logBoard.scrollTop = logBoard.scrollHeight; logBoard.scrollTop = logBoard.scrollHeight;
} }
// Dropdowns mit Sets aus robot.json befüllen
populateBoardSetDropdowns();
// Tabelle beim ersten Öffnen des Tabs befüllen // Tabelle beim ersten Öffnen des Tabs befüllen
loadBoardTable(); loadBoardTable();
document.getElementById('btn-board-run').addEventListener('click', async () => { document.getElementById('btn-board-run').addEventListener('click', async () => {
logB('Board-Erkennung wird gestartet …'); const refSet = document.getElementById('board-ref-set')?.value ?? '';
logB(`Board-Erkennung wird gestartet … Referenz: ${refSet || 'alle'}`);
const btn = document.getElementById('btn-board-run'); const btn = document.getElementById('btn-board-run');
btn.disabled = true; btn.disabled = true;
try { try {
const response = await fetch('/api/board/run', { method: 'POST' }); const response = await fetch('/api/board/run', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refSet: refSet || undefined }),
});
if (!response.ok) { if (!response.ok) {
const raw = await response.text().catch(() => ''); const raw = await response.text().catch(() => '');
let msg; let msg;
@@ -390,8 +419,9 @@ function initBoard() {
if (frame?.contentWindow) { if (frame?.contentWindow) {
frame.contentWindow.postMessage({ type: 'reload' }, '*'); frame.contentWindow.postMessage({ type: 'reload' }, '*');
} }
// Marker-Tabelle aktualisieren // Marker-Tabelle + Set-Dropdowns aktualisieren
loadBoardTable(); loadBoardTable();
populateBoardSetDropdowns();
} }
} else { } else {
logB(`❌ Beendet mit Exit-Code ${evt.exitCode}`); logB(`❌ Beendet mit Exit-Code ${evt.exitCode}`);
@@ -484,11 +514,15 @@ function initBoard() {
// ── Aktion 3: Sets justieren (Kabsch 2D+Z) ───────────────────────────────── // ── Aktion 3: Sets justieren (Kabsch 2D+Z) ─────────────────────────────────
document.getElementById('btn-act-align').addEventListener('click', async () => { document.getElementById('btn-act-align').addEventListener('click', async () => {
const setToMove = document.getElementById('act-align-set').value.trim(); const setFixed = document.getElementById('act-align-fixed').value;
const setToMove = document.getElementById('act-align-move').value;
const result = document.getElementById('act-result'); const result = document.getElementById('act-result');
if (!setToMove) { if (!setToMove) {
result.innerHTML = '<span style="color:#f87171">⚠ Bitte Set-Name eingeben (z.B. "rail").</span>'; return; result.innerHTML = '<span style="color:#f87171">⚠ Bitte Set auswählen, das verschoben werden soll.</span>'; return;
}
if (setFixed && setFixed === setToMove) {
result.innerHTML = '<span style="color:#f87171">⚠ "Bleibt" und "verschoben" dürfen nicht dasselbe Set sein.</span>'; return;
} }
result.innerHTML = '<span style="color:#555b6e">Justierung läuft …</span>'; result.innerHTML = '<span style="color:#555b6e">Justierung läuft …</span>';
@@ -496,7 +530,7 @@ function initBoard() {
const r = await fetch('/api/robot/align-sets', { const r = await fetch('/api/robot/align-sets', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ setToMove }), body: JSON.stringify({ setFixed: setFixed || undefined, setToMove }),
}); });
const data = await r.json(); const data = await r.json();
if (!r.ok || data.error) { if (!r.ok || data.error) {
@@ -505,7 +539,7 @@ function initBoard() {
const t = data.transform; const t = data.transform;
result.innerHTML = result.innerHTML =
`<span style="color:#22c55e">✅ Set "${setToMove}": ${data.numChanged} Marker verschoben` + `<span style="color:#22c55e">✅ Set "${setToMove}": ${data.numChanged} Marker verschoben` +
` (${data.numMatchingPts} Messpunkte)</span>` + ` (${data.numMatchingPts} Messpunkte)${setFixed ? ` "${setFixed}" bleibt` : ''}</span>` +
` &ensp;Δx=${t.tx}&thinsp;mm&ensp;Δy=${t.ty}&thinsp;mm&ensp;Δz=${t.tz}&thinsp;mm` + ` &ensp;Δx=${t.tx}&thinsp;mm&ensp;Δy=${t.ty}&thinsp;mm&ensp;Δz=${t.tz}&thinsp;mm` +
`&ensp;θ=${t.thetaDeg}°`; `&ensp;θ=${t.thetaDeg}°`;
loadBoardTable(); loadBoardTable();

View File

@@ -14,9 +14,15 @@
<span class="info-label">Letzter Run</span> <span class="info-label">Letzter Run</span>
<span class="info-value" id="board-last-run"></span> <span class="info-value" id="board-last-run"></span>
</div> </div>
<div class="controls" style="margin-top: 16px;"> <div class="controls" style="margin-top: 16px; display:flex; align-items:center; gap:10px; flex-wrap:wrap;">
<button id="btn-board-run">Board erkennen</button> <button id="btn-board-run">Board erkennen</button>
<button disabled title="Folgt später">Ergebnis anzeigen</button> <label style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)">
Referenz:
<select id="board-ref-set"
style="background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 8px;font:inherit;font-size:12px;cursor:pointer">
<option value="">alle</option>
</select>
</label>
</div> </div>
</div> </div>
@@ -78,14 +84,21 @@
Sets justieren (zu 3b-Messung) Sets justieren (zu 3b-Messung)
</p> </p>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:12px;color:var(--text)"> <div style="display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:12px;color:var(--text)">
Set verschieben <select id="act-align-fixed"
<input id="act-align-set" type="text" placeholder="rail" style="background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 8px;font:inherit;font-size:12px;cursor:pointer">
style="width:90px;background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 7px;font:inherit;font-size:12px"> <option value=""> bleibt </option>
</select>
bleibt&ensp;/&ensp;
<select id="act-align-move"
style="background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 8px;font:inherit;font-size:12px;cursor:pointer">
<option value=""> verschoben </option>
</select>
wird verschoben
<button id="btn-act-align" <button id="btn-act-align"
style="background:#1e293b;color:#c8cdd8;border:1px solid #4a9eff;border-radius:3px;padding:4px 14px;cursor:pointer;font:inherit;font-size:12px"> style="background:#1e293b;color:#c8cdd8;border:1px solid #4a9eff;border-radius:3px;padding:4px 14px;cursor:pointer;font:inherit;font-size:12px">
Justieren Justieren
</button> </button>
<span style="color:var(--muted);font-size:10px">Rotation (Z-Achse) + Translation → passt Set zu 3b-Messung</span> <span style="color:var(--muted);font-size:10px">Rotation (Z-Achse) + Translation → passt verschobenes Set zu 3b-Messung</span>
</div> </div>
</div> </div>

View File

@@ -212,13 +212,15 @@ def get_marker_rotation(marker: Dict[str, Any]) -> np.ndarray:
return np.eye(3, dtype=np.float32) return np.eye(3, dtype=np.float32)
def load_marker_lookup(robot_json_path: str) -> Dict[int, Dict[str, Any]]: def load_marker_lookup(robot_json_path: str, ref_set: Optional[str] = None) -> Dict[int, Dict[str, Any]]:
""" """
Supports the new format: Supports the new format:
robot_data["links"]["Board"]["markers"] robot_data["links"]["Board"]["markers"]
Fallback: Fallback:
robot_data["Marker"] robot_data["Marker"]
ref_set: wenn angegeben, werden nur Marker mit passendem "set"-Feld als Referenz verwendet.
""" """
robot_json_path = resolve_path(robot_json_path) robot_json_path = resolve_path(robot_json_path)
with open(robot_json_path, "r", encoding="utf-8") as f: with open(robot_json_path, "r", encoding="utf-8") as f:
@@ -248,6 +250,10 @@ def load_marker_lookup(robot_json_path: str) -> Dict[int, Dict[str, Any]]:
if marker_id < 0: if marker_id < 0:
continue continue
# Referenz-Set-Filter: nur Marker mit passendem set-Wert verwenden
if ref_set and str(marker.get("set", "")) != ref_set:
continue
if "position" not in marker: if "position" not in marker:
continue continue
@@ -583,13 +589,18 @@ def main() -> None:
parser.add_argument("--maxRmsPx", type=float, default=None, parser.add_argument("--maxRmsPx", type=float, default=None,
help="Optional soft warning threshold for final reprojection RMS in pixels") help="Optional soft warning threshold for final reprojection RMS in pixels")
parser.add_argument("--epsJac", type=float, default=1e-6, help="Finite-difference epsilon") parser.add_argument("--epsJac", type=float, default=1e-6, help="Finite-difference epsilon")
parser.add_argument("--refSet", default=None,
help="Nur Marker dieses Sets als Referenz verwenden (z.B. 'A0', 'rail'). "
"Leer = alle Marker aus links.Board.")
args = parser.parse_args() args = parser.parse_args()
detection_path = resolve_path(args.input) detection_path = resolve_path(args.input)
robot_path = resolve_path(args.robot) robot_path = resolve_path(args.robot)
detection = load_json(detection_path) detection = load_json(detection_path)
marker_lookup = load_marker_lookup(robot_path) marker_lookup = load_marker_lookup(robot_path, ref_set=args.refSet)
if args.refSet:
print(f"[INFO] Referenz-Set: '{args.refSet}'{len(marker_lookup)} Referenz-Marker")
K, D = load_intrinsics_from_detection(detection) K, D = load_intrinsics_from_detection(detection)

View File

@@ -491,6 +491,8 @@ app.post('/api/board/run', async (req, res) => {
if (!res.writableEnded) res.write(`data: ${JSON.stringify(obj)}\n\n`); if (!res.writableEnded) res.write(`data: ${JSON.stringify(obj)}\n\n`);
}; };
const { refSet } = req.body ?? {};
// 1. Temp-Verzeichnis // 1. Temp-Verzeichnis
const ts = makeTimestamp(); const ts = makeTimestamp();
const runDir = path.join(boardDataDir, ts); const runDir = path.join(boardDataDir, ts);
@@ -501,9 +503,14 @@ app.post('/api/board/run', async (req, res) => {
// Robot-JSON laden und Marker-Anzahl loggen // Robot-JSON laden und Marker-Anzahl loggen
let robotData = null; let robotData = null;
try { robotData = JSON.parse(await fsPromises.readFile(ROBOT_JSON, 'utf8')); } catch {} try { robotData = JSON.parse(await fsPromises.readFile(ROBOT_JSON, 'utf8')); } catch {}
const boardMarkerCount = robotData?.links?.Board?.markers?.length ?? '?'; const boardMarkers = robotData?.links?.Board?.markers ?? [];
const boardMarkerCount = boardMarkers.length;
const refMarkerCount = refSet
? boardMarkers.filter(m => m.set === refSet).length
: boardMarkerCount;
send({ type: 'log', text: `▶ Robot-JSON: ${ROBOT_JSON}` }); send({ type: 'log', text: `▶ Robot-JSON: ${ROBOT_JSON}` });
send({ type: 'log', text: `▶ Board-Marker (A0): ${boardMarkerCount} Marker aus links.Board.markers` }); send({ type: 'log', text: `▶ Board-Marker: ${boardMarkerCount} (links.Board.markers)` });
send({ type: 'log', text: `▶ Referenz-Set: ${refSet ? `"${refSet}" (${refMarkerCount} Marker)` : 'alle'}` });
send({ type: 'log', text: '' }); send({ type: 'log', text: '' });
// 2. Kameras ermitteln // 2. Kameras ermitteln
@@ -567,12 +574,9 @@ app.post('/api/board/run', async (req, res) => {
} }
send({ type: 'log', text: '\n▷ 2_estimate_camera_from_observations' }); send({ type: 'log', text: '\n▷ 2_estimate_camera_from_observations' });
const exit2 = await runScript([ const script2Args = [SCRIPT_2, '-i', detJson, '-robot', ROBOT_JSON, '-outDir', runDir];
SCRIPT_2, if (refSet) script2Args.push('--refSet', refSet);
'-i', detJson, const exit2 = await runScript(script2Args, send);
'-robot', ROBOT_JSON,
'-outDir', runDir,
], send);
if (exit2 !== 0) { if (exit2 !== 0) {
send({ type: 'log', text: `❌ Script 2 Exit ${exit2}` }); send({ type: 'log', text: `❌ Script 2 Exit ${exit2}` });
} }
@@ -753,6 +757,23 @@ app.post('/api/robot/remove-marker', async (req, res) => {
} }
}); });
/**
* GET /api/robot/board-sets
* Gibt die einzigartigen "set"-Werte aller Marker in links.Board zurück.
* Wird vom Frontend genutzt, um Dropdowns zu befüllen.
*/
app.get('/api/robot/board-sets', async (req, res) => {
try {
const robot = JSON.parse(await fsPromises.readFile(ROBOT_JSON, 'utf8'));
const markers = robot?.links?.Board?.markers ?? [];
const sets = [...new Set(markers.map(m => m.set).filter(Boolean))].sort();
return res.json({ sets });
} catch (err) {
console.error('robot/board-sets error:', err);
return res.status(500).json({ error: String(err) });
}
});
/** /**
* POST /api/robot/align-sets * POST /api/robot/align-sets
* Richtet alle Marker des angegebenen Sets rigid (2D-Rotation um Z + 3D-Translation) * Richtet alle Marker des angegebenen Sets rigid (2D-Rotation um Z + 3D-Translation)
@@ -761,7 +782,7 @@ app.post('/api/robot/remove-marker', async (req, res) => {
*/ */
app.post('/api/robot/align-sets', async (req, res) => { app.post('/api/robot/align-sets', async (req, res) => {
try { try {
const { setToMove } = req.body ?? {}; const { setToMove, setFixed } = req.body ?? {};
if (!setToMove) return res.status(400).json({ error: '"setToMove" ist erforderlich.' }); if (!setToMove) return res.status(400).json({ error: '"setToMove" ist erforderlich.' });
let extraMarkers = []; let extraMarkers = [];
@@ -778,7 +799,7 @@ app.post('/api/robot/align-sets', async (req, res) => {
if (result.error) return res.status(400).json(result); if (result.error) return res.status(400).json(result);
console.log( console.log(
`robot/align-sets set="${setToMove}" → ${result.numChanged} Marker verschoben` + `robot/align-sets fixed="${setFixed ?? ''}" move="${setToMove}" → ${result.numChanged} Marker` +
` (${result.numMatchingPts} Messpunkte) Δx=${result.transform.tx} Δy=${result.transform.ty}` + ` (${result.numMatchingPts} Messpunkte) Δx=${result.transform.tx} Δy=${result.transform.ty}` +
` Δz=${result.transform.tz} mm θ=${result.transform.thetaDeg}°`, ` Δz=${result.transform.tz} mm θ=${result.transform.thetaDeg}°`,
); );