You run the calibration launch, RViz renders the marker and the robot, and rosnode list even shows the sampling GUI node — but no hand-eye calibration window ever appears. Load the rqt_easy_handeye plugin by hand and the failure becomes explicit: the plugin dies with TypeError: 'NoneType' object is not callable. That is not a broken rqt install. It means the plugin started without the namespace of your calibration server, so its service proxy was never initialized. This guide takes the failure apart in test order — namespace, service, network, TF frames, move_group — and closes with the checks that keep a computed transform trustworthy on ROS 1 Noetic under Ubuntu 20.04.
The symptom: the node is alive, the window is not
In the failing session, roslaunch brought up RViz and motion planning without complaint. rosnode list showed both halves of the calibration: the server at /ur10_k4a_handeyecalibration_eye_on_base/easy_handeye_calibration_server, plus GUI processes whose names end in _rqt_sampling_gui under the same namespace. The launch, in other words, had done its job. What never arrived was a window, and adding the plugin from a bare rqt session turned a silent failure into a hard one:
[WARN] No namespace was passed at construction; remember to use set_namespace()
PluginManager._load_plugin() could not load plugin "rqt_easy_handeye/Hand-eye Calibration"
File "/home/user/catkin_ws/src/easy_handeye/rqt_easy_handeye/src/rqt_easy_handeye/rqt_easy_handeye.py", line 74, in __init__
resp = self.client.list_algorithms()
File "/home/user/catkin_ws/src/easy_handeye/easy_handeye/src/easy_handeye/handeye_client.py", line 84, in list_algorithms
return self.list_algorithms_proxy()
TypeError: 'NoneType' object is not callable
Read it from the bottom. Line 84 of handeye_client.py calls self.list_algorithms_proxy(), that proxy is None, and Python raises the TypeError. Line 74 of rqt_easy_handeye.py is the plugin constructor making its first call through the client. The warning at the top names the cause: the plugin was constructed with no namespace, so nothing initialized the proxy. The wider sweep of plugin load failures lives in Hand-eye calibration plugin load failure: diagnostic steps; the namespace cause gets the deep treatment below.
Why the plugin dies: one namespace binds the whole calibration
easy_handeye puts everything for one calibration under a single namespace built from two inputs. namespace_prefix is the argument your launch include sets. The suffix comes from the eye_on_hand boolean: true appends _eye_on_hand, false appends _eye_on_base. Inside that namespace sits the service easy_handeye_calibration_server, and the GUI binds to it before it can show anything useful.
Worked example from the failing launch:
namespace_prefix = ur10_k4a_handeyecalibration
eye_on_hand = false -> suffix = _eye_on_base
namespace = ur10_k4a_handeyecalibration_eye_on_base
server service = /ur10_k4a_handeyecalibration_eye_on_base/easy_handeye_calibration_server
That last line matches the node path rosnode list printed, so the launch had built the namespace correctly. A GUI that never receives the string has no service to bind to, which is why you see a NoneType crash instead of a connection error.
The fix: hand the GUI the namespace before it loads
- Confirm the server exists before any GUI starts. Run rosservice list | grep easy_handeye_calibration_server and expect the full path from the example above.
- Prefer the launch-started window. calibrate.launch passes the namespace to the sampling GUI it spawns, which is why that window loads cleanly. The failing launch had its start_sampling_gui argument commented out, so keep yours enabled.
- For a window on your own terms, close the half-started instances first, then run rqt --standalone rqt_easy_handeye and pass the plugin the same namespace argument: namespace_prefix plus the _eye_on_hand or _eye_on_base suffix. The warning "No namespace was passed at construction" means it started bare and will fail the same way.
- Check the result: the panel opens, names your calibration's namespace, and listing the algorithms returns without a traceback.
Cause 2: the namespace string does not match
The suffix rule trips many setups. eye_on_hand is a boolean argument on the calibrate.launch include, and it decides the namespace: _eye_on_hand for true, _eye_on_base for false. Point the GUI at the other one and its client waits on a service that will never exist. The test is mechanical — put the rosservice list output beside the namespace you pass to the GUI and compare character by character. Prefix drift is the usual culprit, handeyecalibration against handeye_calibration, and it survives a casual glance. Define namespace_prefix once and derive both the server and the GUI from it, and this cause disappears.
Cause 3: the server runs on the robot's own PC
In the failing cell the robot ran from its own controller PC while the operator's PC ran RViz and rqt. That split works only when both machines share one ROS master: ROS_MASTER_URI identical on both, ROS_HOSTNAME or ROS_IP set to an address the other side can reach, and names that resolve, with /etc/hosts entries covering it when DNS does not.
Test from the GUI machine, in order. rosservice list must show the calibration server. rosservice info /ur10_k4a_handeyecalibration_eye_on_base/easy_handeye_calibration_server must return a provider node and URI. A call to the list-algorithms service must come back instead of timing out. A timeout is a transport problem, not a plugin problem — the same reachability triage as a 3D camera that won't ping on a Linux controller: one master, reachable addresses, no firewall in between.
Cause 4: a TF frame is missing or misnamed
easy_handeye takes four frames from your launch. The failing session set tracking_base_frame to camera_base, tracking_marker_frame to board, robot_base_frame to ra_base_link and robot_effector_frame to ra_wrist_3_link. All four must exist and hang off one connected tree, with the tracker publishing the marker frame continuously. The rename trap is real: that launch had moved robot_base_frame from base_link to ra_base_link, and any name the robot driver does not publish stalls the server before the GUI can do anything. Repairing the TF tree unblocks a surprising share of these sessions.
Test with rosrun rqt_tf_tree rqt_tf_tree and check that all four names appear exactly as the launch spells them. A marker frame that never appears points at the tracker, not the calibration server: glance at its debug topic such as /aruco_tracker/debug and make sure the camera feed it subscribes to carries detections. The hand-eye target detection topic guide covers picking that feed. Intrinsics belong in the same pass, because moved intrinsics wobble every pose estimate; the camera intrinsics for hand-eye calibration guide explains why the pose comes first.
Target size rounds out the check. The failing setup tracked a 3 × 3 ChArUco board with 0.024 m squares and 0.016 m markers on the DICT_6X6_250 dictionary, workable at desk range. Scale the board until it fills a healthy fraction of the image at your working distance; the hand-eye calibration target size guide works out how big.
Cause 5: move_group, when the robot moves itself
With freehand_robot_movement set to false, the GUI plans each sampling motion through MoveIt into the group named by the move_group argument, and calibrate.launch defaults it to manipulator. A cell whose SRDF group carries any other name never moves during sampling, and the session stalls there even with the window open. Set move_group to your SRDF group name and the planner has a target. Keep robot_velocity_scaling at 0.5 or below and robot_acceleration_scaling at 0.2 or below for the first calibrations, the values the failing launch used. Unreachable poses during those moves have their own fix in Calibration pose unreachable: scale the grid to robot reach.
Once the window opens: sanity-check the result
Run one geometry smell test before you trust the numbers. Tape the distance from camera to robot base; 1.15 m in this example. The norm of the calibrated translation should land close to it:
For t = (1.05, 0.42, 0.31) m the arithmetic runs sqrt(1.1025 + 0.1764 + 0.0961) = sqrt(1.3750) = 1.17 m, which sits 23 mm from the tape — the right ballpark for a rigid mount. A norm near 0.40 m against a 1.15 m tape means the transform was computed against the wrong frames, not that the solver failed. Then make it formal: Hand-eye calibration accuracy verification: test before you trust it gives the test rig, and Eye-to-hand calibration: verify the camera-to-base result covers this eye-on-base layout.
Mount it so the result stays true
Eye-on-base freezes the camera; eye-on-hand freezes the target. Freeze your side with hardware rather than hope: a stand that creeps between sessions hands you a new transform every time. The camera stands range exists for exactly this — the LS-MNT-0040 professional slide-table stand holds a sensor at a 700 mm column height on a 400 x 450 mm base, and its slide clamps once the working distance is set.
- Type
- Camera stands
On the camera side, the 3D sensors catalog runs from laser profilers to stereo and structured-light cameras. A projected structured-light unit such as the MV-DPS200P-02 is the class of sensor whose depth-to-robot transform this calibration delivers: fix it once, calibrate once, and later point clouds land in robot coordinates.
- Type
- Stereo / structured light
Before you relaunch: the checklist
- Service: rosservice list shows easy_handeye_calibration_server under the exact namespace, namespace_prefix plus _eye_on_hand or _eye_on_base.
- GUI: started by calibrate.launch, or standalone with that same namespace passed in — never added from a bare rqt session.
- Network: one ROS master, ROS_MASTER_URI identical on both machines, addresses mutually reachable, service calls returning instead of timing out.
- TF: rqt_tf_tree shows tracking_base_frame, tracking_marker_frame, robot_base_frame and robot_effector_frame connected, with the marker frame publishing.
- Robot: move_group matches your SRDF group, velocity and acceleration scaling at or below 0.5 and 0.2, cell clear before sampling.
- Result: translation norm within about 23 mm of the taped camera-to-base distance — in this example 1.17 m against 1.15 m — then a formal accuracy test.
A window that loads is the milestone; a transform that survives verification is the deliverable. If the picks still land off after a clean verification, bring the launch file and the tf tree output to the contact page and we will read them with you. If you are speccing a cell instead of repairing one, start at the camera finder and the stand configurator; the rest of the hand-eye guides sit on the systems topic page.
Întrebări
Why does the plugin fail in rqt but the same GUI loads from roslaunch?
calibrate.launch starts the sampling GUI itself and passes the calibration namespace to it, so the client initializes against the right service. A plain rqt session has no namespace to pass: the constructor logs the warning, the service proxy stays None, and the first call dies with the NoneType error. Start the window from the launch, or run rqt --standalone rqt_easy_handeye and pass the namespace matching your namespace_prefix plus the _eye_on_hand or _eye_on_base suffix.
How do I find the exact namespace my calibration uses?
Run rosservice list | grep easy_handeye_calibration_server while the launch is up. The namespace is your namespace_prefix with _eye_on_hand appended when eye_on_hand is true and _eye_on_base when it is false. Use that exact string for the GUI: a single character of drift, such as handeyecalibration versus handeye_calibration, sends the client to a service that does not exist and reproduces the crash.
Does the hand-eye GUI need MoveIt set up correctly?
Only when the robot moves itself. With freehand_robot_movement set to false, the GUI plans each sampling pose through MoveIt into the move_group named in calibrate.launch, which defaults to manipulator. Set that argument to your SRDF group name, and keep robot_velocity_scaling and robot_acceleration_scaling at or below 0.5 and 0.2 for the first sessions. With freehand movement you jog the robot yourself and those settings stay idle.
Which frames must show up in the TF tree before sampling?
Four, all connected: tracking_base_frame, tracking_marker_frame, robot_base_frame and robot_effector_frame — in the failing session camera_base, board, ra_base_link and ra_wrist_3_link. Open rqt_tf_tree and confirm each name exists exactly as the launch spells it and hangs off one tree. A marker frame that never appears points at the tracker, not the calibration server: check its debug topic and the camera feed before re-running anything.
Can the GUI run on a different PC from the robot controller?
Yes, if both share one ROS master. Point ROS_MASTER_URI on the GUI machine at the master, set ROS_HOSTNAME or ROS_IP to an address the other side can reach, and cover name resolution in /etc/hosts if needed. Then confirm from the GUI machine that rosservice list shows the calibration server and that a service call returns instead of timing out. A timeout means the network path, not the plugin.

