Loading [MathJax]/extensions/tex2jax.js
iCub-main
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
FineCalibrationCheckerThread.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2025 iCub Facility - Istituto Italiano di Tecnologia
3 * Author: Jacopo Losi
4 * email: jacopo.losi@iit.it
5 * Permission is granted to copy, distribute, and/or modify this program
6 * under the terms of the GNU General Public License, version 2 or any
7 * later version published by the Free Software Foundation.
8 *
9 * A copy of the license can be found at
10 * http://www.robotcub.org/icub/license/gpl.txt
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details
16 */
17// -*- mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
18
19// std includes
20#include <iostream>
21#include <algorithm>
22#include <unordered_map>
23#include <set>
24#include <utility>
25#include <fstream>
26#include <sstream>
27#include <filesystem>
28
29// yarp includes
30#include <yarp/os/LogStream.h>
31#include <yarp/os/Property.h>
32#include <yarp/os/Log.h>
33#include <yarp/os/Searchable.h>
34
35// APIs
37
38namespace
39{
40 YARP_LOG_COMPONENT(FineCalibrationCheckerThreadCOMPONENT, "yarp.device.fineCalibrationCheckerThread")
41}
42
44 : yarp::os::Thread(), _deviceName("fineCalibrationChecker"), _portPrefix("/fineCalibrationChecker"),
45 _robotName("icub"), _remoteRawValuesPort("/icub/rawvalues"), _controlBoardsList(yarp::os::Bottle()), _axesNamesList(yarp::os::Bottle()), _deviceStatus(deviceStatus::NONE)
46{
47 // Initialize device driver as empty PolyDriver
48 _fineCalibrationCheckerDevice = std::make_unique<yarp::dev::PolyDriver>();
49 _rawValuesOublisherDevice = std::make_unique<yarp::dev::PolyDriver>();
50
51 // Read configuration file
52 yarp::os::Searchable &conf_group = rf.findGroup("GENERAL");
53 if (conf_group.isNull())
54 {
55 yCWarning(FineCalibrationCheckerThreadCOMPONENT) << "Missing GENERAL group! The module uses the default values";
56 }
57 else
58 {
59 // Read parameters from the configuration file
60 if (conf_group.check("devicename")) { _deviceName = conf_group.find("devicename").asString(); }
61 if (conf_group.check("robotname")) { _robotName = conf_group.find("robotname").asString(); }
62 if (conf_group.check("remoteRawValuesPort")) { _remoteRawValuesPort = conf_group.find("remoteRawValuesPort").asString(); }
63 if (conf_group.check("remoteControlBoardsList"))
64 {
65 yarp::os::Bottle* subpartsList = conf_group.find("remoteControlBoardsList").asList();
66 yarp::os::Bottle &controlBoards = _controlBoardsList.addList();
67 for (size_t i = 0; i < subpartsList->size(); i++)
68 {
69 controlBoards.addString("/" + _robotName + "/" + subpartsList->get(i).asString());
70 }
71 }
72 if (conf_group.check("axesNamesList"))
73 {
74 yarp::os::Bottle* _jointsList = conf_group.find("axesNamesList").asList();
75 yarp::os::Bottle &axesNames = _axesNamesList.addList();
76 for (size_t i = 0; i < _jointsList->size(); i++)
77 {
78 axesNames.addString(_jointsList->get(i).asString());
79 }
80 }
81 }
82
83 // Read raw golden hard-stop positions from csv file and save them in map
84 std::ifstream file("zeroPositionsData.csv");
85 std::string line;
86 while (std::getline(file, line))
87 {
88 std::istringstream iss(line);
89 std::string key, pos, res;
90 if (std::getline(iss, key, ',') && std::getline(iss, pos, ',') && std::getline(iss, res)) {
91 axesRawGoldenPositionsResMap[key] = {std::stoi(pos), std::stoi(res)};
92 }
93 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << "Zero GOLDEN position for joint" << key
94 << "is" << axesRawGoldenPositionsResMap[key][0]
95 << "and its encoder has" << axesRawGoldenPositionsResMap[key][1] << "ticks per revolution";
96 }
97
98 _deviceStatus = deviceStatus::INITIALIZED;
99}
100
102{
103 // Just for debug. Check the list of controlBoards and axesNames
104 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Configuring controlboards" << _controlBoardsList.toString();
105 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Configuring joint" << _axesNamesList.toString();
106
107
108 // Initialize the remote control boards
109 yarp::os::Property deviceProperties;
110
111 deviceProperties.put("device", "remotecontrolboardremapper");
112 deviceProperties.put("axesNames", _axesNamesList.get(0));
113 deviceProperties.put("remoteControlBoards", _controlBoardsList.get(0));
114 deviceProperties.put("localPortPrefix", "/" + _deviceName);
115
116 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Configuring device driver with properties:" << deviceProperties.toString();
117
118 _fineCalibrationCheckerDevice->open(deviceProperties);
119
120 if (!_fineCalibrationCheckerDevice->isValid())
121 {
122 yCError(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Unable to open device driver. Aborting...";
123 return false;
124 }
125
126 yarp::os::Property rawValuesDeviceProperties;
127 rawValuesDeviceProperties.put("device", "rawValuesPublisherClient");
128 rawValuesDeviceProperties.put("remote", _remoteRawValuesPort);
129 rawValuesDeviceProperties.put("local", "/" + _deviceName + "/rawValuesPublisherClient");
130
131 _rawValuesOublisherDevice->open(rawValuesDeviceProperties);
132
133 if (!_rawValuesOublisherDevice->isValid())
134 {
135 yCError(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Unable to open raw values device driver. Aborting...";
136 return false;
137 }
138
139 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Opened all devices successfully";
140
141 _deviceStatus = deviceStatus::OPENED;
142 return true;
143
144}
145
147{
148 // Run the calibration thread
149 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Thread started to run";
150
151 while(!this->isStopping())
152 {
153
154 if(_deviceStatus == deviceStatus::OPENED)
155 {
156 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << "Configuring calibration device";
157 if(!configureCalibration())
158 {
159 yCError(FineCalibrationCheckerThreadCOMPONENT) << "Failed to configure device";
160 }
161 else
162 {
163 _deviceStatus = deviceStatus::CONFIGURED;
164 }
165 }
166 else if(_deviceStatus == deviceStatus::CONFIGURED)
167 {
168 if(!runCalibration())
169 {
170 yCError(FineCalibrationCheckerThreadCOMPONENT) << "Failed to calibrate axesNames attached to the device";
171 }
172 else
173 {
174 _deviceStatus = deviceStatus::CALIBRATED;
175 }
176 }
177 else
178 {
179 if((_icontrolcalib->calibrationDone(0)) && (_deviceStatus == deviceStatus::CALIBRATED))
180 {
181 if(!_iravap->getRawDataMap(rawDataValuesMap))
182 {
183 yCWarning(FineCalibrationCheckerThreadCOMPONENT) << "telemetryDeviceDumper warning : raw_data_values was not read correctly";
184 }
185 else
186 {
187 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << "Get raw values from encoders:";
188 for (auto [key,value] : rawDataValuesMap)
189 {
190 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << "\t key:" << key << "value:" << value;
191 }
192
193 // Here we have to evaluate the delta between the raw golen position
194 // and the raw position read at the hard stop per each axis
195 evaluateHardStopPositionDelta(_rawValuesTag, "zeroPositionsData.csv", "zeroPositionsDataDelta.csv");
196
198 }
199 if(_deviceStatus == deviceStatus::END_POSITION_CHECKED)
200 {
201 if(!_iremotecalib->homingSingleJoint(0))
202 {
203 yCError(FineCalibrationCheckerThreadCOMPONENT) << "Failed to homing axesNames attached to the device";
204 }
205 else
206 {
207 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << "Successfully homing axesNames attached to the device";
208 _deviceStatus = deviceStatus::IN_HOME_POSITION;
209 }
210 }
211 }
212 }
213 }
214
215 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Thread stopping";
216 // Perform any cleanup or finalization tasks here
217 // For example, you can close the device or release resources
218
219}
220
222{
223 // Release resources and close the device
224
225 // Close all devices
226 if(this->isStopping())
227 {
228 if(_fineCalibrationCheckerDevice->close())
229 {
230 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << "Closed device" << _deviceName;
231 }
232 else
233 {
234 yCError(FineCalibrationCheckerThreadCOMPONENT) << "Unable to close device" << _deviceName;
235 }
236 }
237
238}
239
241{
242 //nothing to do for now
243}
244
245// Private methods
247{
248 // Check if the calibration was successful
249 return (_deviceStatus == deviceStatus::CALIBRATED);
250}
251
252
253bool FineCalibrationCheckerThread::configureCalibration()
254{
255 // Configure the calibration parameters here
256 // For example, you can set the calibration parameters for each subpart
257 // Configure the calibration parameters for the specified subpart
258
259 if (!_fineCalibrationCheckerDevice->view(_imot) || _imot == nullptr)
260 {
261 yCError(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Unable to open motor interface. Aborting...";
262 return false;
263 }
264 // Initialize parametric calibrator device
265 if (!_fineCalibrationCheckerDevice->view(_iremotecalib) || _iremotecalib == nullptr)
266 {
267 yCError(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Unable to open remote calibrator interface. Aborting...";
268 return false;
269 }
270
271 if (!_fineCalibrationCheckerDevice->view(_icontrolcalib) || _icontrolcalib == nullptr)
272 {
273 yCError(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Unable to open control calibration interface. Aborting...";
274 return false;
275 }
276
277 if (!_rawValuesOublisherDevice->view(_iravap) || _iravap == nullptr)
278 {
279 yCError(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Unable to open raw values publisher interface. Aborting...";
280 return false;
281 }
282
283
284 // Configuring raw values metadata
285 rawDataMetadata = {};
286 _iravap->getMetadataMap(rawDataMetadata);
287 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Configured raw values with metadata";
288 for (auto [k, m] : rawDataMetadata.metadataMap)
289 {
290 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName
291 << "\t Key: " << k << "\n"
292 << "\t axesName: " << m.axesNames << "\n"
293 << "\t rawValueNames: " << m.rawValueNames;
294 }
295
296 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Opened remote calibrator and control calibration interfaces successfully";
297 return true;
298}
299
300bool FineCalibrationCheckerThread::runCalibration()
301{
302 // Implement the calibration logic here
303 // For example, you can call methods on _iremotecalib and _icontrolcalib to perform calibration tasks
304
305 int motors = 0;
306 _imot->getNumberOfMotors(&motors);
307 bool isCalibratorSet = false;
308 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Starting calibration process on joint" << motors;
309 if(_iremotecalib->isCalibratorDevicePresent(&isCalibratorSet))
310 {
311 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Calibration device present!. Go on with calibration procedure";
312
313 if (_iremotecalib->calibrateSingleJoint(0))
314 {
315 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Calibration of joint ended successfully!";
316 }
317 else
318 {
319 yCError(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Unable to start calibration!. Closing!";
320 return false;
321 }
322 }
323 else
324 {
325 yCError(FineCalibrationCheckerThreadCOMPONENT) << _deviceName << "Calibration device not set yet. Closing!";
326 return false;
327 }
328
329 return true;
330}
331
332void FineCalibrationCheckerThread::evaluateHardStopPositionDelta(const std::string& key, const std::string& inputFileName, const std::string& outputFileName)
333{
334 // Get the directory of the input file
335 std::filesystem::path inputPath(inputFileName);
336 std::filesystem::path outputPath = inputPath.parent_path() / outputFileName;
337 int32_t goldPosition = 0;
338 int32_t rawPosition = 0;
339 int32_t resolution = 0;
340 int32_t rescaledPos = 0;
341 int32_t delta = 0;
342 std::vector<ItemData> sampleItems = {};
343
344 std::ofstream outFile(outputPath);
345 if (!outFile.is_open())
346 {
347 yCError(FineCalibrationCheckerThreadCOMPONENT) << "Unable to open output file:" << outputPath.string();
348 return;
349 }
350
351 if(auto it = rawDataValuesMap.find(key); it != rawDataValuesMap.end())
352 {
353 std::vector<std::string> axesNames = {};
354 std::vector<std::int32_t> rawData = {};
355 _iravap->getAxesNames(it->first, axesNames);
356 _iravap->getRawData(it->first, rawData);
357
358 for (size_t i = 0; i < axesNames.size(); ++i)
359 {
360 goldPosition = 0;
361 rawPosition = 0;
362 resolution = 0;
363 delta = 0;
364 if (auto it = axesRawGoldenPositionsResMap.find(axesNames[i]); it != axesRawGoldenPositionsResMap.end())
365 {
366 goldPosition = it->second[0];
367 resolution = it->second[1];
368 rawPosition = rawData[3*i]; // This because the raw values for tag eoprot_tag_mc_joint_status_addinfo_multienc
369 // are stored in a vector whose legth is joints_number*3, where each sub-array is made such
370 // [raw_val_primary_enc, raw_val_secondary_enc, rraw_val_auxiliary_enc]
371 // and we want the first value for each joint
372 rescaledPos = goldPosition * resolution / 65535; // Rescale the position (in iCubDegrees) to the encoder full resolution
373 delta = std::abs(rescaledPos - rawPosition);
374 }
375
376 // Write to output CSV file
377 outFile << axesNames[i] << "," << goldPosition << "," << rescaledPos << "," << rawPosition << "," << delta << "\n";
378 sampleItems.push_back({axesNames[i], goldPosition, rescaledPos, rawPosition, delta});
379 }
380 }
381 else
382 {
383 yCError(FineCalibrationCheckerThreadCOMPONENT) << "Key" << key << "not found in rawDataValuesMap";
384 outFile << "Key not found in rawDataValuesMap\n";
385 }
386
387 outFile.close();
388 yCDebug(FineCalibrationCheckerThreadCOMPONENT) << "Output CSV written to:" << outputPath.string();
389
390 generateOutputImage(800, 400, sampleItems);
391}
392
393void FineCalibrationCheckerThread::configureDevicesMap(std::vector<std::string> list)
394{
395 // Configure the devices map based on the provided list of subpart names
396
397 // std::vector<std::string> input = list; // Example input list
398 // std::vector<std::string> desired_order = _robotSubpartsWrapper; // Example desired order
399
400 // // Step 1: Create the order map
401 // std::unordered_map<std::string, int> order_map;
402 // for (size_t i = 0; i < _robotSubpartsWrapper.size(); ++i) {
403 // order_map[_robotSubpartsWrapper[i]] = i;
404 // }
405
406 // // Step 2: Separate known and unknown elements
407 // std::vector<std::string> known_items;
408 // std::vector<std::string> unknown_items;
409
410 // for (const auto& item : list) {
411 // if (order_map.count(item)) {
412 // known_items.push_back(item);
413 // } else {
414 // unknown_items.push_back(item); // Handle unknowns
415 // }
416 // }
417
418 // // Step 3: Sort known items using desired order
419 // std::sort(known_items.begin(), known_items.end(), [&](const std::string& a, const std::string& b) {
420 // return order_map[a] < order_map[b];
421 // });
422
423 // // Step 4: Optionally detect missing elements
424 // std::set<std::string> input_set(list.begin(), list.end());
425 // std::vector<std::string> missing_items;
426 // for (const auto& expected : _robotSubpartsWrapper) {
427 // if (!input_set.count(expected)) {
428 // missing_items.push_back(expected);
429 // }
430 // }
431
432 // // Output results
433 // std::cout << "Sorted known items:\n";
434 // for (const auto& item : known_items) std::cout << item << " ";
435 // std::cout << "\n";
436
437 // if (!unknown_items.empty()) {
438 // std::cout << "Unknown items (not in desired order):\n";
439 // for (const auto& item : unknown_items) std::cout << item << " ";
440 // std::cout << "\n";
441 // }
442
443 // if (!missing_items.empty()) {
444 // std::cout << "Missing expected items:\n";
445 // for (const auto& item : missing_items) std::cout << item << " ";
446 // std::cout << "\n";
447 // }
448
449}
450
451void FineCalibrationCheckerThread::generateOutputImage(int frameWidth, int frameHeight, const std::vector<ItemData>& items)
452{
453 cv::Mat image = cv::Mat::zeros(frameHeight, frameWidth, CV_8UC3);
454 image.setTo(cv::Scalar(255, 255, 255)); // White background
455
456 int lineHeight = 30;
457 int padding = 10;
458 int textOffset = 20;
459
460 for (size_t i = 0; i < items.size(); ++i)
461 {
462 int y = padding + i * lineHeight;
463 // Draw background rectangle for the row
464 cv::Scalar bgColor = getColorForDelta(items[i].val3, 1, 3);
465 cv::rectangle(image,
466 cv::Point(padding, y),
467 cv::Point(frameWidth - padding, y + lineHeight - 5),
468 bgColor, cv::FILLED);
469
470 // Draw border for the row
471 cv::rectangle(image,
472 cv::Point(padding, y),
473 cv::Point(frameWidth - padding, y + lineHeight - 5),
474 cv::Scalar(200, 200, 200), 1);
475
476 // Compose text: name val1 val2 val3
477 std::string text = items[i].name + " " +
478 std::to_string(items[i].val1) + " " +
479 std::to_string(items[i].val2) + " " +
480 std::to_string(items[i].val3) + " " +
481 std::to_string(items[i].val4);
482
483 cv::putText(image, text, cv::Point(padding + 5, y + textOffset),
484 cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 0), 2);
485 }
486
487 cv::imwrite("output_frame.png", image);
488 cv::imshow("Output Frame", image);
489 cv::waitKey(0);
490 cv::destroyAllWindows();
491}
492
493cv::Scalar FineCalibrationCheckerThread::getColorForDelta(int32_t delta, int32_t threshold_1, int32_t threshold_2)
494{
495 if (delta > threshold_2) return cv::Scalar(0, 0, 255); // Red
496 else if (delta > threshold_1) return cv::Scalar(0, 165, 255); // Orange
497 else return cv::Scalar(0, 255, 0); // Green
498}
499
virtual bool getMetadataMap(rawValuesKeyMetadataMap &metamap)=0
virtual bool getRawData(std::string key, std::vector< std::int32_t > &data)=0
virtual bool getAxesNames(std::string key, std::vector< std::string > &axesNames)=0
virtual bool getRawDataMap(std::map< std::string, std::vector< std::int32_t > > &map)=0
FILE * outFile
FILE * file
Definition main.cpp:81
Copyright (C) 2008 RobotCub Consortium.