diff --git a/public/calibration.js b/public/calibration.js
index 953d79d..223572b 100644
--- a/public/calibration.js
+++ b/public/calibration.js
@@ -354,6 +354,27 @@ async function loadBoardTable() {
// ── 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:
- Set verschieben
-
+
+ bleibt /
+
+ wird verschoben
- Rotation (Z-Achse) + Translation → passt Set zu 3b-Messung
+ Rotation (Z-Achse) + Translation → passt verschobenes Set zu 3b-Messung
diff --git a/scripts/2_estimate_camera_from_observations.py b/scripts/2_estimate_camera_from_observations.py
index f6197c7..4b28917 100644
--- a/scripts/2_estimate_camera_from_observations.py
+++ b/scripts/2_estimate_camera_from_observations.py
@@ -212,13 +212,15 @@ def get_marker_rotation(marker: Dict[str, Any]) -> np.ndarray:
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:
robot_data["links"]["Board"]["markers"]
Fallback:
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)
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:
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:
continue
@@ -583,13 +589,18 @@ def main() -> None:
parser.add_argument("--maxRmsPx", type=float, default=None,
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("--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()
detection_path = resolve_path(args.input)
robot_path = resolve_path(args.robot)
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)
diff --git a/scripts/__pycache__/2_estimate_camera_from_observations.cpython-311.pyc b/scripts/__pycache__/2_estimate_camera_from_observations.cpython-311.pyc
new file mode 100644
index 0000000..3e51d55
Binary files /dev/null and b/scripts/__pycache__/2_estimate_camera_from_observations.cpython-311.pyc differ
diff --git a/server/server.js b/server/server.js
index 906b668..419bd53 100755
--- a/server/server.js
+++ b/server/server.js
@@ -491,6 +491,8 @@ app.post('/api/board/run', async (req, res) => {
if (!res.writableEnded) res.write(`data: ${JSON.stringify(obj)}\n\n`);
};
+ const { refSet } = req.body ?? {};
+
// 1. Temp-Verzeichnis
const ts = makeTimestamp();
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
let robotData = null;
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: `▶ 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: '' });
// 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' });
- const exit2 = await runScript([
- SCRIPT_2,
- '-i', detJson,
- '-robot', ROBOT_JSON,
- '-outDir', runDir,
- ], send);
+ const script2Args = [SCRIPT_2, '-i', detJson, '-robot', ROBOT_JSON, '-outDir', runDir];
+ if (refSet) script2Args.push('--refSet', refSet);
+ const exit2 = await runScript(script2Args, send);
if (exit2 !== 0) {
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
* 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) => {
try {
- const { setToMove } = req.body ?? {};
+ const { setToMove, setFixed } = req.body ?? {};
if (!setToMove) return res.status(400).json({ error: '"setToMove" ist erforderlich.' });
let extraMarkers = [];
@@ -778,7 +799,7 @@ app.post('/api/robot/align-sets', async (req, res) => {
if (result.error) return res.status(400).json(result);
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}` +
` Δz=${result.transform.tz} mm θ=${result.transform.thetaDeg}°`,
);