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 yCError(FineCalibrationCheckerCOMPONENT)
203 << _deviceName
204 << "Unable to open raw values device driver. Aborting...";
205 return false;
206 }
207
208 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Opened all devices successfully";
209
210 _deviceStatus = deviceStatus::OPENED;
211 return true;
212}
213
215{
216 // Close the device driver and all the opened ports
217 if(_remappedControlBoardDevice->close())
218 {
219 yCDebug(FineCalibrationCheckerCOMPONENT) << "Closed device" << _deviceName;
220 }
221 else
222 {
223 yCError(FineCalibrationCheckerCOMPONENT) << "Unable to close device" << _deviceName;
224 }
225
226 if (_remappedRawValuesPublisherDevice->close())
227 {
228 yCDebug(FineCalibrationCheckerCOMPONENT) << "Closed raw values publisher device";
229 }
230 else
231 {
232 yCError(FineCalibrationCheckerCOMPONENT) << "Unable to close raw values publisher device";
233 }
234 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Closed all devices successfully";
235
236 return true;
237}
238
240{
241 // Initialize the thread --> can add the view on the interfaces here
242 if (!_remappedControlBoardDevice->view(remappedControlBoardInterfaces._imot) || remappedControlBoardInterfaces._imot == nullptr)
243 {
244 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open motor interface. Aborting...";
245 return false;
246 }
247
248 if (!_remappedControlBoardDevice->view(remappedControlBoardInterfaces._ienc) || remappedControlBoardInterfaces._ienc == nullptr)
249 {
250 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open encoders interface. Aborting...";
251 return false;
252 }
253
254 if (!_remappedControlBoardDevice->view(remappedControlBoardInterfaces._icontrolcalib) || remappedControlBoardInterfaces._icontrolcalib == nullptr)
255 {
256 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open control calibration interface. Aborting...";
257 return false;
258 }
259
260 if (!_remappedRawValuesPublisherDevice->view(remappedRawValuesPublisherInterfaces._iravap) || remappedRawValuesPublisherInterfaces._iravap == nullptr)
261 {
262 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open raw values publisher interface. Aborting...";
263 return false;
264 }
265
266
267 // Configuring raw values metadata
268 rawDataMetadata = {};
269 remappedRawValuesPublisherInterfaces._iravap->getMetadataMap(rawDataMetadata);
270 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Configured raw values with metadata";
271 for (auto [k, m] : rawDataMetadata.metadataMap)
272 {
273 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "\n"
274 << "\t Key: " << k << "\n"
275 << "\t axesName: " << m.axesNames << "\n"
276 << "\t rawValueNames: " << m.rawValueNames
277 ;
278 }
279
280 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Opened remote calibrator and control calibration interfaces successfully";
281
282 _deviceStatus = deviceStatus::CONFIGURED;
283 return true;
284
285}
286
288{
289 // Run the calibration thread
290 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Thread started to run";
291
292 // Use chrono for time-based debug prints
293 static auto lastTimerLog = std::chrono::steady_clock::now();
294 static auto shoutdownTimer = std::chrono::steady_clock::now();
295
296 while(!this->isStopping())
297 {
298 if (_deviceStatus == deviceStatus::CONFIGURED)
299 {
300 auto now = std::chrono::steady_clock::now();
301 if (std::chrono::duration_cast<std::chrono::milliseconds>(now - lastTimerLog).count() > 1000)
302 {
303 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Device configured, waiting for attachAll() to be called.";
304 lastTimerLog = now;
305 }
306 yarp::os::Time::delay(0.1); // Prevent busy-waiting.
307 continue;
308 }
309 else if(_deviceStatus == deviceStatus::STARTED)
310 {
311 // Get number of axes
312 int numAxes = 0;
313 if(!_axesNamesList.isNull() && _axesNamesList.size() > 0 && _axesNamesList.get(0).isList())
314 {
315 numAxes = _axesNamesList.get(0).asList()->size();
316 }
317 // Check if calibration is done by calling IControlCalibration APIs
318 bool calibDone = true;
319 for (size_t i = 0; i < numAxes; i++)
320 {
321 calibDone &= remappedControlBoardInterfaces._icontrolcalib->calibrationDone(i);
322 // TODO: add list of axis that did not perform calibration to be rechecked
323 }
324 if (!calibDone)
325 {
326 yCWarning(FineCalibrationCheckerCOMPONENT) << _deviceName << "Calib not complete for all axis. Waiting for calibration to finish...";
327 yarp::os::Time::delay(0.1); // Prevent busy-waiting.
328 continue;
329 }
330 else
331 {
332 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Calibration done successfully";
333 _deviceStatus = deviceStatus::CALIBRATED;
334 }
335 }
336 else if(_deviceStatus == deviceStatus::CALIBRATED)
337 {
338 // TODO: Check that all joints reached the startup/zero position
339 _deviceStatus = deviceStatus::IN_ZERO_POSITION;
340 }
341 else if(_deviceStatus == deviceStatus::IN_ZERO_POSITION)
342 {
343 if(!remappedRawValuesPublisherInterfaces._iravap->getRawDataMap(rawDataValuesMap))
344 {
345 yCWarning(FineCalibrationCheckerCOMPONENT) << "embObjMotionControl::IRawValuesPublisher warning : raw_data_values map was not read correctly";
346 }
347 else
348 {
349 yCDebug(FineCalibrationCheckerCOMPONENT) << "Get raw values from encoders:";
350 for (auto [key,value] : rawDataValuesMap)
351 {
352 yCDebug(FineCalibrationCheckerCOMPONENT) << "\t key:" << key << "value:" << value;
353 }
354
355 // Here we have to evaluate the delta between the raw golen position
356 // and the raw position read at the hard stop per each axis
357 // TODO: input file not needed anymore. RobeRemoved
358 evaluateHardStopPositionDelta(_rawValuesTag, "zeroPositionsDataDelta.csv");
359 _deviceStatus = deviceStatus::CHECK_COMPLETED;
360 }
361 }
362 else if(_deviceStatus == deviceStatus::CHECK_COMPLETED)
363 {
364 auto now = std::chrono::steady_clock::now();
365 if (std::chrono::duration_cast<std::chrono::milliseconds>(now - shoutdownTimer).count() > 5000)
366 {
367 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Operation completed successfully. Waiting yarprobotinterface to stop the thread...";
368 break; // Exit the loop to stop the thread
369 }
370 }
371 else
372 {
373 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Device is in unknown state. Stopping thread...";
374 break; // Exit the loop to stop the thread
375
376 }
377 }
378
379 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Thread stopping";
380 // Perform any cleanup or finalization tasks here
381 // For example, you can close the device or release resources
382
383}
384
386{
387 //nothing to do for now
388}
389
390bool FineCalibrationChecker::attachAll(const yarp::dev::PolyDriverList& device2attach)
391{
392 // Attach all devices to the FineCalibrationChecker
393 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Attaching all devices";
394
395 // The _imultwrap view should be place before calling the attachToAllControlBoards method
396 // otherwise IMultipleWrapper::attachAll cannot be used
397 if (!_remappedControlBoardDevice->view(remappedControlBoardInterfaces._imultwrap) || remappedControlBoardInterfaces._imultwrap == nullptr)
398 {
399 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open multiple wrapper interface. Aborting...";
400 return false;
401 }
402
403 if (!_remappedControlBoardDevice->isValid())
404 {
405 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Device is not valid. Cannot attach.";
406 return false;
407 }
408
409 if(!_remappedRawValuesPublisherDevice->view(remappedRawValuesPublisherInterfaces._imultwrap) || remappedRawValuesPublisherInterfaces._imultwrap == nullptr)
410 {
411 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Unable to open multiple wrapper interface for raw values publisher. Aborting...";
412 return false;
413 }
414
415 if(!_remappedRawValuesPublisherDevice->isValid())
416 {
417 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Raw values publisher device is not valid. Cannot attach.";
418 return false;
419 }
420
421 // Attach the device to the thread
422 if (!this->attachToAllControlBoards(device2attach))
423 {
424 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to attach all devices.";
425 return false;
426 }
427
428 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Successfully attached all devices. Starting the thread...";
429 // Start the thread
430 if (!this->start())
431 {
432 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to start the thread.";
433 return false;
434 }
435 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Thread started successfully";
436 _deviceStatus = deviceStatus::STARTED;
437
438 return true;
439}
440
442{
443 // Stop the thread and detach all devices
444 if (this->isRunning())
445 {
446 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Stopping the thread before detaching all devices";
447 this->stop();
448 }
449 if(!remappedControlBoardInterfaces._imultwrap->detachAll() || !remappedRawValuesPublisherInterfaces._imultwrap->detachAll())
450 {
451 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to detach all devices";
452 return false;
453 }
454 else
455 {
456 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Detached all devices successfully";
457 }
458 return true;
459}
460
461// Private methods
462
464{
465 // Check if the calibration was successful
466 return (_deviceStatus == deviceStatus::CALIBRATED);
467}
468
469bool FineCalibrationChecker::attachToAllControlBoards(const yarp::dev::PolyDriverList& polyList)
470{
471 // Attach all control boards to the FineCalibrationChecker
472 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Attaching all control boards";
473 if (!_remappedControlBoardDevice->isValid())
474 {
475 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Device is not valid. Cannot attach.";
476 return false;
477 }
478
479 // Setup the control board list given the list of PolyDrivers devices we need to attach to.
480 yarp::dev::PolyDriverList controlBoardsList;
481 for (size_t i = 0; i < polyList.size(); i++)
482 {
483 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Attaching control board" << polyList[i]->key;
484 controlBoardsList.push(const_cast<yarp::dev::PolyDriverDescriptor&>(*polyList[i]));
485 }
486
487 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Control boards list size:" << controlBoardsList.size();
488
489 // Attach the list of control boards to the control board remapper
490 if(!remappedControlBoardInterfaces._imultwrap->attachAll(controlBoardsList))
491 {
492 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to attach all control boards";
493 return false;
494 }
495
496 if(!remappedRawValuesPublisherInterfaces._imultwrap->attachAll(controlBoardsList))
497 {
498 yCError(FineCalibrationCheckerCOMPONENT) << _deviceName << "Failed to attach all control boards to raw values publisher";
499 return false;
500 }
501
502 yCDebug(FineCalibrationCheckerCOMPONENT) << _deviceName << "Successfully attached all control boards";
503
504 return true;
505}
506
507void FineCalibrationChecker::evaluateHardStopPositionDelta(const std::string& key, const std::string& outputFileName)
508{
509
510 // Add comments for magic numbers
511 const int32_t RAW_VALUES_STRIDE = 3; // Number of raw values per joint in the raw data vector
512 const int64_t ICUB_DEGREES_RANGE = (65535); // Range of iCub degrees for rescaling (2^16-1)
513 // Get the directory of the output file
514 std::filesystem::path outputPath(outputFileName);
515 int64_t goldPosition = 0;
516 int64_t rawPosition = 0;
517 int64_t resolution = 0;
518 int64_t rescaledPos = 0;
519 double delta = 0;
520 std::vector<ItemData> sampleItems = {};
521
522 yarp::os::Bottle* caldeltas = _calibrationDeltasList.get(0).asList();
523 yarp::os::Bottle* axesSigns = _axesSignsList.get(0).asList();
524
525 std::ofstream outFile(outputPath);
526 if (!outFile.is_open())
527 {
528 yCError(FineCalibrationCheckerCOMPONENT) << "Unable to open output file:" << outputPath.string();
529 return;
530 }
531 // Add headers to the output CSV file
532 outFile << "AxisName,GoldPosition,RescaledPosition,RawPosition,Delta\n";
533 yCDebug(FineCalibrationCheckerCOMPONENT) << "Evaluating deltas.....";
534 if(auto it = rawDataValuesMap.find(key); it != rawDataValuesMap.end())
535 {
536 std::vector<std::string> axesNames = {};
537 std::vector<std::int32_t> rawData = {};
538
539 // Fill axesNames and rawData vectors
540 axesNames = rawDataMetadata.metadataMap.at(it->first).axesNames;
541 rawData = it->second;
542
543 std::vector<double> homePositions(axesNames.size(), 0);
544 std::vector<double> calibrationDelta(axesNames.size(), 0.0);
545
546
547 for (size_t i = 0; i < axesNames.size(); ++i)
548 {
549 goldPosition = 0;
550 rawPosition = 0;
551 resolution = 0;
552 delta = 0.0;
553 yCDebug(FineCalibrationCheckerCOMPONENT) << "Evaluating axis:" << axesNames[i];
554 if (auto it = axesRawGoldenPositionsResMap.find(axesNames[i]); it != axesRawGoldenPositionsResMap.end())
555 {
556 calibrationDelta[i] = caldeltas->get(i).asFloat64(); // Get the calibration delta for the axis
557 double pos = 0.0;
558 remappedControlBoardInterfaces._ienc->getEncoder(i, &pos); // Update home position by calling the IEncoders API
559 homePositions[i] = (axesSigns->get(i).asInt32() > 0) ? pos : -pos; // Update home position for the axis
560 goldPosition = it->second[0];
561 resolution = it->second[1];
562 rawPosition = rawData[RAW_VALUES_STRIDE*i]; // This because the raw values for tag eoprot_tag_mc_joint_status_addinfo_multienc
563 // are stored in a vector whose legth is joints_number*3, where each sub-array is made such
564 // [raw_val_primary_enc, raw_val_secondary_enc, rraw_val_auxiliary_enc]
565 // and we want the first value for each joint
566 rescaledPos = rawPosition * ICUB_DEGREES_RANGE / resolution; // Rescale the encoder raw position to iCubDegrees
567 // Gold position and rescaled position are in iCubDegrees --> thus between 0 and 65535
568 // Home position is in degrees --> thus between -180 and 180
569 // Delta is in degrees
570 // The issue here is that the home position can be negative or positive, while the gold and rescaled positions are always positive
571 // and when we calculate their difference we are not sure if we are aligned with the home position sign convention
572 // Thus we calculate the delta as the the difference between gold and rescaled positions
573 // divided by the iCub degrees range and multiplied by 360 to convert it to degrees
574 // and then we add the home position to it
575 // Therefore, in order to have the correct sign for the delta, aligned with the standard we use for the joints kinematics
576 // we need to apply the sign of the axis to the home position (line 558) and to the calibration delta (line 577)
577 // the sign is taken from the axesSigns list provided in the configuration file, defined following the primary encoder sign
578 delta = static_cast<double>((goldPosition - rescaledPos) / (ICUB_DEGREES_RANGE/360.0)) + homePositions[i]; // Calculate the delta in degrees
579 calibrationDelta[i] = (axesSigns->get(i).asInt32() > 0) ? calibrationDelta[i] : -calibrationDelta[i]; // Apply the sign to the calibration delta
580 delta += calibrationDelta[i]; // Add the calibration delta to the delta
581 yCDebug(FineCalibrationCheckerCOMPONENT) << "GP:" << goldPosition << "HP:" << homePositions[i] << "RSP:" << rescaledPos << "RWP:" << rawPosition << "DD:" << delta;
582 }
583 else
584 {
585 yCWarning(FineCalibrationCheckerCOMPONENT) << "This device axes has not ben requested to be checked. Continue...";
586 continue;
587 }
588
589 // Write to output CSV file
590 outFile << axesNames[i] << "," << goldPosition << "," << rescaledPos << "," << rawPosition << "," << delta << "\n";
591 sampleItems.push_back({axesNames[i], goldPosition, rescaledPos, rawPosition, delta});
592 }
593 }
594 else
595 {
596 yCError(FineCalibrationCheckerCOMPONENT) << "Key" << key << "not found in rawDataValuesMap";
597 outFile << "Key not found in rawDataValuesMap\n";
598 }
599
600 outFile.close();
601 yCDebug(FineCalibrationCheckerCOMPONENT) << "Output CSV written to:" << outputPath.string();
602 // Generate output image
603 if(_withGui) {
604 generateOutputImage(1800, 400, sampleItems);
605 }
606}
607
608void FineCalibrationChecker::generateOutputImage(int frameWidth, int frameHeight, const std::vector<ItemData>& items)
609{
610 cv::Mat image = cv::Mat::zeros(frameHeight, frameWidth, CV_8UC3);
611 image.setTo(cv::Scalar(255, 255, 255)); // White background
612
613 int lineHeight = 30;
614 int padding = 10;
615 int textOffset = 20;
616 int colWidthAvg = (frameWidth - 2 * padding) / 5; // Average width for each column
617
618 // Define headers and column widths (in pixels)
619 std::vector<std::string> headers = {"AxisName", "GoldPosition[iCubDegrees]", "RescaledPosition[iCubDegrees]", "RawPosition[Ticks]", "Delta[Degrees]"};
620 std::vector<int> colWidths(5, colWidthAvg); // Creates a vector of size 5, all values set to colWidthAvg
621 std::vector<int> colX(headers.size());
622 colX[0] = padding + 5;
623 for (size_t i = 1; i < headers.size(); ++i) {
624 colX[i] = colX[i-1] + colWidths[i-1];
625 }
626
627 // Draw header row
628 int headerY = padding;
629 cv::Scalar headerBgColor(220, 220, 220); // Light gray background
630 // Draw the filled background for the header row (light gray)
631 cv::rectangle(image,
632 cv::Point(padding, headerY),
633 cv::Point(frameWidth - padding, headerY + lineHeight - 5),
634 headerBgColor, cv::FILLED);
635 // Draw a border around the header row (darker gray)
636 cv::rectangle(image,
637 cv::Point(padding, headerY),
638 cv::Point(frameWidth - padding, headerY + lineHeight - 5),
639 cv::Scalar(100, 100, 100), 1);
640 for (size_t c = 0; c < headers.size(); ++c) {
641 cv::putText(image, headers[c], cv::Point(colX[c], headerY + textOffset),
642 cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 0), 2);
643 }
644
645 // Draw data rows
646 for (size_t i = 0; i < items.size(); ++i)
647 {
648 int y = padding + (i + 1) * lineHeight; // +1 to account for header
649 // Draw background rectangle for the row
650 cv::Scalar bgColor = getColorForDelta(items[i].val4, 1.0, 3.0); // Green, Orange, Red based on delta thresholds
651 cv::rectangle(image,
652 cv::Point(padding, y),
653 cv::Point(frameWidth - padding, y + lineHeight - 5),
654 bgColor, cv::FILLED);
655
656 // Draw border for the row
657 cv::rectangle(image,
658 cv::Point(padding, y),
659 cv::Point(frameWidth - padding, y + lineHeight - 5),
660 cv::Scalar(200, 200, 200), 1);
661
662 // Draw each column value aligned with header
663 std::vector<std::string> rowVals = {
664 items[i].name,
665 std::to_string(items[i].val1),
666 std::to_string(items[i].val2),
667 std::to_string(items[i].val3),
668 std::to_string(items[i].val4)
669 };
670 for (size_t c = 0; c < rowVals.size(); ++c) {
671 cv::putText(image, rowVals[c], cv::Point(colX[c], y + textOffset),
672 cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 0), 2);
673 }
674 }
675
676 cv::imwrite("output_frame.png", image);
677 //TODO: remove openCV highgui functions since they are not thread safe and showld be called only in the main thread
678 cv::imshow("Output Frame", image);
679 cv::waitKey(0);
680 cv::destroyAllWindows();
681}
682
683cv::Scalar FineCalibrationChecker::getColorForDelta(double delta, double threshold_1, double threshold_2)
684{
685 if (std::abs(delta) > threshold_2) return cv::Scalar(0, 0, 255); // Red
686 else if (std::abs(delta) > threshold_1) return cv::Scalar(0, 165, 255); // Orange
687 else return cv::Scalar(0, 255, 0); // Green
688}
689
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.