42 const std::string
LOGNAME =
"handeye_control_widget";
46 QHBoxLayout* row =
new QHBoxLayout(
this);
47 row->setContentsMargins(0, 10, 0, 10);
50 name_label_ =
new QLabel(
"Recorded joint state progress:",
this);
51 name_label_->setContentsMargins(0, 0, 0, 0);
52 row->addWidget(name_label_);
54 value_label_ =
new QLabel(QString::number(value),
this);
55 value_label_->setContentsMargins(0, 0, 0, 0);
56 row->addWidget(value_label_);
59 bar_ =
new QProgressBar(
this);
60 bar_->setTextVisible(
true);
61 bar_->setMinimum(min);
62 bar_->setMaximum(max);
63 bar_->setValue(value);
64 bar_->setContentsMargins(0, 0, 0, 0);
65 bar_->setDisabled(max == 0);
68 max_label_ =
new QLabel(QString::number(max),
this);
69 max_label_->setContentsMargins(0, 0, 0, 0);
70 row->addWidget(max_label_);
84 bar_->setMinimum(value);
89 bar_->setValue(value);
100 , calibration_display_(pdisplay)
101 , tf_buffer_(new
tf2_ros::Buffer())
102 , tf_listener_(*tf_buffer_)
104 , from_frame_tag_(
"base")
105 , solver_plugins_loader_(nullptr)
107 , move_group_(nullptr)
109 , auto_started_(false)
113 QVBoxLayout* layout =
new QVBoxLayout();
114 this->setLayout(layout);
116 QHBoxLayout* calib_layout =
new QHBoxLayout();
117 layout->addLayout(calib_layout);
121 layout->addWidget(auto_progress_);
124 QGroupBox* sample_group =
new QGroupBox(
"Pose samples");
125 sample_group->setMinimumWidth(280);
126 calib_layout->addWidget(sample_group);
127 QVBoxLayout* sample_layout =
new QVBoxLayout();
128 sample_group->setLayout(sample_layout);
130 sample_tree_view_ =
new QTreeView(
this);
131 sample_tree_view_->setAutoScroll(
true);
132 sample_tree_view_->setAlternatingRowColors(
true);
133 tree_view_model_ =
new QStandardItemModel(sample_tree_view_);
134 sample_tree_view_->setModel(tree_view_model_);
135 sample_tree_view_->setHeaderHidden(
true);
136 sample_tree_view_->setIndentation(10);
137 sample_layout->addWidget(sample_tree_view_);
138 reprojection_error_label_ =
new QLabel(
"Reprojection error: N/A");
139 sample_layout->addWidget(reprojection_error_label_);
142 QVBoxLayout* layout_right =
new QVBoxLayout();
143 calib_layout->addLayout(layout_right);
145 QGroupBox* setting_group =
new QGroupBox(
"Settings");
146 layout_right->addWidget(setting_group);
147 QVBoxLayout* setting_layout =
new QVBoxLayout();
148 setting_group->setLayout(setting_layout);
149 QFormLayout* setting_layout_top =
new QFormLayout();
150 QGridLayout* setting_layout_bottom =
new QGridLayout();
151 setting_layout->insertLayout(0, setting_layout_top);
152 setting_layout->insertLayout(1, setting_layout_bottom);
154 calibration_solver_ =
new QComboBox();
155 setting_layout_top->addRow(
"AX=XB Solver", calibration_solver_);
157 group_name_ =
new QComboBox();
158 connect(group_name_, SIGNAL(activated(
const QString&)),
this, SLOT(planningGroupNameChanged(
const QString&)));
159 setting_layout_top->addRow(
"Planning Group", group_name_);
161 load_joint_state_btn_ =
new QPushButton(
"Load joint states");
162 connect(load_joint_state_btn_, SIGNAL(clicked(
bool)),
this, SLOT(loadJointStateBtnClicked(
bool)));
163 setting_layout_bottom->addWidget(load_joint_state_btn_, 0, 0);
165 save_joint_state_btn_ =
new QPushButton(
"Save joint states");
166 connect(save_joint_state_btn_, SIGNAL(clicked(
bool)),
this, SLOT(saveJointStateBtnClicked(
bool)));
167 setting_layout_bottom->addWidget(save_joint_state_btn_, 0, 1);
169 load_samples_btn_ =
new QPushButton(
"Load samples");
170 connect(load_samples_btn_, SIGNAL(clicked(
bool)),
this, SLOT(loadSamplesBtnClicked(
bool)));
171 setting_layout_bottom->addWidget(load_samples_btn_, 1, 0);
173 save_samples_btn_ =
new QPushButton(
"Save samples");
174 connect(save_samples_btn_, SIGNAL(clicked(
bool)),
this, SLOT(saveSamplesBtnClicked(
bool)));
175 setting_layout_bottom->addWidget(save_samples_btn_, 1, 1);
177 save_camera_pose_btn_ =
new QPushButton(
"Save camera pose");
178 connect(save_camera_pose_btn_, SIGNAL(clicked(
bool)),
this, SLOT(saveCameraPoseBtnClicked(
bool)));
179 setting_layout_bottom->addWidget(save_camera_pose_btn_, 2, 0, 1, 2);
182 QGroupBox* manual_cal_group =
new QGroupBox(
"Manual Calibration");
183 layout_right->addWidget(manual_cal_group);
184 QHBoxLayout* control_cal_layout =
new QHBoxLayout();
185 manual_cal_group->setLayout(control_cal_layout);
187 take_sample_btn_ =
new QPushButton(
"Take sample");
188 take_sample_btn_->setMinimumHeight(35);
189 connect(take_sample_btn_, SIGNAL(clicked(
bool)),
this, SLOT(takeSampleBtnClicked(
bool)));
190 control_cal_layout->addWidget(take_sample_btn_);
192 reset_sample_btn_ =
new QPushButton(
"Clear samples");
193 reset_sample_btn_->setMinimumHeight(35);
194 connect(reset_sample_btn_, SIGNAL(clicked(
bool)),
this, SLOT(clearSamplesBtnClicked(
bool)));
195 control_cal_layout->addWidget(reset_sample_btn_);
197 solve_btn_ =
new QPushButton(
"Solve");
198 solve_btn_->setMinimumHeight(35);
199 connect(solve_btn_, SIGNAL(clicked(
bool)),
this, SLOT(solveBtnClicked(
bool)));
200 control_cal_layout->addWidget(solve_btn_);
203 QGroupBox* auto_cal_group =
new QGroupBox(
"Calibrate With Recorded Joint States");
204 layout_right->addWidget(auto_cal_group);
205 QVBoxLayout* auto_cal_layout =
new QVBoxLayout();
206 auto_cal_group->setLayout(auto_cal_layout);
208 QHBoxLayout* auto_btns_layout =
new QHBoxLayout();
209 auto_cal_layout->addLayout(auto_btns_layout);
210 auto_plan_btn_ =
new QPushButton(
"Plan");
211 auto_plan_btn_->setMinimumHeight(35);
212 auto_plan_btn_->setToolTip(
"Plan next calibration pose");
213 connect(auto_plan_btn_, SIGNAL(clicked(
bool)),
this, SLOT(autoPlanBtnClicked(
bool)));
214 auto_btns_layout->addWidget(auto_plan_btn_);
216 auto_execute_btn_ =
new QPushButton(
"Execute");
217 auto_execute_btn_->setMinimumHeight(35);
218 auto_execute_btn_->setToolTip(
"Execute the planned motion to next calibration pose");
219 connect(auto_execute_btn_, SIGNAL(clicked(
bool)),
this, SLOT(autoExecuteBtnClicked(
bool)));
220 auto_btns_layout->addWidget(auto_execute_btn_);
222 auto_skip_btn_ =
new QPushButton(
"Skip");
223 auto_skip_btn_->setMinimumHeight(35);
224 auto_skip_btn_->setToolTip(
"Skip the current robot state target");
225 connect(auto_skip_btn_, SIGNAL(clicked(
bool)),
this, SLOT(autoSkipBtnClicked(
bool)));
226 auto_btns_layout->addWidget(auto_skip_btn_);
229 std::vector<std::string> plugins;
230 if (loadSolverPlugin(plugins))
231 fillSolverTypes(plugins);
234 fillPlanningGroupNameComboBox();
237 plan_watcher_ =
new QFutureWatcher<void>(
this);
240 execution_watcher_ =
new QFutureWatcher<void>(
this);
244 calibration_display_->setStatus(
rviz::StatusProperty::Ok,
"Calibration",
"Collect 5 samples to start calibration.");
250 config.mapGetString(
"group", &group_name);
254 std::vector<std::string>::const_iterator it = std::find(groups.begin(), groups.end(), group_name.toStdString());
255 if (it != groups.end())
262 config.mapGetString(
"solver", &solver_name);
263 if (!solver_name.isEmpty())
290 "moveit_calibration_plugins",
"moveit_handeye_calibration::HandEyeSolverBase"));
295 "Couldn't create solver plugin loader.");
296 QMessageBox::warning(
this, tr(
"Exception while creating handeye solver plugin loader "), tr(ex.what()));
303 return !plugins.empty();
325 for (
const std::string& plugin : plugins)
328 const std::vector<std::string>& solvers =
solver_->getSolverNames();
329 for (
const std::string& solver : solvers)
331 std::string solver_name = plugin +
"/" + solver;
339 std::vector<std::string> tokens;
341 std::istringstream tokenStream(solver_name);
342 while (std::getline(tokenStream, token, delimiter))
344 tokens.push_back(token);
346 return tokens.back();
354 geometry_msgs::TransformStamped camera_to_object_tf;
355 geometry_msgs::TransformStamped base_to_eef_tf;
365 tf2::fromMsg(camera_to_object_tf.transform.rotation, tf2_quat);
367 camera_to_object_tf.transform.rotation =
tf2::toMsg(tf2_quat);
368 tf2::fromMsg(base_to_eef_tf.transform.rotation, tf2_quat);
370 base_to_eef_tf.transform.rotation =
tf2::toMsg(tf2_quat);
380 ROS_WARN(
"TF exception: %s", e.what());
396 std::string error_message;
411 std::ostringstream reproj_err_text;
412 reproj_err_text <<
"Reprojection error:\n" << reproj_err.first <<
" m, " << reproj_err.second <<
" rad";
419 if (!from_frame.empty() && !to_frame.empty())
427 <<
"to sensor frame '" << to_frame <<
"'");
434 std::stringstream warn_msg;
435 warn_msg <<
"Found camera pose:" << std::endl
442 std::stringstream warn_msg;
443 warn_msg <<
"Found camera pose:<pre>" << std::endl
445 <<
"</pre>but <b>" <<
from_frame_tag_ <<
"</b> or <b>sensor</b> frame is undefined.";
446 QMessageBox::warning(
this,
"Solver Failed", QString::fromStdString(warn_msg.str()));
449 "Calibration successful but frames are undefined.");
456 QMessageBox::warning(
this, tr(
"Solver Failed"), tr((std::string(
"Error: ") + error_message).c_str()));
463 QMessageBox::warning(
this, tr(
"No Solver Available"), tr(
"No available handeye calibration solver instance."));
474 QMessageBox::warning(
this, tr(
"Empty Frame Name"), tr(
"At least one of the four frame names is empty."));
498 const geometry_msgs::TransformStamped& base_to_eef_tf,
int id)
500 std::string item_name =
"Sample " + std::to_string(
id);
501 QStandardItem* parent =
new QStandardItem(QString(item_name.c_str()));
504 std::ostringstream ss;
506 QStandardItem* child_1 =
new QStandardItem(
"TF base-to-eef");
507 ss << base_to_eef_tf.transform;
508 child_1->appendRow(
new QStandardItem(ss.str().c_str()));
509 parent->appendRow(child_1);
511 QStandardItem* child_2 =
new QStandardItem(
"TF camera-to-target");
513 ss << camera_to_object_tf.transform;
514 child_2->appendRow(
new QStandardItem(ss.str().c_str()));
515 parent->appendRow(child_2);
525 case mhc::EYE_TO_HAND:
528 case mhc::EYE_IN_HAND:
542 for (
const std::pair<const std::string, std::string>& name :
frame_names_)
563 const robot_state::RobotState& state = ps->getCurrentState();
571 std::vector<double> state_joint_values;
572 state.copyJointGroupPositions(jmg, state_joint_values);
573 if (names.size() == state_joint_values.size())
601 if (from_frame.empty() || to_frame.empty())
603 QMessageBox::warning(
this, tr(
"Empty Frame Name"),
604 tr(
"Make sure you have selected the correct frames in the Context tab."));
610 QFileDialog::getSaveFileName(
this, tr(
"Save Camera Robot Pose"),
"", tr(
"Target File (*.launch);;All Files (*)"),
611 nullptr, QFileDialog::DontUseNativeDialog);
613 if (file_name.isEmpty())
616 if (!file_name.endsWith(
".launch"))
617 file_name +=
".launch";
619 QFile file(file_name);
620 if (!file.open(QIODevice::WriteOnly))
622 QMessageBox::warning(
this, tr(
"Unable to open file"), file.errorString());
626 QTextStream out(&file);
631 std::stringstream ss;
632 ss <<
"<launch>" << std::endl;
633 ss <<
" <!-- The rpy in the comment uses the extrinsic XYZ convention, which is the same as is used in a URDF. See"
635 ss <<
" http://wiki.ros.org/geometry2/RotationMethods and https://en.wikipedia.org/wiki/Euler_angles for more "
638 ss <<
" <!-- xyz=\"" <<
t[0] <<
" " <<
t[1] <<
" " <<
t[2] <<
"\" rpy=\"" << r_euler[0] <<
" " << r_euler[1] <<
" "
639 << r_euler[2] <<
"\" -->" << std::endl;
640 ss <<
" <node pkg=\"tf2_ros\" type=\"static_transform_publisher\" name=\"camera_link_broadcaster\"" << std::endl;
641 ss <<
" args=\"" <<
t[0] <<
" " <<
t[1] <<
" " <<
t[2] <<
" " << r_quat.x() <<
" " << r_quat.y() <<
" "
642 << r_quat.z() <<
" " << r_quat.w() <<
" " << from_frame <<
" " << to_frame <<
"\" />" << std::endl;
643 ss <<
"</launch>" << std::endl;
644 out << ss.str().c_str();
655 QMessageBox::warning(
this, tr(
"Invalid Group Name"),
"Group name is empty");
674 catch (std::exception& ex)
695 for (
const std::string& group_name : kmodel->getJointModelGroupNames())
709 QMessageBox::warning(
this, tr(
"Error"), tr(
"No joint states or joint state dosen't match joint names."));
715 QFileDialog::getSaveFileName(
this, tr(
"Save Joint States"),
"", tr(
"Target File (*.yaml);;All Files (*)"),
716 nullptr, QFileDialog::DontUseNativeDialog);
718 if (file_name.isEmpty())
721 if (!file_name.endsWith(
".yaml"))
722 file_name +=
".yaml";
724 QFile file(file_name);
725 if (!file.open(QIODevice::WriteOnly))
727 QMessageBox::warning(
this, tr(
"Unable to open file"), file.errorString());
731 YAML::Emitter emitter;
732 emitter << YAML::BeginMap;
735 emitter << YAML::Key <<
"joint_names";
736 emitter << YAML::Value << YAML::BeginSeq;
739 emitter << YAML::EndSeq;
742 emitter << YAML::Key <<
"joint_values";
743 emitter << YAML::Value << YAML::BeginSeq;
746 emitter << YAML::BeginSeq;
749 emitter << YAML::EndSeq;
751 emitter << YAML::EndSeq;
753 emitter << YAML::EndMap;
755 QTextStream out(&file);
756 out << emitter.c_str();
761 QString file_name = QFileDialog::getOpenFileName(
this, tr(
"Load Samples"),
"", tr(
"Target File (*.yaml)"),
nullptr,
762 QFileDialog::DontUseNativeDialog);
764 if (file_name.isEmpty())
771 typedef Eigen::Matrix<double, 4, 4, Eigen::RowMajor> Matrix4d_rm;
773 YAML::Node yaml_states = YAML::LoadFile(file_name.toStdString());
776 for (std::size_t i = 0; i < yaml_states.size(); i++)
779 Eigen::Map<const Matrix4d_rm>(yaml_states[i][
"effector_wrt_world"].as<std::vector<double>>().
data()));
782 Eigen::Map<const Matrix4d_rm>(yaml_states[i][
"object_wrt_sensor"].as<std::vector<double>>().
data()));
793 catch (
const YAML::Exception& e)
795 QMessageBox::critical(
this,
"YAML Exception",
796 QString::fromStdString(
"YAML exception: " + std::string(e.what()) +
797 "\nCheck that the sample file has the correct format."));
811 QFileDialog::getSaveFileName(
this, tr(
"Save Samples"),
"", tr(
"Target File (*.yaml);;All Files (*)"),
nullptr,
812 QFileDialog::DontUseNativeDialog);
814 if (file_name.isEmpty())
817 if (!file_name.endsWith(
".yaml"))
818 file_name +=
".yaml";
820 QFile file(file_name);
821 if (!file.open(QIODevice::WriteOnly))
823 QMessageBox::warning(
this, tr(
"Unable to open file"), file.errorString());
827 YAML::Emitter emitter;
828 emitter << YAML::BeginSeq;
831 emitter << YAML::Value << YAML::BeginMap;
832 emitter << YAML::Key <<
"effector_wrt_world";
833 emitter << YAML::Value << YAML::BeginSeq;
834 for (
size_t y = 0;
y < 4;
y++)
836 for (
size_t x = 0;
x < 4;
x++)
841 emitter << YAML::EndSeq;
842 emitter << YAML::Key <<
"object_wrt_sensor";
843 emitter << YAML::Value << YAML::BeginSeq;
844 for (
size_t y = 0;
y < 4;
y++)
846 for (
size_t x = 0;
x < 4;
x++)
851 emitter << YAML::EndSeq;
852 emitter << YAML::EndMap;
854 emitter << YAML::EndSeq;
856 QTextStream out(&file);
857 out << emitter.c_str();
864 QFileDialog::getOpenFileName(
this, tr(
"Load Joint States"),
"", tr(
"Target File (*.yaml);;All Files (*)"),
865 nullptr, QFileDialog::DontUseNativeDialog);
867 if (file_name.isEmpty() || !file_name.endsWith(
".yaml"))
870 QFile file(file_name);
871 if (!file.open(QIODevice::ReadOnly))
873 QMessageBox::warning(
this, tr(
"Unable to open file"), file.errorString());
881 YAML::Node doc = YAML::LoadFile(file_name.toStdString());
886 const YAML::Node& names = doc[
"joint_names"];
887 if (!names.IsNull() && names.IsSequence())
890 for (YAML::const_iterator it = names.begin(); it != names.end(); ++it)
900 const YAML::Node&
values = doc[
"joint_values"];
904 for (YAML::const_iterator state_it =
values.begin(); state_it !=
values.end(); ++state_it)
906 std::vector<double> jv;
907 if (!state_it->IsNull() && state_it->IsSequence())
908 for (YAML::const_iterator joint_it = state_it->begin(); joint_it != state_it->end(); ++joint_it)
909 jv.push_back(joint_it->as<
double>());
920 catch (YAML::ParserException& e)
976 robot_state::RobotStatePtr start_state =
move_group_->getCurrentState();
981 start_state.reset(
new robot_state::RobotState(ps->getCurrentState()));
1034 QMessageBox::warning(
this, tr(
"Error"),
1035 tr(
"Could not compute plan. No more prerecorded joint states to execute."));
1038 QMessageBox::warning(
this, tr(
"Error"),
1039 tr(
"Could not compute plan. Invalid joint states (names wrong or missing)."));
1042 QMessageBox::warning(
this, tr(
"Error"), tr(
"Could not compute plan. No planning scene monitor."));
1045 QMessageBox::warning(
this, tr(
"Error"), tr(
"Could not compute plan. Missing move_group."));
1048 QMessageBox::warning(
1050 tr(
"Could not compute plan. Joint names for recorded state do not match names from current planning group."));
1053 QMessageBox::warning(
this, tr(
"Error"), tr(
"Could not compute plan. Planning failed."));