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;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
min-height: 100vh;
overflow-y: auto;
}
#topbar {
display: flex;
@@ -51,12 +51,63 @@
font-size: 11px;
}
.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%; }
#hint {
position: absolute; bottom: 6px; right: 10px;
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>
<script type="importmap">
{
@@ -88,6 +139,8 @@
<div id="hint">Orbit · Scroll · Rechte Taste = Pan</div>
</div>
<div id="table-wrap"></div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -344,6 +397,142 @@ function buildScene(data) {
(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 ───────────────────────────────────────────────────────────────
async function loadData() {
const statusEl = document.getElementById('status');
@@ -359,6 +548,7 @@ async function loadData() {
return;
}
buildScene(data);
buildTable(data);
const robotLabel = data.robotFile ? ` • Robot: ${data.robotFile}` : '';
statusEl.textContent = `Run: ${data.runDir}${robotLabel}${new Date().toLocaleTimeString('de-CH')}`;
} catch (err) {

View File

@@ -33,7 +33,7 @@
<iframe
id="board-viewer-frame"
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"
></iframe>
</div>

View File

@@ -22,8 +22,10 @@ und mit 9_evaluateMarker.py (position_m), erweitert um die gemessene Orientierun
from __future__ import annotations
import argparse
import csv
import glob
import json
import math
import os
import re
import time
@@ -65,14 +67,18 @@ def load_cameras(eval_dir: str) -> Dict[str, dict]:
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"))
out: Dict[int, str] = {}
out: Dict[int, dict] = {}
for link_name, link in (robot.get("links", {}) or {}).items():
for mk in link.get("markers", []) or []:
mid = int(mk.get("id", -1))
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
@@ -122,8 +128,8 @@ def main() -> None:
if len(cams) < 2:
print("[ERROR] need >=2 cameras")
return
links = load_marker_links(args.robot)
print(f"[INFO] Cameras: {sorted(cams.keys())} | marker-link entries: {len(links)}")
marker_info = load_marker_info(args.robot)
print(f"[INFO] Cameras: {sorted(cams.keys())} | marker-info entries: {len(marker_info)}")
marker_cams: Dict[int, List[str]] = {}
for cid, cam in cams.items():
@@ -151,7 +157,7 @@ def main() -> None:
markers_out.append({
"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_mm": [float(v * 1000.0) for v in center],
"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)
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__":
main()