Liste CSV
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user