When you dock a camera twin to an arm link, the mount offset starts as a value you
typed in the editor. That is fine for previewing a field of view and not good
enough for manipulation: every pixel-to-world calculation inherits the error.Hand-eye calibration measures that offset. You hold a printed board still, reorient
the wrist a dozen times, and Cyberwave solves for the camera’s pose on the link — then
writes it to the camera twin’s docking offset.Eye-in-hand only: the camera must be mounted on a moving link.You can run it two ways: from the dashboard on a supported robot, or from the SDK.
Dock the camera twin to the arm link it is physically mounted on.
Print a ChArUco board and measure the squares with calipers. Printers rescale,
and the square size is the only scale input — a 2% error becomes a 2% range error.
Clamp the board so it cannot move for the whole session.
Select the docked camera twin in the environment editor and open Hand-eye
calibration. The panel shows the arm and link it will measure against, plus the
last calibration if there is one. Click Calibrate hand-eye.The robot then guides you through the run in a calibration alert:
Hand-guide the wrist to a new orientation and let it settle.
Click Capture sample. The alert counts your captures and tells you if the
board was not found in one — that capture is not added, so reposition and retake.
After at least three captures, Solve appears. Click it when you have a dozen.
Check the stability figure — that is the number the verdict is keyed on —
then Apply to docking offset, or Capture more to tighten it first.
Available on robots whose camera is managed by the same edge driver as the arm —
the SO-101 today, and any driver whose maintainer has wired up its arm and camera
for it. For anything else, use the SDK.
Reorient, don’t just move. Sliding the wrist around at a fixed orientation
cannot determine the camera’s orientation, no matter how many captures you take.
Tilt it about several different axes. If the captures don’t constrain the answer,
the solve refuses rather than returning a guess.
If you know your camera’s intrinsics (fx, fy, cx, cy), pass them in. If
you don’t, omit intrinsics and they are solved from the same board views you
capture for the calibration.
from cyberwave.calibration import CharucoBoard, HandEyeSessionsession = HandEyeSession( camera_twin=camera, arm_twin=arm, fk_frame="wrist_link", # the link the camera is docked to board=CharucoBoard(squares=(5, 7), square_size_m=0.030, marker_size_m=0.022), intrinsics={"fx": 615.0, "fy": 615.0, "cx": 319.5, "cy": 239.5},)for _ in range(12): input("Reorient the arm, let it settle, then press Enter...") session.add_sample(base_to_gripper=gripper_pose_4x4)result = session.solve()
As above, reorient the wrist rather than sliding it — solve() refuses rather than
returning a guess when the captures don’t constrain the answer. Keep the board still
for the whole session; if you bump it, call session.clear() and start over.add_sample() waits for the arm to stop before grabbing a frame, and checks it is
still in the same place afterwards — an arm that had not settled would otherwise
pair one position’s image with another position’s pose, which nothing downstream
can detect. Captures that fail the check raise HandEyeSyncError and are
discarded rather than stored.If your arm reports joint states slowly, raise settle_timeout_s. Pass
settle=False to turn the check off, and note it is skipped automatically when
you supply your own image=.
Start with stability. It re-solves with each capture dropped in turn and reports
how far the answer moves — so it asks the question you actually have, “will this
number hold?”, rather than “do my captures agree with each other?”.
print(result.leave_one_out.max_stddev_m) # under ~4 mm is good, over ~8 mm is not
Measured on an SO-101 wrist camera, runs whose mount position was reproducible
across independent sessions land at 1.9–3.5 mm; ill-conditioned ones reach 7.7–24 mm,
almost entirely along the camera’s optical axis. The dashboard’s good/borderline/not-
usable verdict is keyed on this number.
The residual is a secondary check — how much the captures disagree with each other,
reported as a standard deviation across them. It is worth reading to catch a
grossly bad fit, but it tracked working distance more than answer quality on this
arm, which is why it is not the headline. A large one usually means, in order of
likelihood: wrong intrinsics, a board that moved, or forward kinematics reported
about a different link than the camera is docked to.Because it is a standard deviation, it reports variability, not offset: a set of
captures that are all wrong by the same amount reads low. max_residual_translation_m
and max_residual_rotation_deg carry the worst single capture, so a max several
times the residual points at one capture to retake rather than at a bad fit.
Neither number is an error bar. Both measure internal consistency, not distance
from the truth, and they come apart in two ways:
A consistent mistake — poses measured about the wrong link, say — produces a
small residual and a wrong answer. (Both the dashboard flow and HandEyeSession
check the link name for you, which is why that particular mistake is hard to make.)
If your captures rotate about only slightly different axes, the problem is
ill-posed and every statistic flatters it. Tilt the wrist about genuinely
different axes between captures rather than sliding it around — rotation variety
is what makes the solve possible.
Intrinsics are an input the hand-eye solve cannot recover from: every board pose
inherits their error, and it shows up only as an inflated residual with nothing
pointing at the cause. So if you don’t pass any, they get solved properly:
session = HandEyeSession(..., intrinsics=None) # or just omit it
Every capture is accepted or refused on the spot — finding the board’s corners
needs no camera model, only turning them into a pose does — and the camera model is
fitted once, at solve(), from every view you captured. Nothing is held back and
nothing is discarded.
print(result.solved_intrinsics.rms_reprojection_px) # < 1 px is goodprint(result.solved_intrinsics.principal_point_is_centred)
Check both. A high RMS means redo it with more varied views; a principal point far
from the image centre usually means the board spec is wrong rather than the sensor
being genuinely offset.Vary the board’s angle and distance, not just its position — views that are all
face-on leave the focal length poorly constrained however many you take. And
intrinsics are per-resolution: mixing frame sizes in one session is rejected.
session.solve() re-solves with each capture dropped in turn and attaches the
result:
print(result.leave_one_out.max_stddev_m) # movement when one capture is droppedprint(result.leave_one_out.worst_sample_index) # capture to re-take first
leave_one_out answers a question the residual cannot. The residual asks whether
your captures agree with each other; leave-one-out asks whether the answer holds up
when the evidence changes. A calibration that moves millimetres when any single
capture is dropped is not one to trust, however tight its residual.leave_one_out is None only when dropping a capture would leave too few to
solve at all — so with the three captures a solve needs, or when the captures are
so alike that every reduced set is degenerate. At low capture counts the spread
says a lot about which capture was removed, so read it as weak evidence and
check subset_count for how many re-solves it came from.
worst_sample_index names the capture to re-take first when the spread is wide.Pass validate=False to skip it.
if result.residual_translation_m < 0.005: camera.calibration.set(result, board=session.board)
This updates the camera twin’s docking offset, so the 3D view and any exported scene
both place the camera where it really is. The solve details — residuals, sample
count, board, and the intrinsics you supplied — are stored alongside it.Read it back with camera.calibration.get(). camera.calibration.delete() forgets
the record but leaves the mount offset in place.
The SO-101 driver ships so101-handeye-debug, which runs the same calibration with
Cyberwave taken out of the loop — no API key, no twins, no MQTT — and writes every
frame, joint reading, board pose and solve to a run directory.
so101-handeye-debug selftest # is the pipeline itself sound?so101-handeye-debug board # does the print match the spec?so101-handeye-debug capture --fov 60 --fk-frame gripper # capture, saving everything
Because the frames and poses are all on disk, you can re-solve a run offline with one
input changed at a time and see exactly what moves:
so101-handeye-debug intrinsics --run $RUN # measure the intrinsics you guessedso101-handeye-debug solve --run $RUN --intrinsics-from-fitso101-handeye-debug solve --run $RUN --drop 4 # was one capture carrying the residual?
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Cyberwave Docs","colors":{"primary":"#00b5dd","light":"#33cfef","dark":"#0091b1"},"logo":{"light":"https://mintcdn.com/cyberwave/gDyqTt86aQ59dugY/images/cyberwave-logo-black.svg?fit=max\u0026auto=format\u0026n=gDyqTt86aQ59dugY\u0026q=85\u0026s=7af7130ac16b7bc13166a3d9f431cdf5","dark":"https://mintcdn.com/cyberwave/gDyqTt86aQ59dugY/images/cyberwave-logo-white.svg?fit=max\u0026auto=format\u0026n=gDyqTt86aQ59dugY\u0026q=85\u0026s=7befe341f16cadf8b7a4acb4ba0d294a"},"favicon":"https://mintcdn.com/cyberwave/gDyqTt86aQ59dugY/favicon.svg?fit=max\u0026auto=format\u0026n=gDyqTt86aQ59dugY\u0026q=85\u0026s=20eaaf30d7f090c5eb2628572ee4eb20","navbar":{"links":[{"href":"https://cyberwave.com/blog","label":"Blog"},{"href":"https://cyberwave.com/contact-us","label":"Contact"},{"href":"https://status.cyberwave.com","label":"Status"}]},"footer":{"socials":{"x":"https://x.com/cyberwave_com","linkedin":"https://www.linkedin.com/company/cyberwave-com","youtube":"https://www.youtube.com/channel/UCmw6EQ1nJj7sqjkITm2wjCw"},"links":[{"header":"Resources","items":[{"label":"Status","href":"https://status.cyberwave.com"},{"label":"Changelog","href":"https://docs.cyberwave.com/changelog"},{"label":"Discord","href":"https://discord.gg/dfGhNrawyF"},{"label":"GitHub","href":"https://github.com/cyberwave-os"}]}]},"integrations":{"posthog":{"apiKey":"phc_JswM4a2VwYGkjsKr7xVYwdiX6BDSDKDXc25fkvApnhZ","apiHost":"https://eu.i.posthog.com"}},"contextual":{"options":["copy","view","chatgpt","claude","perplexity","mcp","cursor","vscode"],"display":"header"},"styling":{"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"cyberwave","hasAudioNarration":false,"actualSubdomain":"cyberwave","customDomains":["docs.cyberwave.com"],"gitSource":{"type":"github","owner":"cyberwave-os","repo":"docs-mintlify","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"entitlements":{},"buildId":"6ae050f854afc9d90cc53677","clientVersion":"0.0.4111","searchFilterCounts":[{"language":"default","tag":"API","count":241},{"language":"default","tag":"APIs","count":241},{"language":"default","tag":"REST (generated)","count":235},{"language":"default","tag":"Models","count":229},{"language":"default","tag":"Docs","count":114},{"language":"default","tag":"Reference","count":56},{"language":"default","tag":"Connect hardware","count":28},{"language":"default","tag":"Simulate \u0026 test","count":27},{"language":"default","tag":"Build with AI","count":26},{"language":"default","tag":"Workflow nodes","count":26}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":"#sidebar a[href=\"/sdks/cpp-sdk\"]{pointer-events:none;opacity:0.5;cursor:default}"},{"filePath":"images/src/_base.css","content":":root{--cyan:#00b5dd;--cyan-2:#33cfef;--cyan-3:#5fdaf3;--ink:#04212b;--text:#e6f4f8;--muted:#9ab4bd;--dim:#6f8b96;--line:#24363e;--panel:#101c22;--card:#15252d;--card-line:#2c434d}*{box-sizing:border-box;margin:0}html,body{background:transparent}body{font-family:\"Inter\",ui-sans-serif,system-ui,sans-serif;color:var(--text);-webkit-font-smoothing:antialiased}.label{font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--dim)}"}],"page":{"pagePath":"feature-reference/hand-eye-calibration","slug":"/feature-reference/hand-eye-calibration","pageMetadata":{"title":"Hand-eye calibration","description":"Measure where a camera actually sits on a robot arm, instead of entering the mount offset by eye","icon":"crosshairs","href":"/feature-reference/hand-eye-calibration"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Measure where a camera actually sits on a robot arm, instead of entering the mount offset by eye\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,li:`li`,ol:`ol`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsx(Heading,{level:`2`,id:`what-it-is`,children:`What it is`}),`\n`,_jsx(_components.p,{children:`When you dock a camera twin to an arm link, the mount offset starts as a value you\ntyped in the editor. That is fine for previewing a field of view and not good\nenough for manipulation: every pixel-to-world calculation inherits the error.`}),`\n`,_jsx(_components.p,{children:`Hand-eye calibration measures that offset. You hold a printed board still, reorient\nthe wrist a dozen times, and Cyberwave solves for the camera’s pose on the link — then\nwrites it to the camera twin’s docking offset.`}),`\n`,_jsx(_components.p,{children:`Eye-in-hand only: the camera must be mounted on a moving link.`}),`\n`,_jsx(_components.p,{children:`You can run it two ways: from the dashboard on a supported robot, or from the SDK.`}),`\n`,_jsx(Heading,{level:`2`,id:`before-you-start`,children:`Before you start`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Dock the camera twin to the arm link it is physically mounted on.`}),`\n`,_jsxs(_components.li,{children:[`Print a ChArUco board and `,_jsx(_components.strong,{children:`measure the squares with calipers`}),`. Printers rescale,\nand the square size is the only scale input — a 2% error becomes a 2% range error.`]}),`\n`,_jsx(_components.li,{children:`Clamp the board so it cannot move for the whole session.`}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`from-the-dashboard`,children:`From the dashboard`}),`\n`,_jsxs(_components.p,{children:[`Select the docked camera twin in the environment editor and open `,_jsx(_components.strong,{children:`Hand-eye\ncalibration`}),`. The panel shows the arm and link it will measure against, plus the\nlast calibration if there is one. Click `,_jsx(_components.strong,{children:`Calibrate hand-eye`}),`.`]}),`\n`,_jsx(_components.p,{children:`The robot then guides you through the run in a calibration alert:`}),`\n`,_jsxs(_components.ol,{children:[`\n`,_jsx(_components.li,{children:`Hand-guide the wrist to a new orientation and let it settle.`}),`\n`,_jsxs(_components.li,{children:[`Click `,_jsx(_components.strong,{children:`Capture sample`}),`. The alert counts your captures and tells you if the\nboard was not found in one — that capture is not added, so reposition and retake.`]}),`\n`,_jsxs(_components.li,{children:[`After at least three captures, `,_jsx(_components.strong,{children:`Solve`}),` appears. Click it when you have a dozen.`]}),`\n`,_jsxs(_components.li,{children:[`Check the stability figure — that is the number the verdict is keyed on —\nthen `,_jsx(_components.strong,{children:`Apply to docking offset`}),`, or `,_jsx(_components.strong,{children:`Capture more`}),` to tighten it first.`]}),`\n`]}),`\n`,_jsx(_components.p,{children:`Available on robots whose camera is managed by the same edge driver as the arm —\nthe SO-101 today, and any driver whose maintainer has wired up its arm and camera\nfor it. For anything else, use the SDK.`}),`\n`,_jsx(Warning,{children:_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Reorient, don’t just move.`}),` Sliding the wrist around at a fixed orientation\ncannot determine the camera’s orientation, no matter how many captures you take.\nTilt it about several different axes. If the captures don’t constrain the answer,\nthe solve refuses rather than returning a guess.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`from-the-sdk`,children:`From the SDK`}),`\n`,_jsxs(_components.p,{children:[`If you know your camera’s intrinsics (`,_jsx(_components.code,{children:`fx`}),`, `,_jsx(_components.code,{children:`fy`}),`, `,_jsx(_components.code,{children:`cx`}),`, `,_jsx(_components.code,{children:`cy`}),`), pass them in. If\nyou don’t, omit `,_jsx(_components.code,{children:`intrinsics`}),` and they are solved from the same board views you\ncapture for the calibration.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`pip`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` install`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"cyberwave[calibration]\"`})]}),`\n`]})})}),`\n`,_jsx(Heading,{level:`2`,id:`capturing`,children:`Capturing`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`15`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`15`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`from`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` cyberwave.calibration `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`import`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` CharucoBoard, HandEyeSession`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`session `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` HandEyeSession(`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:` camera_twin`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`camera,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:` arm_twin`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`arm,`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:` fk_frame`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"wrist_link\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# the link the camera is docked to`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:` board`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`CharucoBoard(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`squares`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`5`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`7`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`), `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`square_size_m`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0.030`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`marker_size_m`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`0.022`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`),`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:` intrinsics`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`{`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"fx\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`615.0`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"fy\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`615.0`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"cx\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`319.5`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"cy\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`239.5`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`},`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`for`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` _ `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`in`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:` range`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`12`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`):`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:` input`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"Reorient the arm, let it settle, then press Enter...\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` session.add_sample(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`base_to_gripper`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`gripper_pose_4x4)`})]}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`result `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` session.solve()`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`As above, reorient the wrist rather than sliding it — `,_jsx(_components.code,{children:`solve()`}),` refuses rather than\nreturning a guess when the captures don’t constrain the answer. Keep the board still\nfor the whole session; if you bump it, call `,_jsx(_components.code,{children:`session.clear()`}),` and start over.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`add_sample()`}),` waits for the arm to stop before grabbing a frame, and checks it is\nstill in the same place afterwards — an arm that had not settled would otherwise\npair one position’s image with another position’s pose, which nothing downstream\ncan detect. Captures that fail the check raise `,_jsx(_components.code,{children:`HandEyeSyncError`}),` and are\ndiscarded rather than stored.`]}),`\n`,_jsxs(_components.p,{children:[`If your arm reports joint states slowly, raise `,_jsx(_components.code,{children:`settle_timeout_s`}),`. Pass\n`,_jsx(_components.code,{children:`settle=False`}),` to turn the check off, and note it is skipped automatically when\nyou supply your own `,_jsx(_components.code,{children:`image=`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`checking-the-result`,children:`Checking the result`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Start with stability.`}),` It re-solves with each capture dropped in turn and reports\nhow far the answer moves — so it asks the question you actually have, “will this\nnumber hold?”, rather than “do my captures agree with each other?”.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`print`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(result.leave_one_out.max_stddev_m) `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# under ~4 mm is good, over ~8 mm is not`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Measured on an SO-101 wrist camera, runs whose mount position was reproducible\nacross independent sessions land at 1.9–3.5 mm; ill-conditioned ones reach 7.7–24 mm,\nalmost entirely along the camera’s optical axis. The dashboard’s good/borderline/not-\nusable verdict is keyed on this number.`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`print`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(result.residual_translation_m, result.residual_rotation_deg)`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`The residual is a secondary check — how much the captures disagree with each other,\nreported as a standard deviation across them. It is worth reading to catch a\ngrossly bad fit, but it tracked working distance more than answer quality on this\narm, which is why it is not the headline. A large one usually means, in order of\nlikelihood: wrong intrinsics, a board that moved, or forward kinematics reported\nabout a different link than the camera is docked to.`}),`\n`,_jsxs(_components.p,{children:[`Because it is a standard deviation, it reports `,_jsx(_components.em,{children:`variability`}),`, not offset: a set of\ncaptures that are all wrong by the same amount reads low. `,_jsx(_components.code,{children:`max_residual_translation_m`}),`\nand `,_jsx(_components.code,{children:`max_residual_rotation_deg`}),` carry the worst single capture, so a max several\ntimes the residual points at one capture to retake rather than at a bad fit.`]}),`\n`,_jsxs(Warning,{children:[_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Neither number is an error bar.`}),` Both measure internal consistency, not distance\nfrom the truth, and they come apart in two ways:`]}),_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[`A consistent mistake — poses measured about the wrong link, say — produces a\n`,_jsx(_components.em,{children:`small`}),` residual and a wrong answer. (Both the dashboard flow and `,_jsx(_components.code,{children:`HandEyeSession`}),`\ncheck the link name for you, which is why that particular mistake is hard to make.)`]}),`\n`,_jsx(_components.li,{children:`If your captures rotate about only slightly different axes, the problem is\nill-posed and every statistic flatters it. Tilt the wrist about genuinely\ndifferent axes between captures rather than sliding it around — rotation variety\nis what makes the solve possible.`}),`\n`]})]}),`\n`,_jsx(Heading,{level:`2`,id:`intrinsics`,children:`Intrinsics`}),`\n`,_jsx(_components.p,{children:`Intrinsics are an input the hand-eye solve cannot recover from: every board pose\ninherits their error, and it shows up only as an inflated residual with nothing\npointing at the cause. So if you don’t pass any, they get solved properly:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`1`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`1`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`session `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` HandEyeSession(`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#D4D4D4`},children:`...`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`intrinsics`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:`None`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# or just omit it`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Every capture is accepted or refused on the spot — finding the board’s corners\nneeds no camera model, only turning them into a pose does — and the camera model is\nfitted once, at `,_jsx(_components.code,{children:`solve()`}),`, from every view you captured. Nothing is held back and\nnothing is discarded.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`print`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(result.solved_intrinsics.rms_reprojection_px) `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# \u003c 1 px is good`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`print`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(result.solved_intrinsics.principal_point_is_centred)`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Check both. A high RMS means redo it with more varied views; a principal point far\nfrom the image centre usually means the board spec is wrong rather than the sensor\nbeing genuinely offset.`}),`\n`,_jsxs(_components.p,{children:[`Vary the board’s `,_jsx(_components.strong,{children:`angle and distance`}),`, not just its position — views that are all\nface-on leave the focal length poorly constrained however many you take. And\nintrinsics are per-resolution: mixing frame sizes in one session is rejected.`]}),`\n`,_jsx(Heading,{level:`2`,id:`cross-checking-the-solve`,children:`Cross-checking the solve`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`session.solve()`}),` re-solves with each capture dropped in turn and attaches the\nresult:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`print`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(result.leave_one_out.max_stddev_m) `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# movement when one capture is dropped`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`print`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(result.leave_one_out.worst_sample_index) `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# capture to re-take first`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`leave_one_out`}),` answers a question the residual cannot. The residual asks whether\nyour captures agree with each other; leave-one-out asks whether the answer holds up\nwhen the evidence changes. A calibration that moves millimetres when any single\ncapture is dropped is not one to trust, however tight its residual.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`leave_one_out`}),` is `,_jsx(_components.code,{children:`None`}),` only when dropping a capture would leave too few to\nsolve at all — so with the three captures a solve needs, or when the captures are\nso alike that every reduced set is degenerate. At low capture counts the spread\nsays a lot about `,_jsx(_components.em,{children:`which`}),` capture was removed, so read it as weak evidence and\ncheck `,_jsx(_components.code,{children:`subset_count`}),` for how many re-solves it came from.\n`,_jsx(_components.code,{children:`worst_sample_index`}),` names the capture to re-take first when the spread is wide.`]}),`\n`,_jsxs(_components.p,{children:[`Pass `,_jsx(_components.code,{children:`validate=False`}),` to skip it.`]}),`\n`,_jsx(Heading,{level:`2`,id:`saving`,children:`Saving`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`2`,language:`python`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`python`,children:_jsxs(_components.code,{language:`python`,numberOfLines:`2`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#C586C0`},children:`if`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` result.residual_translation_m `}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`\u003c`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:` 0.005`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`:`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` camera.calibration.set(result, `}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#9CDCFE`},children:`board`}),_jsx(_components.span,{style:{color:`#CF222E`,\"--shiki-dark\":`#D4D4D4`},children:`=`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`session.board)`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`This updates the camera twin’s docking offset, so the 3D view and any exported scene\nboth place the camera where it really is. The solve details — residuals, sample\ncount, board, and the intrinsics you supplied — are stored alongside it.`}),`\n`,_jsxs(_components.p,{children:[`Read it back with `,_jsx(_components.code,{children:`camera.calibration.get()`}),`. `,_jsx(_components.code,{children:`camera.calibration.delete()`}),` forgets\nthe record but leaves the mount offset in place.`]}),`\n`,_jsx(Heading,{level:`2`,id:`when-the-result-looks-wrong`,children:`When the result looks wrong`}),`\n`,_jsxs(_components.p,{children:[`The SO-101 driver ships `,_jsx(_components.code,{children:`so101-handeye-debug`}),`, which runs the same calibration with\nCyberwave taken out of the loop — no API key, no twins, no MQTT — and writes every\nframe, joint reading, board pose and solve to a run directory.`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`so101-handeye-debug`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` selftest`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # is the pipeline itself sound?`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`so101-handeye-debug`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` board`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # does the print match the spec?`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`so101-handeye-debug`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` capture`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --fov`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:` 60`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --fk-frame`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` gripper`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # capture, saving everything`})]}),`\n`]})})}),`\n`,_jsx(_components.p,{children:`Because the frames and poses are all on disk, you can re-solve a run offline with one\ninput changed at a time and see exactly what moves:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`3`,language:`shellscript`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`shellscript`,children:_jsxs(_components.code,{language:`shellscript`,numberOfLines:`3`,children:[_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`so101-handeye-debug`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` intrinsics`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --run`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` $RUN`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # measure the intrinsics you guessed`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`so101-handeye-debug`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` solve`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --run`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` $RUN`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --intrinsics-from-fit`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`so101-handeye-debug`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` solve`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --run`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#9CDCFE`},children:` $RUN`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --drop`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:` 4`}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:` # was one capture carrying the residual?`})]}),`\n`]})})}),`\n`,_jsxs(_components.p,{children:[`Full options: `,_jsx(_components.a,{href:`https://github.com/cyberwave/cyberwave-edge-python-so101#so101-handeye-debug`,children:`SO-101 driver\nREADME`}),`.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Hand-eye calibration","description":"Measure where a camera actually sits on a robot arm, instead of entering the mount offset by eye","icon":"crosshairs","href":"/feature-reference/hand-eye-calibration"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"What it is","slug":"what-it-is","depth":2,"children":[]},{"title":"Before you start","slug":"before-you-start","depth":2,"children":[]},{"title":"From the dashboard","slug":"from-the-dashboard","depth":2,"children":[]},{"title":"From the SDK","slug":"from-the-sdk","depth":2,"children":[]},{"title":"Capturing","slug":"capturing","depth":2,"children":[]},{"title":"Checking the result","slug":"checking-the-result","depth":2,"children":[]},{"title":"Intrinsics","slug":"intrinsics","depth":2,"children":[]},{"title":"Cross-checking the solve","slug":"cross-checking-the-solve","depth":2,"children":[]},{"title":"Saving","slug":"saving","depth":2,"children":[]},{"title":"When the result looks wrong","slug":"when-the-result-looks-wrong","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"cyberwave","siteName":"Cyberwave Docs","title":"Hand-eye calibration - Cyberwave Docs","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-09-28T09:00:07.124Z","originalFileLocation":"feature-reference/hand-eye-calibration.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Measure where a camera actually sits on a robot arm, instead of entering the mount offset by eye\">\u003cmeta data-mint-head name=\"application-name\" content=\"Cyberwave Docs\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Hand-eye calibration - Cyberwave Docs\">\u003cmeta data-mint-head property=\"og:description\" content=\"Measure where a camera actually sits on a robot arm, instead of entering the mount offset by eye\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Cyberwave Docs\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://cyberwave.mintlify.site/_mintlify/api/og/v2?division=Robot+arms\u0026amp;title=Hand-eye+calibration\u0026amp;description=Measure+where+a+camera+actually+sits+on+a+robot+arm%2C+instead+of+entering+the+mount+offset+by+eye\u0026amp;theme=6d163fce0009c02e4f3f953a\u0026amp;signature=0e77e3d59a9dfb68355f0c1236ea7aad68830595498e5b7f2c77640f10fee765\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Hand-eye calibration - Cyberwave Docs\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Measure where a camera actually sits on a robot arm, instead of entering the mount offset by eye\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://cyberwave.mintlify.site/_mintlify/api/og/v2?division=Robot+arms\u0026amp;title=Hand-eye+calibration\u0026amp;description=Measure+where+a+camera+actually+sits+on+a+robot+arm%2C+instead+of+entering+the+mount+offset+by+eye\u0026amp;theme=6d163fce0009c02e4f3f953a\u0026amp;signature=0e77e3d59a9dfb68355f0c1236ea7aad68830595498e5b7f2c77640f10fee765\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/cyberwave/vQ8b9_MAwUfrGxQ_/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Cyberwave Docs\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#00b5dd\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/feature-reference/hand-eye-calibration.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.cyberwave.com/#organization\",\"name\":\"Cyberwave Docs\",\"url\":\"https://docs.cyberwave.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/cyberwave/gDyqTt86aQ59dugY/images/cyberwave-logo-black.svg?fit=max\u0026auto=format\u0026n=gDyqTt86aQ59dugY\u0026q=85\u0026s=7af7130ac16b7bc13166a3d9f431cdf5\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.cyberwave.com#website\",\"name\":\"Cyberwave Docs\",\"url\":\"https://docs.cyberwave.com\",\"publisher\":{\"@id\":\"https://docs.cyberwave.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration#webpage\",\"url\":\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration\",\"name\":\"Hand-eye calibration\",\"description\":\"Measure where a camera actually sits on a robot arm, instead of entering the mount offset by eye\",\"dateModified\":\"2026-09-28T09:00:07.124Z\",\"isPartOf\":{\"@id\":\"https://docs.cyberwave.com#website\"},\"breadcrumb\":{\"@id\":\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Connect hardware\",\"item\":\"https://docs.cyberwave.com/robots/overview\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Robot arms\",\"item\":\"https://docs.cyberwave.com/overview/robotic-arms\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Hand-eye calibration\",\"item\":\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration#article\",\"headline\":\"Hand-eye calibration\",\"name\":\"Hand-eye calibration\",\"description\":\"Measure where a camera actually sits on a robot arm, instead of entering the mount offset by eye\",\"url\":\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration\",\"mainEntityOfPage\":{\"@id\":\"https://docs.cyberwave.com/feature-reference/hand-eye-calibration#webpage\"},\"dateModified\":\"2026-09-28T09:00:07.124Z\",\"publisher\":{\"@id\":\"https://docs.cyberwave.com/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.cyberwave.com#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Cyberwave Docs\",\"publisher\":{\"@type\":\"Organization\",\"name\":\"Cyberwave Docs\"},\"provider\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","agentPointerHref":"/feature-reference/hand-eye-calibration.md","siteName":"Cyberwave Docs","nav":{"global":null,"tabs":[{"tab":"Docs","groups":[{"group":"Get started","icon":"rocket","pages":[{"title":"Welcome","icon":"house","href":"/overview"},{"title":"Agentic quickstart","icon":"robot","href":"/get-started/quickstart"},{"title":"Choose your path","icon":"signs-post","href":"/get-started/choose-your-path"},{"title":"Core concepts","icon":"shapes","href":"/get-started/core-concepts"},{"title":"Account and API keys","icon":"key","href":"/get-started/account"}]},{"group":"Build with AI","icon":"wand-magic-sparkles","pages":[{"title":"Connect AI to robots","sidebarTitle":"Connect AI to robots","icon":"wand-magic-sparkles","href":"/ai/overview"},{"title":"Your first AI loop","sidebarTitle":"Your first AI loop","icon":"wand-magic-sparkles","href":"/overview/first-ai-loop"},{"title":"Connect your AI tool","icon":"plug","href":"/ai/connect-your-ai-tool"},{"title":"Prompt library","icon":"message-lines","href":"/ai/prompt-library"},{"group":"Agents","icon":"comments","pages":[{"title":"AI agents","sidebarTitle":"Overview","icon":"comments","href":"/ai/agents"},{"title":"Environment assistant","sidebarTitle":"Environment assistant","icon":"cubes","href":"/ai/environment-assistant"},{"title":"Control agent","sidebarTitle":"Control agent","icon":"gamepad","href":"/ai/control-agent"},{"title":"Workflow assistant","sidebarTitle":"Workflow assistant","icon":"diagram-project","href":"/ai/workflow-assistant"}]},{"group":"AI models","icon":"brain","pages":[{"title":"AI models","sidebarTitle":"Overview","icon":"brain","href":"/ai/models"},{"title":"Model catalog","icon":"brain","href":"/feature-reference/ml-models/index"},{"title":"Tasks and outputs","icon":"brackets-curly","href":"/feature-reference/ml-models/structured-actions"},{"title":"Model playground","sidebarTitle":"Try models in the Playground","icon":"flask","href":"/feature-reference/ml-models/playground"},{"title":"Bring your own model","icon":"upload","href":"/feature-reference/ml-models/uploading-your-model"},{"title":"Speech-to-text models","icon":"microphone","href":"/feature-reference/ml-models/speech-to-text"}]},{"title":"Ways to control a robot","sidebarTitle":"Ways to control a robot","icon":"gamepad","href":"/ai/control"},{"group":"Workflows","icon":"diagram-project","pages":[{"title":"Build and run workflows","icon":"diagram-project","href":"/feature-reference/workflows"},{"title":"Running a workflow in simulation","icon":"cube","href":"/feature-reference/workflows/simulation-execution-target"},{"title":"Prompting Call Model","icon":"message","href":"/feature-reference/workflows/call-model-prompt"},{"title":"Reusing chains across upstreams","icon":"link","href":"/feature-reference/workflows/reusing-chains"},{"title":"Edge workflow dependencies","icon":"puzzle-piece","href":"/feature-reference/workflows/edge-dependencies"},{"group":"Voice commands","icon":"microphone","pages":[{"title":"Audio in workflows","icon":"waveform-lines","href":"/feature-reference/workflows/audio-in-workflows"},{"title":"Call Model — STT on edge","icon":"microphone-lines","href":"/feature-reference/workflows/call-model-stt-edge"},{"title":"Wake Word — testing","icon":"vial","href":"/feature-reference/workflows/wake-word-engine/testing"}]},{"group":"Examples","icon":"lightbulb","pages":[{"title":"Inspect areas with Move Twin","icon":"magnifying-glass","href":"/feature-reference/workflows/inspect-area"},{"title":"Direct GPS targets with Move Twin","icon":"location-dot","href":"/feature-reference/workflows/move-to-gps"}]}]},{"title":"Agent safety and approval","icon":"shield-check","href":"/ai/safety"}]},{"group":"Simulate \u0026 test","icon":"cube","pages":[{"title":"Simulate in the browser","sidebarTitle":"Playground and MuJoCo","icon":"hand-wave","href":"/overview/hello-robot"},{"title":"Simulation","icon":"atom","href":"/overview/features/simulation"},{"title":"Simulation view","icon":"video","href":"/feature-reference/environment-editor/simulation-view"},{"title":"Simulation timing and advanced settings","icon":"gauge","href":"/feature-reference/environment-editor/simulation-timing"},{"group":"Environments \u0026 editor","icon":"map","pages":[{"title":"Environments","icon":"globe","href":"/overview/features/environment"},{"title":"Environment editor","icon":"pen-to-square","href":"/feature-reference/environment-editor/index"},{"title":"Assistant asset generation","icon":"wand-magic-sparkles","href":"/feature-reference/environment-editor/assistant-asset-generation"},{"title":"Environment templates","icon":"copy","href":"/feature-reference/environment-editor/environment-templates"},{"title":"Fixed and free-floating objects","icon":"anchor","href":"/feature-reference/environment-editor/fixed-base-objects"},{"title":"Disabling collision on a twin","icon":"ghost","href":"/feature-reference/environment-editor/disable-collision"},{"title":"Point cloud import","icon":"braille","href":"/feature-reference/environment-editor/point-cloud-import"},{"group":"Maps \u0026 navigation","icon":"route","pages":[{"title":"Geo reference","icon":"location-crosshairs","href":"/feature-reference/environment-editor/geo-reference"},{"title":"Waypoints on the map","icon":"map-pin","href":"/feature-reference/environment-editor/map-waypoints"},{"title":"Generate a map from the scene","icon":"grid-2","href":"/feature-reference/environment-editor/generated-occupancy-map"},{"title":"Waypoint reachability","icon":"route","href":"/feature-reference/environment-editor/waypoint-reachability"}]}]},{"group":"Twins","icon":"clone","pages":[{"title":"What is a digital twin","icon":"clone","href":"/overview/features/digital-twin"},{"title":"Catalog","icon":"book-bookmark","href":"/feature-reference/catalog"},{"title":"Work with digital twins","icon":"clone","href":"/feature-reference/digital-twins"},{"title":"Coupled and passive joints","icon":"link","href":"/feature-reference/digital-twins/mimic-joints"},{"title":"Live position \u0026 camera frames","icon":"satellite-dish","href":"/feature-reference/twin-live-position-and-frames"},{"title":"Telemetry event time","icon":"clock","href":"/feature-reference/telemetry-event-time"}]},{"group":"Teleoperation \u0026 poses","icon":"gamepad","pages":[{"title":"Teleoperation and remote control","icon":"gamepad","href":"/feature-reference/environment-editor/teleoperation"},{"title":"Arm commands","icon":"terminal","href":"/feature-reference/arm-commands"},{"title":"Saved poses","icon":"bookmark","href":"/feature-reference/twin-saved-poses"},{"title":"Twin joint home positions","icon":"house","href":"/feature-reference/twin-joint-home-positions"}]},{"group":"Live view","icon":"video","pages":[{"title":"Driver logs","icon":"scroll-text","href":"/feature-reference/environment-editor/driver-logs"},{"title":"Consume live video with WebRTC","icon":"video","href":"/overview/tools/custom-webrtc-consumer"}]}]},{"group":"Connect hardware","icon":"plug","pages":[{"title":"How hardware works in Cyberwave","sidebarTitle":"Overview","icon":"cubes","href":"/robots/overview"},{"title":"Supported hardware","icon":"microchip","href":"/overview/supported-hardware"},{"title":"Remote lab","sidebarTitle":"Remote lab","icon":"robot","href":"/overview/develop-remote-hardware"},{"group":"Robot arms","icon":"robot","pages":[{"title":"Robotic arms","icon":"hand","href":"/overview/robotic-arms"},{"title":"Get started with the SO-101 arm","sidebarTitle":"SO-101 quickstart","icon":"microchip","href":"/tutorials/so101-teleop-dataset"},{"title":"Get started with the AgileX Piper arm","sidebarTitle":"AgileX Piper quickstart","icon":"microchip","href":"/tutorials/agilex-piper-quickstart"},{"title":"OpenArm (ROS 2)","description":"Set up an OpenArm bimanual robot for the first time and connect it to Cyberwave as a ROS 2 edge driver — CAN bus, ROS 2 Humble, Docker, and MQTT mode switching.","icon":"robot","href":"/feature-reference/edge/drivers/openarm-ros2"},{"title":"Hand-eye calibration","icon":"crosshairs","href":"/feature-reference/hand-eye-calibration"}]},{"group":"Quadrupeds \u0026 rovers","icon":"dog","pages":[{"title":"Robotic dogs","description":"Build inspection automations with Cyberwave, mixing and matching any type of robotic dog","icon":"dog","href":"/overview/robotic-dogs"},{"title":"Relocalize a Go2 on a saved map with an AprilTag","sidebarTitle":"AprilTag relocalization","icon":"location-crosshairs","href":"/robots/go2-apriltag-relocalization"},{"title":"ROS 2 driver","sidebarTitle":"ROS 2 driver","icon":"terminal","href":"/api-reference/autonomous-navigation-driver"}]},{"group":"Drones","icon":"helicopter","pages":[{"title":"Drones","icon":"drone","href":"/overview/drones"}]},{"group":"Cameras \u0026 audio","icon":"camera","pages":[{"title":"Cameras","icon":"camera","href":"/overview/cameras"},{"title":"Native microphone driver","sidebarTitle":"Native microphone driver","icon":"microphone-lines","href":"/feature-reference/edge/drivers/native-microphone-driver"},{"title":"Native speaker driver","sidebarTitle":"Native speaker driver","icon":"volume-high","href":"/feature-reference/edge/drivers/native-speaker-driver"},{"title":"macOS microphone bridge","sidebarTitle":"macOS microphone","icon":"microphone-lines","href":"/feature-reference/edge/macos-microphone"}]},{"group":"Edge computers","icon":"microchip","pages":[{"title":"Overview","icon":"microchip","href":"/feature-reference/edge/overview"},{"title":"Prepare your Raspberry Pi","sidebarTitle":"Raspberry Pi setup","icon":"raspberry-pi","href":"/feature-reference/edge/raspberry-pi"},{"title":"Prepare your Jetson Orin Nano","sidebarTitle":"Jetson Orin Nano setup","icon":"microchip","href":"/feature-reference/edge/jetson-orin-nano"}]},{"group":"Add your own robot","icon":"screwdriver-wrench","pages":[{"title":"Add your own robot","sidebarTitle":"Add your robot","icon":"screwdriver-wrench","href":"/robots/add-your-robot"},{"title":"Simulator compatibility","icon":"cube","href":"/feature-reference/catalog/simulation-support"},{"title":"Editing a universal schema","icon":"pencil","href":"/feature-reference/edit-universal-schema"},{"title":"Asset kits","icon":"boxes-stacked","href":"/feature-reference/catalog/asset-kits"},{"title":"Writing compatible drivers","icon":"pen-to-square","href":"/feature-reference/edge/drivers/writing-compatible-drivers"},{"title":"ROS 2 topic namespacing (fleets)","icon":"tags","href":"/feature-reference/edge/drivers/ros2-topic-namespacing"},{"title":"Docker registry","icon":"clone","href":"/feature-reference/docker-registry"}]},{"group":"Drivers","icon":"gears","pages":[{"title":"Drivers","icon":"gears","href":"/feature-reference/edge/drivers/overview"},{"title":"Multi-camera driver orchestration","icon":"camera","href":"/feature-reference/edge/drivers/multi-camera-driver-orchestration"}]}]},{"group":"Train \u0026 improve","icon":"chart-line","pages":[{"title":"Models \u0026 datasets","icon":"brain","href":"/overview/features/models-and-datasets"},{"title":"Record and replay","icon":"clock-rotate-left","href":"/overview/features/replay-and-historical-data"},{"group":"Datasets","icon":"database","pages":[{"title":"Import datasets","icon":"file-import","href":"/feature-reference/datasets/import"},{"title":"Dataset playground","icon":"flask","href":"/feature-reference/datasets/playground"},{"title":"Dataset export \u0026 format conversion","icon":"arrow-right-arrow-left","href":"/feature-reference/datasets/format-conversion"},{"title":"When episodes fail","icon":"triangle-alert","href":"/feature-reference/datasets/episode-failures"}]},{"group":"Train a VLA","icon":"eye","pages":[{"title":"SmolVLA training","sidebarTitle":"SmolVLA","icon":"microchip","href":"/feature-reference/ml-models/smolvla-training"},{"title":"OpenVLA-OFT training","sidebarTitle":"OpenVLA-OFT","icon":"microchip","href":"/feature-reference/ml-models/openvla-oft-training"},{"title":"VLA Cloud Node","icon":"brain","href":"/overview/tools/vla-cloud-node"}]},{"group":"Train an RL policy","icon":"dumbbell","pages":[{"title":"RL tasks","icon":"graduation-cap","href":"/feature-reference/rl-tasks"},{"title":"RL task scene sensors","icon":"camera","href":"/feature-reference/rl-task-scene-sensors"}]},{"group":"Deploy a policy","icon":"upload","pages":[{"title":"Uploading policies","icon":"upload","href":"/feature-reference/uploading-policies"},{"title":"Online controllers","icon":"microchip","href":"/feature-reference/online-controllers"}]}]},{"group":"Deploy \u0026 operate","icon":"server","pages":[{"title":"Run models on the edge","icon":"microchip-ai","href":"/feature-reference/workflows/workers/overview"},{"group":"Vision pipelines on the edge","icon":"filter","pages":[{"title":"Frame substitution","icon":"user-shield","href":"/feature-reference/edge/drivers/frame-filters"},{"title":"Camera detection overlays","icon":"object-group","href":"/feature-reference/edge/drivers/camera-detection-overlays"},{"title":"Multi-camera detection routing","icon":"shuffle","href":"/feature-reference/edge/drivers/multi-camera-detection-routing"},{"title":"Record \u0026 replay","icon":"circle-play","href":"/feature-reference/edge/drivers/data-record-replay"},{"title":"Security pipeline (multi-camera, anonymising)","icon":"user-shield","href":"/feature-reference/edge/drivers/security-pipeline"}]},{"title":"Cloud Node","icon":"cloud","href":"/overview/tools/cloud-node"},{"group":"Fleet and alerts","icon":"bell","pages":[{"title":"Fleet provisioning","icon":"server","href":"/enterprise/fleet-provisioning"},{"title":"Alerts","icon":"bell","href":"/feature-reference/edge/drivers/alerts"}]},{"group":"For IT \u0026 integrators","icon":"building","pages":[{"title":"Cyberwave for integrators","sidebarTitle":"Overview","icon":"building","href":"/enterprise/overview"},{"title":"Integration surfaces","icon":"plug","href":"/enterprise/integration-surfaces"},{"title":"Network and firewall requirements","sidebarTitle":"Network and firewall","icon":"network-wired","href":"/enterprise/network-and-firewall"},{"title":"Access control","icon":"shield","href":"/feature-reference/access-control"},{"title":"Zenoh-MQTT bridge","icon":"bridge","href":"/overview/tools/zenoh-mqtt-bridge"},{"title":"A2A partner task API","icon":"handshake","href":"/api-reference/a2a-partner-api"}]}]}]},{"tab":"Tutorials","pages":[{"title":"Tutorials","href":"/tutorials"}]},{"tab":"Reference","pages":[{"title":"Reference","href":"/overview/tools/python-sdk"}]},{"tab":"API","pages":[{"title":"API","href":"/api-reference/overview"}]},{"tab":"Changelog","pages":[{"title":"Changelog","href":"/changelog"}]}]},"basePath":"","title":"Hand-eye calibration - Cyberwave Docs","buildId":"dpl_DqYu5MPb49gWBBbUGgpHQUmCHY3P","deploymentId":"dpl_DqYu5MPb49gWBBbUGgpHQUmCHY3P","dynamics":[],"chunks":[]}