iCub-main
Loading...
Searching...
No Matches
FineCalibrationChecker.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#include <chrono>
29
30// yarp includes
31#include <yarp/os/LogStream.h>
32#include <yarp/os/Property.h>
33#include <yarp/os/Log.h>
34#include <yarp/os/Searchable.h>
35
36// APIs
38
39
40namespace
41{
42 YARP_LOG_COMPONENT(FineCalibrationCheckerCOMPONENT, "yarp.device.FineCalibrationChecker")
43}
44
46 : yarp::os::Thread(), _deviceName("fineCalibrationChecker"), _portPrefix("/fineCalibrationChecker"),
47 _robotName("icub"), _remoteRawValuesPort("/icub/rawvalues"), _axesNamesList(yarp::os::Bottle()),
48 _goldPositionsList(yarp::os::Bottle()), _encoderResolutionsList(yarp::os::Bottle()), _calibrationDeltasList(yarp::os::Bottle()), _deviceStatus(deviceStatus::NONE)
49{
50 // Initialize device driver as empty PolyDriver
51 _remappedControlBoardDevice = std::make_unique<yarp::dev::PolyDriver>();
52 _remappedRawValuesPublisherDevice = std::make_unique<yarp::dev::PolyDriver>();
53}
54
55bool FineCalibrationChecker::open(yarp::os::Searchable& config)
56{
57 // Read configuration file
58 yarp::os::Property property;
59 property.fromString(config.toString().c_str());
60 if (property.isNull())
61 {
62 yCError(FineCalibrationCheckerCOMPONENT) << "Failed to read configuration file. Stopping device...";
63 return false;
64 }
65 else
66 {
67 // Read parameters from the configuration file
68 if (property.check("devicename")) { _deviceName = property.find("devicename").asString(); }
69 if (property.check("robotname")) { _robotName = property.find("robotname").asString(); }
70 if (property.check("remoteRawValuesPort")) { _remoteRawValuesPort = property.find("remoteRawValuesPort").asString(); }
71 if (property.check("axesNamesList"))
72 {
73 yarp::os::Bottle* _jointsList = property.find("axesNamesList").asList();
74 yarp::os::Bottle &axesNames = _axesNamesList.addList();
75
76 for (size_t i = 0; i < _jointsList->size(); i++)
77 {
78 axesNames.addString(_jointsList->get(i).asString());
79 }
80 }
81 if(property.check("goldPositions"))
82 {
83 yarp::os::Bottle* _goldPositions = property.find("goldPositions").asList();
84 yarp::os::Bottle &goldPositions = _goldPositionsList.addList();
85
86 for (size_t i = 0; i < _goldPositions->size(); i++)
87 {
88 goldPositions.addInt32(_goldPositions->get(i).asInt32());
89 }
90 }
91 if(property.check("calibrationDeltas"))
92 {
93 yarp::os::Bottle* _calibrationDeltas = property.find("calibrationDeltas").asList();
94 yarp::os::Bottle &calibrationDeltas = _calibrationDeltasList.addList();
95
96 for (size_t i = 0; i < _calibrationDeltas->size(); i++)
97 {
98 calibrationDeltas.addFloat64(_calibrationDeltas->get(i).asFloat64());
99 }
100 }
101 if(property.check("encoderResolutions"))
102 {
103 yarp::os::Bottle* _encoderResolutions = property.find("encoderResolutions").asList();
104 yarp::os::Bottle &encoderResolutions = _encoderResolutionsList.addList();
105
106 for (size_t i = 0; i < _encoderResolutions->size(); i++)
107 {
108 encoderResolutions.addInt32(_encoderResolutions->get(i).asInt32());
109 }
110 }
111 if (property.check("axesSigns"))
112 {
113 yarp::os::Bottle* _axesSigns = property.find("axesSigns").asList();
114 yarp::os::Bottle &axesSigns = _axesSignsList.addList();
115
116 for (size_t i = 0; i < _axesSigns->size(); i++)
117 {
118 axesSigns.addInt32(_axesSigns->get(i).asInt32());
119 }
120 }
121
122
123 // Use pointer to list to simplify the listing
124 yarp::os::Bottle* axes = _axesNamesList.get(0).asList();
125 yarp::os::Bottle* goldpos = _goldPositionsList.get(0).asList();
126 yarp::os::Bottle* encres = _encoderResolutionsList.get(0).asList();
127 yarp::os::Bottle* caldeltas = _calibrationDeltasList.get(0).asList();
128 yarp::os::Bottle* signs = _axesSignsList.get(0).asList();
129
130 // Check list sizes. They must be equal
131 if (axes->size() != goldpos->size() ||
132 axes->size() != encres->size() ||
133 axes->size() != caldeltas->size() ||
134 axes->size() != signs->size())
135 {
136 yCError(FineCalibrationCheckerCOMPONENT) << "Axes names, gold positions and encoder resolutions lists must have the same size. Stopping device...";
137 return false;
138 }
139 else
140 {
141 yCDebug(FineCalibrationCheckerCOMPONENT) << "Axes names list:" << _axesNamesList.toString();
142 yCDebug(FineCalibrationCheckerCOMPONENT) << "Gold positions list:" << goldpos->toString();
143 yCDebug(FineCalibrationCheckerCOMPONENT) << "Encoder resolutions list:" << encres->toString();
144 yCDebug(FineCalibrationCheckerCOMPONENT) << "Calibration deltas list:" << caldeltas->toString();
145 yCDebug(FineCalibrationCheckerCOMPONENT) << "Axes signs list:" << signs->toString();
146 }
147
148
149 for (size_t i = 0; i < axes->size(); i++)
150 {
151 yCDebug(FineCalibrationCheckerCOMPONENT) << "Adding to MAP key:" << axes->get(i).asString()
152 << "GP:" << goldpos->get(i).asInt32() << "ER:" << encres->get(i).asInt32() << "CD:" << caldeltas->get(i).asFloat64();
153 axesRawGoldenPositionsResMap[axes->get(i).asString()] = {goldpos->get(i).asInt32(), encres->get(i).asInt32()};
154 yCDebug(FineCalibrationCheckerCOMPONENT) << "Array added to MAP:" << axesRawGoldenPositionsResMap.at(axes->get(i).asString())[0]
155 << "and" << axesRawGoldenPositionsResMap.at(axes->get(i).asString())[1];
156 }
157 }
158
159 _withGui = property.check("withGui", yarp::os::Value(false)).asBool();
160
161 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Initialized device driver";
162 _deviceStatus = deviceStatus::INITIALIZED;
163
164 // Open ports to control boards and network wrapper servers
165 // Open the port to the remote control boards and connect the remappedControlBoardDevice to it
166 yarp::os::Property deviceProperties;
167
168 deviceProperties.put("device", "controlboardremapper");
169 deviceProperties.put("axesNames", _axesNamesList.get(0));
170
171 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Configuring device driver with properties:" << deviceProperties.toString();
172
173 _remappedControlBoardDevice->open(deviceProperties);
174
175 if (!_remappedControlBoardDevice->isValid())
176 {
177 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open device driver. Aborting...";
178 return false;
179 }
180
181 // Open port to the remote raw values publisher server and connect the client network wrapper to it
182 if (_remoteRawValuesPort.empty())
183 {
184 yCError(FineCalibrationCheckerCOMPONENT) << "Remote raw values port is empty. Cannot open device driver. Stopping device...";
185 return false;
186 }
187 else
188 {
189 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Remote raw values port:" << _remoteRawValuesPort;
190
191 }
192
193 yarp::os::Property rawValuesDeviceProperties;
194 rawValuesDeviceProperties.put("device", "rawvaluespublisherremapper");
195 rawValuesDeviceProperties.put("axesNames", _axesNamesList.get(0));
196 rawValuesDeviceProperties.put("remote", _remoteRawValuesPort);
197 rawValuesDeviceProperties.put("local", "/" + _deviceName + "/rawValuesPublisherRemapper");
198
199 _remappedRawValuesPublisherDevice->open(rawValuesDeviceProperties);
200
201 if (!_remappedRawValuesPublisherDevice->isValid())
202 {
203 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open raw values device driver. Aborting...";
204 return false;
205 }
206
207 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Opened all devices successfully";
208
209 _deviceStatus = deviceStatus::OPENED;
210 return true;
211}
212
214{
215 // Close the device driver and all the opened ports
216 if(_remappedControlBoardDevice->close())
217 {
218 yCDebug(FineCalibrationCheckerCOMPONENT) << "Closed device" << _deviceName;
219 }
220 else
221 {
222 yCError(FineCalibrationCheckerCOMPONENT) << "Unable to close device" << _deviceName;
223 }
224
225 if (_remappedRawValuesPublisherDevice->close())
226 {
227 yCDebug(FineCalibrationCheckerCOMPONENT) << "Closed raw values publisher device";
228 }
229 else
230 {
231 yCError(FineCalibrationCheckerCOMPONENT) << "Unable to close raw values publisher device";
232 }
233 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Closed all devices successfully";
234
235 return true;
236}
237
239{
240 // Initialize the thread --> can add the view on the interfaces here
241 if (!_remappedControlBoardDevice->view(remappedControlBoardInterfaces._imot) || remappedControlBoardInterfaces._imot == nullptr)
242 {
243 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open motor interface. Aborting...";
244 return false;
245 }
246
247 if (!_remappedControlBoardDevice->view(remappedControlBoardInterfaces._ienc) || remappedControlBoardInterfaces._ienc == nullptr)
248 {
249 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open encoders interface. Aborting...";
250 return false;
251 }
252
253 if (!_remappedControlBoardDevice->view(remappedControlBoardInterfaces._icontrolcalib) || remappedControlBoardInterfaces._icontrolcalib == nullptr)
254 {
255 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open control calibration interface. Aborting...";
256 return false;
257 }
258
259 if (!_remappedRawValuesPublisherDevice->view(remappedRawValuesPublisherInterfaces._iravap) || remappedRawValuesPublisherInterfaces._iravap == nullptr)
260 {
261 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open raw values publisher interface. Aborting...";
262 return false;
263 }
264
265
266 // Configuring raw values metadata
267 rawDataMetadata = {};
268 remappedRawValuesPublisherInterfaces._iravap->getMetadataMap(rawDataMetadata);
269 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Configured raw values with metadata";
270 for (auto [k, m] : rawDataMetadata.metadataMap)
271 {
272 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "\n"
273 << "\t Key: " << k << "\n"
274 << "\t axesName: " << m.axesNames << "\n"
275 << "\t rawValueNames: " << m.rawValueNames
276 ;
277 }
278
279 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Opened remote calibrator and control calibration interfaces successfully";
280
281 _deviceStatus = deviceStatus::CONFIGURED;
282 return true;
283
284}
285
287{
288 // Run the calibration thread
289 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Thread started to run";
290
291 // Use chrono for time-based debug prints
292 static auto lastTimerLog = std::chrono::steady_clock::now();
293 static auto shoutdownTimer = std::chrono::steady_clock::now();
294
295 while(!this->isStopping())
296 {
297 if (_deviceStatus == deviceStatus::CONFIGURED)
298 {
299 auto now = std::chrono::steady_clock::now();
300 if (std::chrono::duration_cast<std::chrono::milliseconds>(now - lastTimerLog).count() > 1000)
301 {
302 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Device configured, waiting for attachAll() to be called.";
303 lastTimerLog = now;
304 }
305 yarp::os::Time::delay(0.1); // Prevent busy-waiting.
306 continue;
307 }
308 else if(_deviceStatus == deviceStatus::STARTED)
309 {
310 // Get number of axes
311 int numAxes = 0;
312 if(!_axesNamesList.isNull() && _axesNamesList.size() > 0 && _axesNamesList.get(0).isList())
313 {
314 numAxes = _axesNamesList.get(0).asList()->size();
315 }
316 // Check if calibration is done by calling IControlCalibration APIs
317 bool calibDone = true;
318 for (size_t i = 0; i < numAxes; i++)
319 {
320 calibDone &= remappedControlBoardInterfaces._icontrolcalib->calibrationDone(i);
321 // TODO: add list of axis that did not perform calibration to be rechecked
322 }
323 if (!calibDone)
324 {
325 yCWarning(FineCalibrationCheckerCOMPONENT) << _deviceName << "Calib not complete for all axis. Waiting for calibration to finish...";
326 yarp::os::Time::delay(0.1); // Prevent busy-waiting.
327 continue;
328 }
329 else
330 {
331 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Calibration done successfully";
332 _deviceStatus = deviceStatus::CALIBRATED;
333 }
334 }
335 else if(_deviceStatus == deviceStatus::CALIBRATED)
336 {
337 // TODO: Check that all joints reached the startup/zero position
338 _deviceStatus = deviceStatus::IN_ZERO_POSITION;
339 }
340 else if(_deviceStatus == deviceStatus::IN_ZERO_POSITION)
341 {
342 if(!remappedRawValuesPublisherInterfaces._iravap->getRawDataMap(rawDataValuesMap))
343 {
344 yCWarning(FineCalibrationCheckerCOMPONENT) << "embObjMotionControl::IRawValuesPublisher warning : raw_data_values map was not read correctly";
345 }
346 else
347 {
348 yCDebug(FineCalibrationCheckerCOMPONENT) << "Get raw values from encoders:";
349 for (auto [key,value] : rawDataValuesMap)
350 {
351 yCDebug(FineCalibrationCheckerCOMPONENT) << "\t key:" << key << "value:" << value;
352 }
353
354 // Here we have to evaluate the delta between the raw golen position
355 // and the raw position read at the hard stop per each axis
356 // TODO: input file not needed anymore. RobeRemoved
357 evaluateHardStopPositionDelta(_rawValuesTag, "zeroPositionsDataDelta.csv");
358 _deviceStatus = deviceStatus::CHECK_COMPLETED;
359 }
360 }
361 else if(_deviceStatus == deviceStatus::CHECK_COMPLETED)
362 {
363 auto now = std::chrono::steady_clock::now();
364 if (std::chrono::duration_cast<std::chrono::milliseconds>(now - shoutdownTimer).count() > 5000)
365 {
366 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Operation completed successfully. Waiting yarprobotinterface to stop the thread...";
367 break; // Exit the loop to stop the thread
368 }
369 }
370 else
371 {
372 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Device is in unknown state. Stopping thread...";
373 break; // Exit the loop to stop the thread
374
375 }
376 }
377
378 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Thread stopping";
379 // Perform any cleanup or finalization tasks here
380 // For example, you can close the device or release resources
381
382}
383
385{
386 //nothing to do for now
387}
388
389bool FineCalibrationChecker::attachAll(const yarp::dev::PolyDriverList& device2attach)
390{
391 // Attach all devices to the FineCalibrationChecker
392 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Attaching all devices";
393
394 // The _imultwrap view should be place before calling the attachToAllControlBoards method
395 // otherwise IMultipleWrapper::attachAll cannot be used
396 if (!_remappedControlBoardDevice->view(remappedControlBoardInterfaces._imultwrap) || remappedControlBoardInterfaces._imultwrap == nullptr)
397 {
398 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open multiple wrapper interface. Aborting...";
399 return false;
400 }
401
402 if (!_remappedControlBoardDevice->isValid())
403 {
404 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Device is not valid. Cannot attach.";
405 return false;
406 }
407
408 if(!_remappedRawValuesPublisherDevice->view(remappedRawValuesPublisherInterfaces._imultwrap) || remappedRawValuesPublisherInterfaces._imultwrap == nullptr)
409 {
410 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open multiple wrapper interface for raw values publisher. Aborting...";
411 return false;
412 }
413
414 if(!_remappedRawValuesPublisherDevice->isValid())
415 {
416 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Raw values publisher device is not valid. Cannot attach.";
417 return false;
418 }
419
420 // Attach the device to the thread
421 if (!this->attachToAllControlBoards(device2attach))
422 {
423 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to attach all devices.";
424 return false;
425 }
426
427 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Successfully attached all devices. Starting the thread...";
428 // Start the thread
429 if (!this->start())
430 {
431 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to start the thread.";
432 return false;
433 }
434 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Thread started successfully";
435 _deviceStatus = deviceStatus::STARTED;
436
437 return true;
438}
439
441{
442 // Stop the thread and detach all devices
443 if (this->isRunning())
444 {
445 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Stopping the thread before detaching all devices";
446 this->stop();
447 }
448 if(!remappedControlBoardInterfaces._imultwrap->detachAll() || !remappedRawValuesPublisherInterfaces._imultwrap->detachAll())
449 {
450 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to detach all devices";
451 return false;
452 }
453 else
454 {
455 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Detached all devices successfully";
456 }
457 return true;
458}
459
460// Private methods
461
463{
464 // Check if the calibration was successful
465 return (_deviceStatus == deviceStatus::CALIBRATED);
466}
467
468bool FineCalibrationChecker::attachToAllControlBoards(const yarp::dev::PolyDriverList& polyList)
469{
470 // Attach all control boards to the FineCalibrationChecker
471 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Attaching all control boards";
472 if (!_remappedControlBoardDevice->isValid())
473 {
474 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Device is not valid. Cannot attach.";
475 return false;
476 }
477
478 // Setup the control board list given the list of PolyDrivers devices we need to attach to.
479 yarp::dev::PolyDriverList controlBoardsList;
480 for (size_t i = 0; i < polyList.size(); i++)
481 {
482 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Attaching control board" << polyList[i]->key;
483 controlBoardsList.push(const_cast<yarp::dev::PolyDriverDescriptor&>(*polyList[i]));
484 }
485
486 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Control boards list size:" << controlBoardsList.size();
487
488 // Attach the list of control boards to the control board remapper
489 if(!remappedControlBoardInterfaces._imultwrap->attachAll(controlBoardsList))
490 {
491 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to attach all control boards";
492 return false;
493 }
494
495 if(!remappedRawValuesPublisherInterfaces._imultwrap->attachAll(controlBoardsList))
496 {
497 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to attach all control boards to raw values publisher";
498 return false;
499 }
500
501 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Successfully attached all control boards";
502
503 return true;
504}
505
506void FineCalibrationChecker::evaluateHardStopPositionDelta(const std::string& key, const std::string& outputFileName)
507{
508
509 // Add comments for magic numbers
510 const int32_t RAW_VALUES_STRIDE = 3; // Number of raw values per joint in the raw data vector
511 const int64_t ICUB_DEGREES_RANGE = (65535); // Range of iCub degrees for rescaling (2^16-1)
512 // Get the directory of the output file
513 std::filesystem::path outputPath(outputFileName);
514 int64_t goldPosition = 0;
515 int64_t rawPosition = 0;
516 int64_t resolution = 0;
517 int64_t rescaledPos = 0;
518 double delta = 0;
519 std::vector<ItemData> sampleItems = {};
520
521 yarp::os::Bottle* caldeltas = _calibrationDeltasList.get(0).asList();
522 yarp::os::Bottle* axesSigns = _axesSignsList.get(0).asList();
523
524 std::ofstream outFile(outputPath);
525 if (!outFile.is_open())
526 {
527 yCError(FineCalibrationCheckerCOMPONENT) << "Unable to open output file:" << outputPath.string();
528 return;
529 }
530 // Add headers to the output CSV file
531 outFile << "AxisName,GoldPosition,RescaledPosition,RawPosition,Delta\n";
532 yCDebug(FineCalibrationCheckerCOMPONENT) << "Evaluating deltas.....";
533 if(auto it = rawDataValuesMap.find(key); it != rawDataValuesMap.end())
534 {
535 std::vector<std::string> axesNames = {};
536 std::vector<std::int32_t> rawData = {};
537
538 // Fill axesNames and rawData vectors
539 axesNames = rawDataMetadata.metadataMap.at(it->first).axesNames;
540 rawData = it->second;
541
542 std::vector<double> homePositions(axesNames.size(), 0);
543 std::vector<double> calibrationDelta(axesNames.size(), 0.0);
544
545
546 for (size_t i = 0; i < axesNames.size(); ++i)
547 {
548 goldPosition = 0;
549 rawPosition = 0;
550 resolution = 0;
551 delta = 0.0;
552 yCDebug(FineCalibrationCheckerCOMPONENT) << "Evaluating axis:" << axesNames[i];
553 if (auto it = axesRawGoldenPositionsResMap.find(axesNames[i]); it != axesRawGoldenPositionsResMap.end())
554 {
555 calibrationDelta[i] = caldeltas->get(i).asFloat64(); // Get the calibration delta for the axis
556 double pos = 0.0;
557 remappedControlBoardInterfaces._ienc->getEncoder(i, &pos); // Update home position by calling the IEncoders API
558 homePositions[i] = (axesSigns->get(i).asInt32() > 0) ? pos : -pos; // Update home position for the axis
559 goldPosition = it->second[0];
560 resolution = it->second[1];
561 rawPosition = rawData[RAW_VALUES_STRIDE*i]; // This because the raw values for tag eoprot_tag_mc_joint_status_addinfo_multienc
562 // are stored in a vector whose legth is joints_number*3, where each sub-array is made such
563 // [raw_val_primary_enc, raw_val_secondary_enc, rraw_val_auxiliary_enc]
564 // and we want the first value for each joint
565 rescaledPos = rawPosition * ICUB_DEGREES_RANGE / resolution; // Rescale the encoder raw position to iCubDegrees
566 // Gold position and rescaled position are in iCubDegrees --> thus between 0 and 65535
567 // Home position is in degrees --> thus between -180 and 180
568 // Delta is in degrees
569 // The issue here is that the home position can be negative or positive, while the gold and rescaled positions are always positive
570 // and when we calculate their difference we are not sure if we are aligned with the home position sign convention
571 // Thus we calculate the delta as the the difference between gold and rescaled positions
572 // divided by the iCub degrees range and multiplied by 360 to convert it to degrees
573 // and then we add the home position to it
574 // Therefore, in order to have the correct sign for the delta, aligned with the standard we use for the joints kinematics
575 // we need to apply the sign of the axis to the home position (line 558) and to the calibration delta (line 577)
576 // the sign is taken from the axesSigns list provided in the configuration file, defined following the primary encoder sign
577 delta = static_cast<double>((goldPosition - rescaledPos) / (ICUB_DEGREES_RANGE/360.0)) + homePositions[i]; // Calculate the delta in degrees
578 calibrationDelta[i] = (axesSigns->get(i).asInt32() > 0) ? calibrationDelta[i] : -calibrationDelta[i]; // Apply the sign to the calibration delta
579 delta += calibrationDelta[i]; // Add the calibration delta to the delta
580 yCDebug(FineCalibrationCheckerCOMPONENT) << "GP:" << goldPosition << "HP:" << homePositions[i] << "RSP:" << rescaledPos << "RWP:" << rawPosition << "DD:" << delta;
581 }
582 else
583 {
584 yCWarning(FineCalibrationCheckerCOMPONENT) << "This device axes has not ben requested to be checked. Continue...";
585 continue;
586 }
587
588 // Write to output CSV file
589 outFile << axesNames[i] << "," << goldPosition << "," << rescaledPos << "," << rawPosition << "," << delta << "\n";
590 sampleItems.push_back({axesNames[i], goldPosition, rescaledPos, rawPosition, delta});
591 }
592 }
593 else
594 {
595 yCError(FineCalibrationCheckerCOMPONENT) << "Key" << key << "not found in rawDataValuesMap";
596 outFile << "Key not found in rawDataValuesMap\n";
597 }
598
599 outFile.close();
600 yCDebug(FineCalibrationCheckerCOMPONENT) << "Output CSV written to:" << outputPath.string();
601 // Generate output image
602 if(_withGui) {
603 generateOutputImage(1800, 400, sampleItems);
604 }
605}
606
607void FineCalibrationChecker::generateOutputImage(int frameWidth, int frameHeight, const std::vector<ItemData>& items)
608{
609 cv::Mat image = cv::Mat::zeros(frameHeight, frameWidth, CV_8UC3);
610 image.setTo(cv::Scalar(255, 255, 255)); // White background
611
612 int lineHeight = 30;
613 int padding = 10;
614 int textOffset = 20;
615 int colWidthAvg = (frameWidth - 2 * padding) / 5; // Average width for each column
616
617 // Define headers and column widths (in pixels)
618 std::vector<std::string> headers = {"AxisName", "GoldPosition[iCubDegrees]", "RescaledPosition[iCubDegrees]", "RawPosition[Ticks]", "Delta[Degrees]"};
619 std::vector<int> colWidths(5, colWidthAvg); // Creates a vector of size 5, all values set to colWidthAvg
620 std::vector<int> colX(headers.size());
621 colX[0] = padding + 5;
622 for (size_t i = 1; i < headers.size(); ++i) {
623 colX[i] = colX[i-1] + colWidths[i-1];
624 }
625
626 // Draw header row
627 int headerY = padding;
628 cv::Scalar headerBgColor(220, 220, 220); // Light gray background
629 // Draw the filled background for the header row (light gray)
630 cv::rectangle(image,
631 cv::Point(padding, headerY),
632 cv::Point(frameWidth - padding, headerY + lineHeight - 5),
633 headerBgColor, cv::FILLED);
634 // Draw a border around the header row (darker gray)
635 cv::rectangle(image,
636 cv::Point(padding, headerY),
637 cv::Point(frameWidth - padding, headerY + lineHeight - 5),
638 cv::Scalar(100, 100, 100), 1);
639 for (size_t c = 0; c < headers.size(); ++c) {
640 cv::putText(image, headers[c], cv::Point(colX[c], headerY + textOffset),
641 cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 0), 2);
642 }
643
644 // Draw data rows
645 for (size_t i = 0; i < items.size(); ++i)
646 {
647 int y = padding + (i + 1) * lineHeight; // +1 to account for header
648 // Draw background rectangle for the row
649 cv::Scalar bgColor = getColorForDelta(items[i].val4, 1.0, 3.0); // Green, Orange, Red based on delta thresholds
650 cv::rectangle(image,
651 cv::Point(padding, y),
652 cv::Point(frameWidth - padding, y + lineHeight - 5),
653 bgColor, cv::FILLED);
654
655 // Draw border for the row
656 cv::rectangle(image,
657 cv::Point(padding, y),
658 cv::Point(frameWidth - padding, y + lineHeight - 5),
659 cv::Scalar(200, 200, 200), 1);
660
661 // Draw each column value aligned with header
662 std::vector<std::string> rowVals = {
663 items[i].name,
664 std::to_string(items[i].val1),
665 std::to_string(items[i].val2),
666 std::to_string(items[i].val3),
667 std::to_string(items[i].val4)
668 };
669 for (size_t c = 0; c < rowVals.size(); ++c) {
670 cv::putText(image, rowVals[c], cv::Point(colX[c], y + textOffset),
671 cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 0), 2);
672 }
673 }
674
675 cv::imwrite("output_frame.png", image);
676 //TODO: remove openCV highgui functions since they are not thread safe and showld be called only in the main thread
677 cv::imshow("Output Frame", image);
678 cv::waitKey(0);
679 cv::destroyAllWindows();
680}
681
682cv::Scalar FineCalibrationChecker::getColorForDelta(double delta, double threshold_1, double threshold_2)
683{
684 if (std::abs(delta) > threshold_2) return cv::Scalar(0, 0, 255); // Red
685 else if (std::abs(delta) > threshold_1) return cv::Scalar(0, 165, 255); // Orange
686 else return cv::Scalar(0, 255, 0); // Green
687}
688
bool open(yarp::os::Searchable &config) override
bool attachAll(const yarp::dev::PolyDriverList &device2attach) override
std::map< std::string, rawValuesKeyMetadata > metadataMap
FILE * outFile
Copyright (C) 2008 RobotCub Consortium.