Liste CSV

This commit is contained in:
chk
2026-06-10 16:25:30 +02:00
parent b1e4a8b0be
commit 97ff7eb33f
3 changed files with 256 additions and 10 deletions

View File

@@ -19,8 +19,8 @@
font: 11px/1.5 'IBM Plex Mono', 'Cascadia Code', 'Courier New', monospace; font: 11px/1.5 'IBM Plex Mono', 'Cascadia Code', 'Courier New', monospace;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; min-height: 100vh;
overflow: hidden; overflow-y: auto;
} }
#topbar { #topbar {
display: flex; display: flex;
@@ -51,12 +51,63 @@
font-size: 11px; font-size: 11px;
} }
.btn:hover { border-color: var(--accent); color: var(--accent); } .btn:hover { border-color: var(--accent); color: var(--accent); }
#canvas-wrap { flex: 1; position: relative; overflow: hidden; } #canvas-wrap { flex: 1; min-height: 360px; position: relative; overflow: hidden; }
canvas { display: block; width: 100%; height: 100%; } canvas { display: block; width: 100%; height: 100%; }
#hint { #hint {
position: absolute; bottom: 6px; right: 10px; position: absolute; bottom: 6px; right: 10px;
font-size: 9px; color: var(--muted); pointer-events: none; font-size: 9px; color: var(--muted); pointer-events: none;
} }
/* ── Daten-Tabelle ── */
#table-wrap {
flex-shrink: 0;
max-height: 260px;
overflow-y: auto;
border-top: 1px solid var(--border);
background: var(--bg);
}
.tbl-head {
padding: 4px 10px 2px;
font-size: 9px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: .07em;
background: var(--panel);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
}
table.dtbl {
width: 100%;
border-collapse: collapse;
font-size: 10px;
}
table.dtbl th {
position: sticky;
top: 0;
background: var(--panel);
color: var(--muted);
font-weight: normal;
text-align: right;
padding: 2px 7px;
border-bottom: 1px solid var(--border);
white-space: nowrap;
}
table.dtbl th:first-child,
table.dtbl th:nth-child(2) { text-align: left; }
table.dtbl td {
text-align: right;
padding: 1px 7px;
border-bottom: 1px solid #111418;
white-space: nowrap;
}
table.dtbl td:first-child,
table.dtbl td:nth-child(2) { text-align: left; }
table.dtbl tr:hover td { background: #1a1f2b; }
.row-1cam td:first-child::before { content: ''; }
.cell-hi { color: #fbbf24; } /* amber: trianguliert */
.cell-lo { color: #fde68a; } /* hell: nur 2D */
.cell-unk { color: #3b82f6; } /* blau: fremd */
.cell-mut { color: var(--muted); }
</style> </style>
<script type="importmap"> <script type="importmap">
{ {
@@ -88,6 +139,8 @@
<div id="hint">Orbit · Scroll · Rechte Taste = Pan</div> <div id="hint">Orbit · Scroll · Rechte Taste = Pan</div>
</div> </div>
<div id="table-wrap"></div>
<script type="module"> <script type="module">
import * as THREE from 'three'; import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -344,6 +397,142 @@ function buildScene(data) {
(camInfo ? ` │ RMS: ${camInfo}` : ''); (camInfo ? ` │ RMS: ${camInfo}` : '');
} }
// ── Daten-Tabelle ─────────────────────────────────────────────────────────────
function buildTable(data) {
const wrap = document.getElementById('table-wrap');
if (!wrap) return;
const { robot, detections, cameraPoses, measuredMarkers } = data;
const boardMarkers = robot?.links?.Board?.markers ?? [];
// Marker → Liste der Kameras, die ihn gesehen haben
const camerasByMkr = {};
for (const det of (detections ?? [])) {
for (const id of (det.detectedMarkerIds ?? [])) {
(camerasByMkr[id] ??= []).push(det.cameraId);
}
}
// measuredMarkers indiziert nach marker_id
const measuredMap = {};
for (const m of (measuredMarkers?.markers ?? [])) measuredMap[m.marker_id] = m;
// Modell-Positionen indiziert
const modelMap = {};
for (const m of boardMarkers) modelMap[m.id] = m.position; // [x,y,z] mm
// Alle relevanten IDs (erkannt oder trianguliert)
const allIds = new Set([
...(detections ?? []).flatMap(d => d.detectedMarkerIds ?? []),
...Object.keys(measuredMap).map(Number),
]);
const f1 = v => (v == null ? '' : Number(v).toFixed(1));
const f2 = v => (v == null ? '' : Number(v).toFixed(2));
const f4 = v => (v == null ? '' : Number(v).toFixed(4));
// Zeilen zusammenstellen
const rows = [...allIds].map(id => {
const meas = measuredMap[id];
const model = modelMap[id]; // null wenn nicht in Board
const link = meas?.link ?? (model ? 'Board' : 'unknown');
const cameras = camerasByMkr[id] ?? [];
const numCam = meas?.num_cameras ?? cameras.length;
let x = null, y = null, z = null;
let nx = null, ny = null, nz = null;
let dist = null, dz = null, edge = null;
let state = 'none'; // 'tri', '1cam', 'unk'
if (meas) {
[x, y, z] = meas.position_mm;
[nx, ny, nz] = meas.normal;
edge = meas.edge_length_mm;
state = model ? 'tri' : 'unk';
if (model) {
const [mx, my, mz] = model;
const dx = x - mx, dy = y - my, ddz = z - mz;
dist = Math.sqrt(dx*dx + dy*dy + ddz*ddz);
dz = ddz;
}
} else if (cameras.length > 0) {
state = '1cam';
}
return { id, link, numCam, x, y, z, nx, ny, nz, dist, dz, edge, state };
}).sort((a, b) => {
// Reihenfolge: Board tri → Board 1cam → unknown → Rest; innerhalb nach ID
const rank = r => r.state === 'tri' ? 0 : r.state === '1cam' ? 1 : r.state === 'unk' ? 2 : 3;
return rank(a) - rank(b) || a.id - b.id;
});
// ── Marker-Tabelle ──
const stateStyle = { tri: 'cell-hi', '1cam': 'cell-lo', unk: 'cell-unk', none: 'cell-mut' };
const stateLabel = { tri: '▲', '1cam': '●', unk: '◆', none: '' };
let html = `
<div class="tbl-head">Erkannte Marker (${rows.length})</div>
<table class="dtbl">
<thead><tr>
<th>ID</th><th>Link</th><th>Kam.</th>
<th>x mm</th><th>y mm</th><th>z mm</th>
<th>nx</th><th>ny</th><th>nz</th>
<th>dist mm</th><th>Δz mm</th><th>Kante mm</th>
</tr></thead>
<tbody>`;
for (const r of rows) {
const cs = stateStyle[r.state] ?? '';
html += `<tr>
<td class="${cs}">${stateLabel[r.state]} ${r.id}</td>
<td>${r.link}</td>
<td>${r.numCam}</td>
<td>${f1(r.x)}</td><td>${f1(r.y)}</td><td>${f1(r.z)}</td>
<td>${f4(r.nx)}</td><td>${f4(r.ny)}</td><td>${f4(r.nz)}</td>
<td>${f2(r.dist)}</td><td>${f2(r.dz)}</td><td>${f1(r.edge)}</td>
</tr>`;
}
html += `</tbody></table>`;
// ── Kamera-Tabelle ──
const camRows = measuredMarkers?.cameras?.length
? measuredMarkers.cameras.map(c => ({
id: c.camera_id,
pos: c.position_mm,
dir: c.direction,
}))
: (cameraPoses ?? []).map(cp => ({
id: cp.cameraId,
pos: cp.position_mm,
dir: cp.rotation_matrix?.[2] ?? null,
}));
if (camRows.length > 0) {
html += `
<div class="tbl-head" style="margin-top:0">Kameras (${camRows.length})</div>
<table class="dtbl">
<thead><tr>
<th>ID</th>
<th>x mm</th><th>y mm</th><th>z mm</th>
<th>dir_x</th><th>dir_y</th><th>dir_z</th>
</tr></thead>
<tbody>`;
for (const c of camRows) {
const [cx, cy, cz] = c.pos ?? [null, null, null];
const [dx, dy, dz] = c.dir ?? [null, null, null];
html += `<tr>
<td style="color:var(--accent)">${c.id}</td>
<td>${f1(cx)}</td><td>${f1(cy)}</td><td>${f1(cz)}</td>
<td>${f4(dx)}</td><td>${f4(dy)}</td><td>${f4(dz)}</td>
</tr>`;
}
html += `</tbody></table>`;
}
wrap.innerHTML = html;
}
// ── Daten laden ─────────────────────────────────────────────────────────────── // ── Daten laden ───────────────────────────────────────────────────────────────
async function loadData() { async function loadData() {
const statusEl = document.getElementById('status'); const statusEl = document.getElementById('status');
@@ -359,6 +548,7 @@ async function loadData() {
return; return;
} }
buildScene(data); buildScene(data);
buildTable(data);
const robotLabel = data.robotFile ? ` • Robot: ${data.robotFile}` : ''; const robotLabel = data.robotFile ? ` • Robot: ${data.robotFile}` : '';
statusEl.textContent = `Run: ${data.runDir}${robotLabel}${new Date().toLocaleTimeString('de-CH')}`; statusEl.textContent = `Run: ${data.runDir}${robotLabel}${new Date().toLocaleTimeString('de-CH')}`;
} catch (err) { } catch (err) {

View File

@@ -33,7 +33,7 @@
<iframe <iframe
id="board-viewer-frame" id="board-viewer-frame"
src="/boardViewer.html" src="/boardViewer.html"
style="width: 100%; height: 520px; border: 1px solid #334155; border-radius: 6px; background: #0d0f13; display: block;" style="width: 100%; height: 740px; border: 1px solid #334155; border-radius: 6px; background: #0d0f13; display: block;"
title="Board-Viewer" title="Board-Viewer"
></iframe> ></iframe>
</div> </div>

View File

@@ -22,8 +22,10 @@ und mit 9_evaluateMarker.py (position_m), erweitert um die gemessene Orientierun
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import csv
import glob import glob
import json import json
import math
import os import os
import re import re
import time import time
@@ -65,14 +67,18 @@ def load_cameras(eval_dir: str) -> Dict[str, dict]:
return cams return cams
def load_marker_links(robot_path: str) -> Dict[int, str]: def load_marker_info(robot_path: str) -> Dict[int, dict]:
"""Returns {marker_id: {"link": ..., "position_mm": [x,y,z]}} for every marker in robot.json."""
robot = json.load(open(robot_path, "r", encoding="utf-8")) robot = json.load(open(robot_path, "r", encoding="utf-8"))
out: Dict[int, str] = {} out: Dict[int, dict] = {}
for link_name, link in (robot.get("links", {}) or {}).items(): for link_name, link in (robot.get("links", {}) or {}).items():
for mk in link.get("markers", []) or []: for mk in link.get("markers", []) or []:
mid = int(mk.get("id", -1)) mid = int(mk.get("id", -1))
if mid >= 0: if mid >= 0:
out[mid] = link_name out[mid] = {
"link": link_name,
"position_mm": mk.get("position"), # [x_mm, y_mm, z_mm] robot coords
}
return out return out
@@ -122,8 +128,8 @@ def main() -> None:
if len(cams) < 2: if len(cams) < 2:
print("[ERROR] need >=2 cameras") print("[ERROR] need >=2 cameras")
return return
links = load_marker_links(args.robot) marker_info = load_marker_info(args.robot)
print(f"[INFO] Cameras: {sorted(cams.keys())} | marker-link entries: {len(links)}") print(f"[INFO] Cameras: {sorted(cams.keys())} | marker-info entries: {len(marker_info)}")
marker_cams: Dict[int, List[str]] = {} marker_cams: Dict[int, List[str]] = {}
for cid, cam in cams.items(): for cid, cam in cams.items():
@@ -151,7 +157,7 @@ def main() -> None:
markers_out.append({ markers_out.append({
"marker_id": int(mid), "marker_id": int(mid),
"link": links.get(mid, "unknown"), "link": marker_info.get(mid, {}).get("link", "unknown"),
"position_m": [float(v) for v in center], "position_m": [float(v) for v in center],
"position_mm": [float(v * 1000.0) for v in center], "position_mm": [float(v * 1000.0) for v in center],
"normal": [float(v) for v in normal], "normal": [float(v) for v in normal],
@@ -184,6 +190,56 @@ def main() -> None:
json.dump(output, open(out_path, "w", encoding="utf-8"), indent=2) json.dump(output, open(out_path, "w", encoding="utf-8"), indent=2)
print(f"[INFO] {len(markers_out)} marker poses -> {out_path}") print(f"[INFO] {len(markers_out)} marker poses -> {out_path}")
# ── CSV (Debug-Daten) ────────────────────────────────────────────────────────
csv_path = os.path.splitext(out_path)[0] + ".csv"
with open(csv_path, "w", newline="", encoding="utf-8") as fcsv:
w = csv.writer(fcsv)
# Marker-Tabelle
w.writerow(["marker_id", "link", "num_cameras",
"x_mm", "y_mm", "z_mm",
"nx", "ny", "nz",
"model_x_mm", "model_y_mm", "model_z_mm",
"dist_to_model_mm", "delta_z_mm",
"edge_length_mm"])
for m in markers_out:
mid = m["marker_id"]
link = m["link"]
mi = marker_info.get(mid, {})
mpos = mi.get("position_mm") # Modellposition aus robot.json
px, py, pz = m["position_mm"]
mnx, mny, mnz = m["normal"]
if mpos and link == "Board":
mx, my, mz = mpos
ddx, ddy, ddz = px - mx, py - my, pz - mz
dist_val = round(math.sqrt(ddx**2 + ddy**2 + ddz**2), 3)
dz_val = round(ddz, 3)
row_model = [round(mx, 2), round(my, 2), round(mz, 2), dist_val, dz_val]
else:
row_model = ["", "", "", "", ""]
w.writerow([
mid, link, m["num_cameras"],
round(px, 2), round(py, 2), round(pz, 2),
round(mnx, 5), round(mny, 5), round(mnz, 5),
*row_model,
round(m["edge_length_mm"], 2),
])
# Kamera-Tabelle (nach einer Leerzeile)
w.writerow([])
w.writerow(["camera_id", "x_mm", "y_mm", "z_mm", "dir_x", "dir_y", "dir_z"])
for c in cameras_out:
cx, cy, cz = c["position_mm"]
dx, dy, dz = c["direction"]
w.writerow([
c["camera_id"],
round(cx, 2), round(cy, 2), round(cz, 2),
round(dx, 5), round(dy, 5), round(dz, 5),
])
print(f"[INFO] CSV -> {csv_path}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()