diff --git a/public/calculateActions.js b/public/calculateActions.js index efcbe73..cda0702 100755 --- a/public/calculateActions.js +++ b/public/calculateActions.js @@ -718,9 +718,6 @@ if (typeof window !== "undefined") { if (typeof module !== "undefined") { module.exports = { calculate, - createAnalysisResult, - calculateAngleFromPosition, - calculateAngleFromRollColumn, - calculateAngleFromRelativePosition + createAnalysisResult }; } \ No newline at end of file diff --git a/public/calculateAngles.js b/public/calculateAngles.js index d3cde74..23b0582 100644 --- a/public/calculateAngles.js +++ b/public/calculateAngles.js @@ -21,12 +21,16 @@ function appendToAnalysis(line) { function calculateXPos(listIdAndX, jsonRobot){ - appendToAnalysis("Calculate XPos"); + appendToAnalysis("xPos"); const partsMovingFixedX = new Set(['Base', 'Arm1', 'Joint1']); const markersMovingFixedX = jsonRobot.Marker.filter(m => partsMovingFixedX.has(m.on)); + + appendToAnalysis(`xPos found ${markersMovingFixedX.length} Markers`); // Join: Robot-Marker ↔ Found-Marker + + const markersListeWithRobotInfo = jsonRobot.Marker .filter(m => partsMovingFixedX.has(m.on)) // nur relevante Teile .map(m => { @@ -48,6 +52,8 @@ function calculateXPos(listIdAndX, jsonRobot){ const variancePx = pxValues.reduce((sum, x) => sum + Math.pow(x - meanPx, 2), 0) / pxValues.length; const stdDevPx = Math.sqrt(variancePx); + + appendToAnalysis(`xPos > ${meanPx.toFixed(2)} ± ${stdDevPx.toFixed(2)}`) return { meanPx, stdDevPx }; } @@ -117,7 +123,63 @@ function optimizeRobot(listFoundMarkers, jsonRobot) { return withStats; } +function calculateRotationAngleByTwoMarkers(listIdAndX, jsonRobot, jointName){ + + // Achse finden + const jointInfo = jsonRobot.Joints[jointName]; + if(!jointInfo){return null, null; } + if(jointInfo.type !== 'revolute'){ return null, null; } + + if(!(jointInfo.axis)){ return null, null; } + if(!(jointInfo.child)){ return null, null; } + + var a = 1; + var b = 2; + if(jointInfo.axis == [1,0,0]){a=1, b=2} + if(jointInfo.axis == [0,1,0]){a=0, b=2} + if(jointInfo.axis == [0,0,1]){a=0, b=1} + + const achsisName = (jointName == "jointB") ? "y" : (jointName == "jointC") ? "z" : "a" + + + appendToAnalysis(`${achsisName}Ang - Double Started with on dir [${a}, ${b}]`) + + markerUsed = jsonRobot.Marker.filter(m => m.on === jointInfo.child) + if(markerUsed.length === 0){ + appendToAnalysis(`${achsisName}Ang - Double no Markers found`) + return null, null; + } + + const markerFound = markerUsed + .map(m => [m.id, listIdAndX.get(m.id)]) + .filter(v => v !== undefined && v[1] !== undefined); + + + const pairs = []; + for (let i = 0; i < markerFound.length; i++) { + for (let j = i + 1; j < markerFound.length; j++) { + const [idA, posA] = markerFound[i]; + const [idB, posB] = markerFound[j]; + + if (posA[a] === posB[a] && posA[b] === posB[b]) { + pairs.push([idA, idB]); + } + } + } + + if(pairs == []){ appendToAnalysis(`${achsisName}Ang - Double not found`); return; } + + + for(i in pairs){ + m0 = listIdAndX.get(i[0]); + m1 = listIdAndX.get(i[1]); + + appendToAnalysis(`${achsisName}Ang - Double ${JSON.stringify(m0)} and ${JSON.stringify(m1)} -- ${i}`); + } +} + function calculateRotationAngle(listIdAndX, jsonRobot, jointName, method = "tan") { + // Achse finden const jointInfo = jsonRobot.Joints[jointName]; if(!jointInfo){return null, null; } @@ -143,19 +205,25 @@ function calculateRotationAngle(listIdAndX, jsonRobot, jointName, method = "tan" b = 1; } + const achsisName = (jointName == "jointB") ? "y" : (jointName == "jointC") ? "z" : "a" + appendToAnalysis(`${achsisName}Ang - Started with ${method} on dir [${a}, ${b}]`) + const jointA = jointInfo.origin[a]; const jointB = jointInfo.origin[b]; + + appendToAnalysis(`${achsisName}Ang - Axis: (${jointA.toFixed(2)}, ${jointB.toFixed(2)})`); markerUsed = jsonRobot.Marker.filter(m => m.on === jointInfo.child) if(markerUsed.length === 0){ return null, null; } -const markerFound = markerUsed + const markerFound = markerUsed .map(m => [m.id, listIdAndX.get(m.id)]) .filter(v => v !== undefined && v[1] !== undefined); // Nur Marker, die gefunden wurden - var angles = []; + appendToAnalysis(`${achsisName}Ang found ${markerFound.length} markers`); + var angles = []; for(const pos of markerFound) { const id = pos[0]; const mRobot = jsonRobot.Marker.filter(m => m.id === id)[0]; @@ -168,6 +236,8 @@ const markerFound = markerUsed const angleOne = Math.atan2(db, da) * (180 / Math.PI); const deltaAngleTan = angleOne - angleZero; angles.push(deltaAngleTan); + + appendToAnalysis(`${achsisName}Ang tan: ${mRobot.id} Pos=(${pos[1][a].toFixed(2)}, ${pos[1][b].toFixed(2)}) Δ=(${da.toFixed(2)},${db.toFixed(2)}) α°=${angleZero.toFixed(2)} ${achsisName}=${deltaAngleTan.toFixed(2)}`); } else{ const hypotenuse = Math.sqrt(mRobot.relPos[a]**2 + mRobot.relPos[b]**2); @@ -176,15 +246,17 @@ const markerFound = markerUsed if(method === "sin"){ const db = pos[1][b] - jointB; var angleOneSin; - if(Math.abs(db) > hypotenuse && db < 1.3 * hypotenuse){angleOneSin = 180} + if(Math.abs(db) > hypotenuse && db < 1.3 * hypotenuse){angleOneSin = -180} else if(Math.abs(db) < hypotenuse && -1*Math.abs(db) > -hypotenuse){ angleOneSin = Math.asin(db / hypotenuse) * (180 / Math.PI); } - else if(Math.abs(db) < -1*hypotenuse && Math.abs(db) > 1.3*Math.abs(db)){angleOneSin = -180} + else if(Math.abs(db) < -1*hypotenuse && Math.abs(db) > 1.3*Math.abs(db)){angleOneSin = 180} else angleOneSin = NaN; const deltaAngleSin = angleOneSin - angleZero; angles.push(deltaAngleSin); + + appendToAnalysis(`${achsisName}Ang sin: ${mRobot.id} Pos=(${pos[1][a].toFixed(2)}, ${pos[1][b].toFixed(2)}) Δ=${db.toFixed(2)} hyp=${hypotenuse.toFixed(2)} α°=${angleOneSin.toFixed(2)} ${achsisName}=${deltaAngleSin.toFixed(2)}`); } // Arbeiten mit cos und hypotenuse else{ @@ -192,10 +264,11 @@ const markerFound = markerUsed const angleOneCos = Math.acos(db / hypotenuse) * (180 / Math.PI); const deltaAngleCos = -(angleOneCos - angleZero); angles.push(deltaAngleCos); + + appendToAnalysis(`${achsisName}Ang cos: ${mRobot.id} Pos=(${pos[1][a].toFixed(2)}, ${pos[1][b].toFixed(2)}) Δ=${db.toFixed(2)} hyp=${hypotenuse.toFixed(2)} α°=${angleOneCos.toFixed(2)} ${achsisName}=${deltaAngleCos.toFixed(2)}`); } } } - const n = angles.length; if(n === 0){ return null, null; } @@ -203,25 +276,39 @@ const markerFound = markerUsed const average = angles.reduce((a, b) => a + b, 0) / n; const deviation = Math.sqrt(angles.reduce((sum, x) => sum + Math.pow(x - average, 2), 0) / n); - + appendToAnalysis(`${achsisName}Ang ${achsisName}=${average.toFixed(2)} ± ${deviation.toFixed(2)}`) return {average, deviation}; } async function calculate(foundMarkers, jsonRobot) { + if(foundMarkers == undefined || jsonRobot == undefined){console.warn("calculateAngles mit falschen Parametern aufgerufen.");} + const foundById = new Map(foundMarkers.markers.map(f => [f.id, f.position_mm ])); const { meanPx: x, stdDevPx: varx } = calculateXPos(foundById, jsonRobot); jsonRobot.recognized.x = x; const { average: y, deviation: vary } = calculateRotationAngle(foundById, jsonRobot, "jointB", "tan"); - + + try{ + calculateRotationAngleByTwoMarkers(foundById, jsonRobot, "jointB"); + } + catch(e){ + appendToAnalysis(e); + } jsonRobot.recognized.y = y; + // ToDo ! callibration jsonRobot.Joints["jointD"].origin[0] = x; jsonRobot.Joints["jointD"].origin[1] = -jsonRobot.ElementLength["Arm1"]*Math.cos(y*Math.PI/180) jsonRobot.Joints["jointD"].origin[2] = jsonRobot.ElementLength["Arm1"]*Math.sin(y*Math.PI/180) const { average: a, deviation: vara } = calculateRotationAngle(foundById, jsonRobot, "jointD", "sin"); + + jsonRobot.Joints["jointC"].origin[0] = x; + jsonRobot.Joints["jointC"].origin[1] = -jsonRobot.ElementLength["Arm1"]*Math.cos(y*Math.PI/180) + jsonRobot.Joints["jointC"].origin[2] = jsonRobot.ElementLength["Arm1"]*Math.sin(y*Math.PI/180) + return { diff --git a/public/client.js b/public/client.js index 4b4124a..f66b597 100755 --- a/public/client.js +++ b/public/client.js @@ -84,10 +84,33 @@ function renderResult(result) { renderTree(treeEl, result, "result", true); } -async function fetchCSV() { +dataCache = null +headersCache = null +rowsCache = null +timeCache = null +jsonCache = null + +async function fetchCSV(){ + if(dataCache == null || headersCache == null || rowsCache == null || timeCache == null){ + ({ data: dataCache, headers: headersCache, rows: rowsCache } = await fetchCSV_fromServer()); + timeCache = Date.now(); + } + if (Date.now() - timeCache > 1000){ + + ({ data: dataCache, headers: headersCache, rows: rowsCache } = await fetchCSV_fromServer()); + timeCache = Date.now(); + } + + return {data: dataCache, headers: headersCache, rows: rowsCache} + +} + +async function fetchCSV_fromServer() { + const res = await fetch("/api/latest-snapshot"); if (!res.ok) throw new Error("Fehler beim Laden des Snapshots"); + let data; if (res.headers.get("content-type")?.includes("application/json")) { data = await res.json(); @@ -99,6 +122,10 @@ async function fetchCSV() { content: csvData }; } + + if (!jsonCache && data.jsonFile) { + jsonCache = JSON.parse(data.jsonFile.content); + } const lines = data.content.trim().split(/\r?\n/).filter(Boolean); if (lines.length < 2) { @@ -199,7 +226,15 @@ async function onCalculateClick() { appendLog("Starte Berechnung..."); try { - const result = await window.calculate(); + const response = await fetch("robot.json"); + await fetchCSV(); + + //console.log("Data:", dataCache); + //console.log("json: ", JSON.stringify(jsonCache)); + //console.log("Keys:", Object.keys(dataCache)); + + const robot = await response.json(); + const result = await window.calculate(jsonCache, robot); renderResult(result); await renderSnapshot(); appendLog("Result angezeigt."); diff --git a/public/index.html b/public/index.html index baba715..034a250 100755 --- a/public/index.html +++ b/public/index.html @@ -35,7 +35,7 @@ GCodeMotor - + @@ -86,12 +86,11 @@ - + + + - - - \ No newline at end of file diff --git a/public/robot.json b/public/robot.json index 4a930ca..929c332 100644 --- a/public/robot.json +++ b/public/robot.json @@ -4,7 +4,7 @@ "ElementLength":{"Arm1":250, "Arm2":250, "Finger1":100, "Finger2":100}, "Joints":{ "jointA":{"name":"Slider", "type":"lninear", "axis":[1,0,0],"parent":"Board","child":"Base"}, - "jointB":{"name":"Shoulder","type":"revolute","axis":[1,0,0],"parent":"Base","child":"Arm1","origin":[-89.5, 115, 61], "originSource":[null, "229_198_Foto_5_2026", "Fuson"]}, + "jointB":{"name":"Shoulder","type":"revolute","axis":[1,0,0],"parent":"Base","child":"Arm1","origin":[-89.5, 115, 52], "originSource":[null, "229_198_Foto_5_2026", "Fuson"]}, "jointC":{"name":"EllbowLift","type":"revolute","axis":[1,0,0],"parent":"Arm1","child":"Joint1", "origin":[null, null, null]}, "jointD":{"name":"EllbowTwist","type":"revolute","axis":[0,1,0],"parent":"Joint1","child":"Arm2", "origin":[null, null, null]} }, @@ -32,14 +32,14 @@ {"id":226,"on":"Joint1", "relPos":[0,0, 35]}, {"id":228,"on":"Arm2", "relPos":[-24.75, 112, 24.75], "relPosSource":["Fusion","Fusion","Fusion"]}, - {"id": 0,"on":"Arm2", "relPos":[-24.75, 182, 24.75], "relPosSource":["Fusion","Fusion","Fusion"]}, - {"id": 0,"on":"Arm2", "relPos":[-35,112,0], "relPosSource":["Fusion","Fusion","Fusion"]}, - {"id": 0,"on":"Arm2", "relPos":[-35,219,0], "relPosSource":["Fusion","Fusion","Fusion"]}, + {"id": -1,"on":"Arm2", "relPos":[-24.75, 182, 24.75], "relPosSource":["Fusion","Fusion","Fusion"]}, + {"id": -1,"on":"Arm2", "relPos":[-35,112,0], "relPosSource":["Fusion","Fusion","Fusion"]}, + {"id": -1,"on":"Arm2", "relPos":[-35,219,0], "relPosSource":["Fusion","Fusion","Fusion"]}, {"id":223,"on":"Arm2", "relPos":[-28.67,112,-20.08], "relPosSource":["Fusion","Fusion","Fusion"]}, - {"id": 0,"on":"Arm2", "relPos":[0,182,-30], "relPosSource":["Fusion","Fusion","Fusion"]}, + {"id": -1,"on":"Arm2", "relPos":[0,182,-30], "relPosSource":["Fusion","Fusion","Fusion"]}, {"id":218,"on":"Arm2", "relPos":[35,112,0], "relPosSource":["Fusion","Fusion","Fusion"]}, {"id":219,"on":"Arm2", "relPos":[35,219,0], "relPosSource":["Fusion","Fusion","Fusion"]}, - {"id": 0,"on":"Arm2", "relPos":[24.75, 182, 24.75], "relPosSource":["Fusion","Fusion","Fusion"]}, + {"id": -1,"on":"Arm2", "relPos":[24.75, 182, 24.75], "relPosSource":["Fusion","Fusion","Fusion"]}, {"id":218,"on":"Finger1","name":"A1","relPos":[-1.70,-25.14, 38.04]}, {"id":222,"on":"Finger1","name":"B1","relPos":[-14.55, 0.84, 74.79]} diff --git a/server/server.js b/server/server.js index 6eed246..f07431f 100755 --- a/server/server.js +++ b/server/server.js @@ -208,11 +208,16 @@ app.get('/api/latest-snapshot', (req, res) => { const latestFile = csvFiles[0]; const baseName = path.basename(latestFile.name, path.extname(latestFile.name)); const jsonFilename = `${baseName}.json`; + const jsonPath = path.join(snapshotsDir, jsonFilename); + +console.log("JSON Pfad:", jsonPath); +console.log("Existiert JSON:", fs.existsSync(jsonPath)); + const imageFilename = `${baseName}_annotated.jpg`; const imagePath = path.join(snapshotsDir, imageFilename); const imatePath2 = imagePath.includes('video0') ? imagePath.replace('video0', 'video1') : imagePath.replace('video1', 'video0'); - + //-- fs.readFile(latestFile.path, 'utf8', (err, data) => { if (err) { return res.status(500).json({ error: 'Fehler beim Lesen der Datei' }); @@ -224,39 +229,45 @@ app.get('/api/latest-snapshot', (req, res) => { content: data }; - // Lade JSON wenn vorhanden - fs.readFile(jsonFilename, { encoding: 'base64' }, (jpgErr, jpgBase64) => { - if (!jpgErr && jpgBase64) { - response.imageFile = { + const jsonPath = path.join(snapshotsDir, jsonFilename); + + // ✅ JSON FIRST, dann alles andere + fs.readFile(jsonPath, 'utf8', (jsonErr, jsonData) => { + if (!jsonErr && jsonData) { + response.jsonFile = { filename: jsonFilename, - mimeType: 'json', - contentBase64: jpgBase64 - }; - } - }); - - // Lade beide Bilder - fs.readFile(imagePath, { encoding: 'base64' }, (jpgErr, jpgBase64) => { - if (!jpgErr && jpgBase64) { - response.imageFile = { - filename: imageFilename, - mimeType: 'image/jpeg', - contentBase64: jpgBase64 + content: jsonData }; } - fs.readFile(imatePath2, { encoding: 'base64' }, (jpgErr2, jpgBase642) => { - if (!jpgErr2 && jpgBase642) { - response.image2 = { - filename: path.basename(imatePath2), + // Bild 1 + fs.readFile(imagePath, { encoding: 'base64' }, (jpgErr, jpgBase64) => { + if (!jpgErr && jpgBase64) { + response.imageFile = { + filename: imageFilename, mimeType: 'image/jpeg', - contentBase64: jpgBase642 + contentBase64: jpgBase64 }; } - res.json(response); + + // Bild 2 + fs.readFile(imatePath2, { encoding: 'base64' }, (jpgErr2, jpgBase642) => { + if (!jpgErr2 && jpgBase642) { + response.image2 = { + filename: path.basename(imatePath2), + mimeType: 'image/jpeg', + contentBase64: jpgBase642 + }; + } + + // ✅ jetzt erst senden + res.json(response); + }); }); }); }); + + //-- }); }); diff --git a/test/snapshots/snapshot_video0_1778845508432.jpg b/test/snapshots/snapshot_video0_1778845508432.jpg new file mode 100755 index 0000000..3c28f5d Binary files /dev/null and b/test/snapshots/snapshot_video0_1778845508432.jpg differ diff --git a/test/snapshots/snapshot_video0_1778845508432_two_cam.csv b/test/snapshots/snapshot_video0_1778845508432_two_cam.csv new file mode 100755 index 0000000..be00d8a --- /dev/null +++ b/test/snapshots/snapshot_video0_1778845508432_two_cam.csv @@ -0,0 +1,20 @@ +id,x_mm,y_mm,z_mm,roll_deg,pitch_deg,yaw_deg,seen_by +camera 0,-45.26,-643.92,610.54,-123.820,-3.052,-30.995 +camera 1,10.55,-438.57,997.81,-160.771,-18.041,-14.950 +180,419.14,-140.22,418.18,72.800,42.247,-106.109,2 +189,453.50,-168.57,403.03,88.856,-7.808,-48.251,3 +196,426.40,-168.29,377.81,16.748,-9.999,-21.351,3 +197,297.35,-122.58,90.09,95.407,83.104,-83.205,1 +198,338.69,-34.96,116.45,-10.259,0.249,-1.646,3 +200,268.46,-18.68,115.06,0.906,-1.063,1.907,3 +201,232.36,58.93,100.62,48.972,59.258,20.582,3 +204,268.60,134.65,120.12,-3.903,1.616,-1.027,3 +205,803.19,-90.66,-0.77,131.746,-47.948,-88.085,3 +210,-5.27,3.72,-4.15,-1.090,1.598,-1.102,3 +211,200.22,0.62,-1.19,-1.206,0.232,-1.179,3 +215,199.19,-89.68,-0.60,-1.626,-0.834,-1.483,3 +218,410.85,-161.13,200.05,-93.281,-7.490,129.360,3 +219,418.18,-170.76,306.64,-36.855,-10.153,137.791,3 +222,453.79,-138.61,61.69,-96.553,-4.102,-175.991,1 +229,341.88,-124.09,129.35,-7.622,0.840,-0.059,3 +243,353.63,-145.71,80.99,81.127,1.472,0.651,1 diff --git a/test/snapshots/snapshot_video0_1778845508432_two_cam.json b/test/snapshots/snapshot_video0_1778845508432_two_cam.json new file mode 100755 index 0000000..f7add22 --- /dev/null +++ b/test/snapshots/snapshot_video0_1778845508432_two_cam.json @@ -0,0 +1,267 @@ +{ + "metadata": { + "timestamp": "2026-05-15 11:45:08", + "reference_markers": [ + 205, + 210, + 211, + 215 + ], + "dict": "DICT_4X4_250", + "marker_size_mm": 25.0, + "rms_refs_px_cam1": 5.39648196111485, + "rms_refs_px_cam2": 5.377608516390921, + "description": "Two-camera joint optimization with triangulation" + }, + "cameras": [ + { + "id": "camera1", + "position_mm": [ + -45.26291564111129, + -643.9152747329521, + 610.5360477447284 + ], + "orientation_deg": { + "roll": -123.81983227060897, + "pitch": -3.052095021020194, + "yaw": -30.994640267051796 + } + }, + { + "id": "camera2", + "position_mm": [ + 10.553380406378764, + -438.5675198007304, + 997.8097666875392 + ], + "orientation_deg": { + "roll": -160.77106232170593, + "pitch": -18.041298309463212, + "yaw": -14.950430767356504 + } + } + ], + "markers": [ + { + "id": 180, + "position_mm": [ + 419.144582367835, + -140.22299366478097, + 418.1777757275768 + ], + "orientation_deg": { + "roll": 72.80001565888858, + "pitch": 42.24706355578098, + "yaw": -106.10873668157622 + } + }, + { + "id": 189, + "position_mm": [ + 453.49652099609375, + -168.57383728027344, + 403.028076171875 + ], + "orientation_deg": { + "roll": 88.85554131822369, + "pitch": -7.808091604864712, + "yaw": -48.2507766865305 + } + }, + { + "id": 196, + "position_mm": [ + 426.4018859863281, + -168.291748046875, + 377.80609130859375 + ], + "orientation_deg": { + "roll": 16.74814878527144, + "pitch": -9.998891362798654, + "yaw": -21.35050086279306 + } + }, + { + "id": 197, + "position_mm": [ + 297.35264103055096, + -122.58445163275444, + 90.08801199506212 + ], + "orientation_deg": { + "roll": 95.40654253536692, + "pitch": 83.10358092445118, + "yaw": -83.20497125428815 + } + }, + { + "id": 198, + "position_mm": [ + 338.6861877441406, + -34.96370315551758, + 116.44953918457031 + ], + "orientation_deg": { + "roll": -10.259342232120957, + "pitch": 0.24870528512126713, + "yaw": -1.645925923556587 + } + }, + { + "id": 200, + "position_mm": [ + 268.4554138183594, + -18.676528930664062, + 115.06306457519531 + ], + "orientation_deg": { + "roll": 0.9062729692050792, + "pitch": -1.0632288819207127, + "yaw": 1.9070692806416922 + } + }, + { + "id": 201, + "position_mm": [ + 232.35826110839844, + 58.9295539855957, + 100.61632537841797 + ], + "orientation_deg": { + "roll": 48.97241093155402, + "pitch": 59.257661898345866, + "yaw": 20.58152841399833 + } + }, + { + "id": 204, + "position_mm": [ + 268.6045837402344, + 134.6527099609375, + 120.11669921875 + ], + "orientation_deg": { + "roll": -3.9025079982094075, + "pitch": 1.6157619620164512, + "yaw": -1.0273413143882555 + } + }, + { + "id": 205, + "position_mm": [ + 803.1948852539062, + -90.6617660522461, + -0.7710586786270142 + ], + "orientation_deg": { + "roll": 131.7462374697395, + "pitch": -47.94838144672623, + "yaw": -88.08547595658573 + } + }, + { + "id": 210, + "position_mm": [ + -5.272350788116455, + 3.7151503562927246, + -4.148390293121338 + ], + "orientation_deg": { + "roll": -1.0895745898564984, + "pitch": 1.597902036769897, + "yaw": -1.101966066052541 + } + }, + { + "id": 211, + "position_mm": [ + 200.220947265625, + 0.6223337650299072, + -1.1855000257492065 + ], + "orientation_deg": { + "roll": -1.2056214342111915, + "pitch": 0.2315656693236613, + "yaw": -1.1785846140745873 + } + }, + { + "id": 215, + "position_mm": [ + 199.1853485107422, + -89.6837387084961, + -0.5997236967086792 + ], + "orientation_deg": { + "roll": -1.6261955295642851, + "pitch": -0.8340209558933352, + "yaw": -1.4828278676661735 + } + }, + { + "id": 218, + "position_mm": [ + 410.84588623046875, + -161.1257781982422, + 200.04945373535156 + ], + "orientation_deg": { + "roll": -93.28051907841518, + "pitch": -7.489671997832344, + "yaw": 129.36034680599943 + } + }, + { + "id": 219, + "position_mm": [ + 418.1803283691406, + -170.76272583007812, + 306.6425476074219 + ], + "orientation_deg": { + "roll": -36.85526866474793, + "pitch": -10.152567906310786, + "yaw": 137.79108214891733 + } + }, + { + "id": 222, + "position_mm": [ + 453.786514652246, + -138.60719584007464, + 61.69153094975155 + ], + "orientation_deg": { + "roll": -96.55321857084233, + "pitch": -4.102189790510929, + "yaw": -175.99142561055683 + } + }, + { + "id": 229, + "position_mm": [ + 341.875244140625, + -124.09217071533203, + 129.34832763671875 + ], + "orientation_deg": { + "roll": -7.622257491837189, + "pitch": 0.8403760856279705, + "yaw": -0.05929184338877813 + } + }, + { + "id": 243, + "position_mm": [ + 353.6258784328338, + -145.70869032187207, + 80.99019815263763 + ], + "orientation_deg": { + "roll": 81.12712553142183, + "pitch": 1.472073634639913, + "yaw": 0.6510713155997453 + } + } + ] +} \ No newline at end of file diff --git a/test/snapshots/snapshot_video0_1778845508432_two_cam_annotated.jpg b/test/snapshots/snapshot_video0_1778845508432_two_cam_annotated.jpg new file mode 100755 index 0000000..a934fbd Binary files /dev/null and b/test/snapshots/snapshot_video0_1778845508432_two_cam_annotated.jpg differ diff --git a/test/snapshots/snapshot_video0_1778845508432_two_cam_overlay.png b/test/snapshots/snapshot_video0_1778845508432_two_cam_overlay.png new file mode 100755 index 0000000..bd409cf Binary files /dev/null and b/test/snapshots/snapshot_video0_1778845508432_two_cam_overlay.png differ diff --git a/test/snapshots/snapshot_video1_1778845508432.jpg b/test/snapshots/snapshot_video1_1778845508432.jpg new file mode 100755 index 0000000..ba5ad29 Binary files /dev/null and b/test/snapshots/snapshot_video1_1778845508432.jpg differ diff --git a/test/snapshots/snapshot_video1_1778845508432_two_cam_annotated.jpg b/test/snapshots/snapshot_video1_1778845508432_two_cam_annotated.jpg new file mode 100755 index 0000000..b90f2ce Binary files /dev/null and b/test/snapshots/snapshot_video1_1778845508432_two_cam_annotated.jpg differ diff --git a/test/snapshots/snapshot_video1_1778845508432_two_cam_overlay.png b/test/snapshots/snapshot_video1_1778845508432_two_cam_overlay.png new file mode 100755 index 0000000..fd33d93 Binary files /dev/null and b/test/snapshots/snapshot_video1_1778845508432_two_cam_overlay.png differ