Callibration Board 0
This commit is contained in:
214
server/server.js
214
server/server.js
@@ -260,7 +260,14 @@ async function capturePhotos(sessionName) {
|
||||
|
||||
const savedFiles = [];
|
||||
for (const camId of cameraIds) {
|
||||
const response = await new WebcamClient(WEBCAM_URL).getSnapshot(camId, true);
|
||||
let response;
|
||||
// Bei 503 (Kamera kurz busy nach Hires-Grab) einmal nach 2 s neu versuchen
|
||||
for (let attempt = 1; attempt <= 2; attempt++) {
|
||||
response = await new WebcamClient(WEBCAM_URL).getSnapshot(camId, true);
|
||||
if (response.status !== 503) break;
|
||||
if (attempt < 2) await new Promise(r => setTimeout(r, 2000));
|
||||
}
|
||||
if (!response.ok) throw new Error(`getSnapshot(${camId}): HTTP ${response.status}`);
|
||||
const buffer = Buffer.from(await response.arrayBuffer());
|
||||
const filename = `${camId}_${setNr}.jpg`;
|
||||
await fsPromises.writeFile(path.join(sessionDir, filename), buffer);
|
||||
@@ -371,44 +378,15 @@ app.post('/api/calibration/compute', async (req, res) => {
|
||||
send({ type: 'log', text: `▶ Script: ${calibScriptPath}` });
|
||||
send({ type: 'log', text: '' });
|
||||
|
||||
// -u = unbuffered (Python gibt jede Zeile sofort aus)
|
||||
const proc = spawn(PYTHON_BIN, [
|
||||
'-u',
|
||||
const exitCode = await runScript([
|
||||
calibScriptPath,
|
||||
'--camera', camera,
|
||||
'--input-dir', sessionDir,
|
||||
'--output-dir', sessionDir,
|
||||
]);
|
||||
], send);
|
||||
|
||||
let stdoutBuf = '';
|
||||
proc.stdout.on('data', (chunk) => {
|
||||
stdoutBuf += chunk.toString();
|
||||
const lines = stdoutBuf.split('\n');
|
||||
stdoutBuf = lines.pop();
|
||||
for (const line of lines) send({ type: 'log', text: line });
|
||||
});
|
||||
|
||||
let stderrBuf = '';
|
||||
proc.stderr.on('data', (chunk) => {
|
||||
stderrBuf += chunk.toString();
|
||||
const lines = stderrBuf.split('\n');
|
||||
stderrBuf = lines.pop();
|
||||
for (const line of lines) send({ type: 'log', text: `[stderr] ${line}` });
|
||||
});
|
||||
|
||||
proc.on('error', (err) => {
|
||||
console.error('calibration/compute spawn error:', err);
|
||||
send({ type: 'log', text: `Fehler beim Starten: ${err.message}` });
|
||||
send({ type: 'done', exitCode: -1 });
|
||||
if (!res.writableEnded) res.end();
|
||||
});
|
||||
|
||||
proc.on('close', (code) => {
|
||||
if (stdoutBuf) send({ type: 'log', text: stdoutBuf });
|
||||
if (stderrBuf) send({ type: 'log', text: `[stderr] ${stderrBuf}` });
|
||||
send({ type: 'done', exitCode: code ?? -1 });
|
||||
if (!res.writableEnded) res.end();
|
||||
});
|
||||
send({ type: 'done', exitCode });
|
||||
if (!res.writableEnded) res.end();
|
||||
|
||||
} catch (err) {
|
||||
// Fehler VOR flushHeaders → normaler JSON-Fehler
|
||||
@@ -426,6 +404,174 @@ app.post('/api/calibration/compute', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── Board-Erkennung ───────────────────────────────────────────────────────────
|
||||
|
||||
const boardDataDir = path.join(__dirname, '..', 'data', 'board');
|
||||
const ROBOT_JSON = process.env.ROBOT_JSON
|
||||
|| path.join(__dirname, '..', 'scripts', 'robot_1781069752019.json');
|
||||
const SCRIPT_1 = path.join(__dirname, '..', 'scripts', '1_detect_aruco_observations.py');
|
||||
const SCRIPT_2 = path.join(__dirname, '..', 'scripts', '2_estimate_camera_from_observations.py');
|
||||
|
||||
/**
|
||||
* Führt ein Python-Script aus und leitet stdout/stderr zeilenweise an `send` weiter.
|
||||
* Gibt den Exit-Code zurück (Promise<number>).
|
||||
*/
|
||||
function runScript(args, send) {
|
||||
return new Promise((resolve) => {
|
||||
const proc = spawn(PYTHON_BIN, ['-u', ...args]);
|
||||
|
||||
let outBuf = '';
|
||||
proc.stdout.on('data', chunk => {
|
||||
outBuf += chunk.toString();
|
||||
const lines = outBuf.split('\n');
|
||||
outBuf = lines.pop();
|
||||
for (const line of lines) send({ type: 'log', text: line });
|
||||
});
|
||||
|
||||
let errBuf = '';
|
||||
proc.stderr.on('data', chunk => {
|
||||
errBuf += chunk.toString();
|
||||
const lines = errBuf.split('\n');
|
||||
errBuf = lines.pop();
|
||||
for (const line of lines) send({ type: 'log', text: `[stderr] ${line}` });
|
||||
});
|
||||
|
||||
proc.on('error', err => {
|
||||
send({ type: 'log', text: `Fehler beim Starten: ${err.message}` });
|
||||
resolve(-1);
|
||||
});
|
||||
|
||||
proc.on('close', code => {
|
||||
if (outBuf) send({ type: 'log', text: outBuf });
|
||||
if (errBuf) send({ type: 'log', text: `[stderr] ${errBuf}` });
|
||||
resolve(code ?? -1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/board/run
|
||||
* 1. Erstellt data/board/{timestamp}/
|
||||
* 2. Holt Snapshot jeder Kamera
|
||||
* 3. Für jede Kamera: Script 1 (ArUco-Erkennung) → Script 2 (Kamera-Pose)
|
||||
* SSE-Stream während der Ausführung.
|
||||
*/
|
||||
app.post('/api/board/run', async (req, res) => {
|
||||
try {
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
res.flushHeaders();
|
||||
|
||||
const send = (obj) => {
|
||||
if (!res.writableEnded) res.write(`data: ${JSON.stringify(obj)}\n\n`);
|
||||
};
|
||||
|
||||
// 1. Temp-Verzeichnis
|
||||
const ts = makeTimestamp();
|
||||
const runDir = path.join(boardDataDir, ts);
|
||||
await fsPromises.mkdir(runDir, { recursive: true });
|
||||
send({ type: 'log', text: `▶ Board-Run: ${ts}` });
|
||||
send({ type: 'log', text: `▶ Ordner: ${runDir}` });
|
||||
send({ type: 'log', text: `▶ Robot-JSON: ${ROBOT_JSON}` });
|
||||
send({ type: 'log', text: '' });
|
||||
|
||||
// 2. Kameras ermitteln
|
||||
if (!WEBCAM_URL) throw new Error('WEBCAM_URL nicht konfiguriert');
|
||||
const camData = await new WebcamClient(WEBCAM_URL).getCameras();
|
||||
const cameraIds = (camData.cameras ?? []).map(c => c.id);
|
||||
send({ type: 'log', text: `▶ Kameras: ${cameraIds.join(', ')}` });
|
||||
|
||||
// 3. Aktuelle Kalibrierungs-Session für NPZ-Dateien
|
||||
const calibSession = await findLatestCalibSession();
|
||||
if (!calibSession) throw new Error('Keine Kalibrierungs-Session. Bitte zuerst Camera NPZ kalibrieren.');
|
||||
send({ type: 'log', text: `▶ NPZ-Session: ${calibSession}` });
|
||||
send({ type: 'log', text: '' });
|
||||
|
||||
// 4. Pro Kamera: Foto → Script 1 → Script 2
|
||||
for (const camId of cameraIds) {
|
||||
send({ type: 'log', text: `─── ${camId} ${'─'.repeat(40 - camId.length)}` });
|
||||
|
||||
// Snapshot
|
||||
send({ type: 'log', text: 'Foto aufnehmen …' });
|
||||
let snapResp;
|
||||
for (let attempt = 1; attempt <= 2; attempt++) {
|
||||
snapResp = await new WebcamClient(WEBCAM_URL).getSnapshot(camId, true);
|
||||
if (snapResp.status !== 503) break;
|
||||
if (attempt < 2) await new Promise(r => setTimeout(r, 2000));
|
||||
}
|
||||
if (!snapResp.ok) {
|
||||
send({ type: 'log', text: `⚠ HTTP ${snapResp.status} – Kamera übersprungen` });
|
||||
continue;
|
||||
}
|
||||
const imgPath = path.join(runDir, `${camId}.jpg`);
|
||||
await fsPromises.writeFile(imgPath, Buffer.from(await snapResp.arrayBuffer()));
|
||||
send({ type: 'log', text: `✅ Foto: ${camId}.jpg` });
|
||||
|
||||
// NPZ prüfen
|
||||
const npzPath = path.join(calibDataDir, calibSession, `${camId}_calibration.npz`);
|
||||
try { await fsPromises.access(npzPath); }
|
||||
catch {
|
||||
send({ type: 'log', text: `⚠ Keine NPZ (${camId}_calibration.npz) – übersprungen` });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Script 1 – ArUco-Erkennung
|
||||
send({ type: 'log', text: '\n▷ 1_detect_aruco_observations' });
|
||||
const exit1 = await runScript([
|
||||
SCRIPT_1,
|
||||
'-i', imgPath,
|
||||
'-npz', npzPath,
|
||||
'-robot', ROBOT_JSON,
|
||||
'-cameraId', camId,
|
||||
'-outDir', runDir,
|
||||
'--saveDebugImage',
|
||||
], send);
|
||||
if (exit1 !== 0) {
|
||||
send({ type: 'log', text: `❌ Script 1 Exit ${exit1}` });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Script 2 – Kamera-Pose schätzen
|
||||
const detJson = path.join(runDir, `${camId}_aruco_detection.json`);
|
||||
try { await fsPromises.access(detJson); }
|
||||
catch {
|
||||
send({ type: 'log', text: '⚠ Detection-JSON fehlt – Script 2 übersprungen' });
|
||||
continue;
|
||||
}
|
||||
|
||||
send({ type: 'log', text: '\n▷ 2_estimate_camera_from_observations' });
|
||||
const exit2 = await runScript([
|
||||
SCRIPT_2,
|
||||
'-i', detJson,
|
||||
'-robot', ROBOT_JSON,
|
||||
'-outDir', runDir,
|
||||
], send);
|
||||
if (exit2 !== 0) {
|
||||
send({ type: 'log', text: `❌ Script 2 Exit ${exit2}` });
|
||||
}
|
||||
|
||||
send({ type: 'log', text: '' });
|
||||
}
|
||||
|
||||
send({ type: 'log', text: `✅ Board-Run abgeschlossen: ${ts}` });
|
||||
send({ type: 'done', exitCode: 0, runDir: ts });
|
||||
if (!res.writableEnded) res.end();
|
||||
|
||||
} catch (err) {
|
||||
console.error('board/run error:', err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: String(err) });
|
||||
} else {
|
||||
try {
|
||||
res.write(`data: ${JSON.stringify({ type: 'log', text: `❌ ${err.message}` })}\n\n`);
|
||||
res.write(`data: ${JSON.stringify({ type: 'done', exitCode: -1 })}\n\n`);
|
||||
res.end();
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/calibration/upload-npz
|
||||
* Liest {camera}_calibration.npz aus der aktuellen Session und
|
||||
|
||||
Reference in New Issue
Block a user