32 #include <VirtualRobot/Nodes/Sensor.h>
34 #include <Eigen/Geometry>
41 namespace fs = std::filesystem;
46 std::vector<float>
data;
47 if (_logRepeatAccuracy)
49 data.push_back(-1.0f);
50 data.push_back(-1.0f);
55 CsvWriter writer(_filePath, std::vector<std::string>(),
true);
65 if (!_logRepeatAccuracy)
71 std::vector<float>
data;
76 std::pair<float, float> repeatErrors;
77 repeatErrors = calculateRepeatError();
78 data[0] = repeatErrors.first;
79 data[1] = repeatErrors.second;
83 CsvWriter writer(_filePath, std::vector<std::string>(),
true);
88 ARMARX_DEBUG <<
"Logging data with repeat accuracy finished";
93 _viconLoggingActive =
true;
98 _viconLoggingActive =
false;
102 const std::map<std::string, std::string>& neckMarkerMapping,
103 const std::map<std::string, std::string>& handMarkerMapping,
104 const std::string& loggingFileName,
105 bool logRepeatAccuracy,
112 _neckMarkerMapping = neckMarkerMapping;
113 _handMarkerMapping = handMarkerMapping;
118 for (
auto& pair : neckMarkerMapping)
120 _neckMarker.push_back(pair.first);
122 for (
auto& pair : handMarkerMapping)
124 _handMarker.push_back(pair.first);
126 for (
auto&
s : _neckMarker)
128 if (std::find(_handMarker.begin(), _handMarker.end(),
s) != _handMarker.end())
130 ARMARX_ERROR <<
"HandMarker and NeckMarker can not contain the same marker!! Abort.";
134 std::vector<VirtualRobot::SensorPtr> sensors = localRobot->getSensors();
135 for (
auto& pair : neckMarkerMapping)
137 auto& i = pair.second;
140 return s->getName() == i;
141 }) != sensors.end()) <<
142 "Robot does not have a sensor with name '" + i +
"'";
144 for (
auto& pair : handMarkerMapping)
146 auto& i = pair.second;
149 return s->getName() == i;
150 }) != sensors.end()) <<
151 "Robot does not have a sensor with name '" + i +
"'";
154 _kcName = kinematicChainName;
155 ARMARX_CHECK_EXPRESSION(localRobot->hasRobotNodeSet(_kcName)) <<
"Robot " + localRobot->getName() +
" does not have a kinematic chain with name " + _kcName;
156 _kc = localRobot->getRobotNodeSet(_kcName);
157 _jointNames = _kc->getNodeNames();
158 _logRepeatAccuracy = logRepeatAccuracy;
160 setupFile(loggingFileName);
163 CsvWriter writer(_filePath, _header,
true);
173 StringVector3fMap copyMap;
174 for (
auto& it : markerPosMap)
176 std::string key = it.first;
177 int i = key.find(
':') + 1;
178 copyMap[key.erase(0, i)] = it.second;
179 Vector3f
v = it.second;
181 debugObserverPrx->setDebugDatafield(
"DHParameterOptimization", it.first +
"_x",
new Variant(
v.e0));
182 debugObserverPrx->setDebugDatafield(
"DHParameterOptimization", it.first +
"_y",
new Variant(
v.e1));
183 debugObserverPrx->setDebugDatafield(
"DHParameterOptimization", it.first +
"_z",
new Variant(
v.e2));
186 if (_viconLoggingActive)
188 std::unique_lock lock(_bufferMutex);
189 _viconMarkerBuffer = copyMap;
191 _viconBufferUpdated =
true;
201 void DHParameterOptimizationLogger::setupFile(
const std::string& fileName)
203 std::string localFileName = fileName;
204 using namespace std::filesystem;
206 auto timeToString = []()
208 std::time_t now = std::time(
nullptr);
209 std::tm* nowTm = std::localtime(&now);
211 std::stringstream ss;
212 ss << std::put_time(nowTm,
"%Y%m%d_%H%M%S");
219 localFileName = localFileName.substr(0, localFileName.size() - 4);
221 localFileName +=
"_";
222 localFileName += timeToString();
223 localFileName +=
".csv";
225 std::string pathOrig = getProperty<std::string>(
"LoggingFilePath").getValue();
226 if (!pathOrig.empty() && pathOrig[0] ==
'~')
228 ARMARX_CHECK_EXPRESSION(pathOrig.size() == 1 || pathOrig[1] ==
'/') <<
"LoggingFilePath isviconValues not valid: '~' has to be followed by ’/’";
229 char const* home = std::getenv(
"HOME");
231 pathOrig.replace(0, 1, home);
233 fs::path filePath(pathOrig);
234 if (!fs::exists(filePath))
236 fs::create_directory(filePath);
238 filePath /= path(localFileName);
239 _filePath = filePath.string();
244 usingTopic(getProperty<std::string>(
"ViconDataTopicName").getValue());
245 usingProxy(getProperty<std::string>(
"RobotStateComponentName").getValue());
246 usingProxy(getProperty<std::string>(
"RobotUnitObserverName").getValue());
248 _initialized =
false;
250 int size = getProperty<int>(
"FilterWindowSize").getValue();
254 _viconObjectName_neck = getProperty<std::string>(
"Neck_ViconObjectName").getValue();
255 _viconObjectName_hand = getProperty<std::string>(
"Hand_ViconObjectName").getValue();
261 getProxy(robotStateComponent, getProperty<std::string>(
"RobotStateComponentName").getValue());
262 getProxy(robotUnitObserver, getProperty<std::string>(
"RobotUnitObserverName").getValue());
263 _viconLoggingActive =
true;
265 debugObserverPrx = getTopic<DebugObserverInterfacePrx>(getProperty<std::string>(
"DebugObserverTopicName").getValue());
266 debugDrawerPrx = getTopic<DebugDrawerInterfacePrx>(
"DebugDrawerUpdates");
271 std::vector<VirtualRobot::SensorPtr>
s = localRobot->getSensors();
281 _viconLoggingActive =
false;
296 void DHParameterOptimizationLogger::setupHeader()
298 std::vector<std::string> header;
303 if (_logRepeatAccuracy)
305 header.push_back(
"Error_Translation");
306 header.push_back(
"Error_Orientation");
310 for (
auto& m : _neckMarker)
312 header.push_back(m +
"_x");
313 header.push_back(m +
"_y");
314 header.push_back(m +
"_z");
318 for (
auto& m : _handMarker)
320 header.push_back(m +
"_x");
321 header.push_back(m +
"_y");
322 header.push_back(m +
"_z");
326 for (
auto& node : _jointNames)
328 header.push_back(
"JointValue_" + node);
330 header.push_back(
"JointValue_TorsoJoint");
333 for (
auto& m : _handMarker)
335 header.push_back(
"FK_" + m +
"_x");
336 header.push_back(
"FK_" + m +
"_y");
337 header.push_back(
"FK_" + m +
"_z");
341 header.push_back(
"Error_translation");
342 header.push_back(
"Error_orientation");
345 for (
auto& node : _jointNames)
347 header.push_back(node +
"_torqueTicks");
351 for (
auto& node : _jointNames)
353 header.push_back(node +
"_torque");
357 if (_kc->getTCP()->getName() ==
"Hand R TCP")
359 header.push_back(
"FT R_FT_torque_x");
360 header.push_back(
"FT R_FT_torque_y");
361 header.push_back(
"FT R_FT_torque_z");
363 else if (_kc->getTCP()->getName() ==
"Hand L TCP")
365 header.push_back(
"FT L_FT_torque_x");
366 header.push_back(
"FT L_FT_torque_y");
367 header.push_back(
"FT L_FT_torque_z");
376 logViconMarkerPositions(
data);
377 logJointValues(
data);
378 logForwardKinematicToViconMarker(
data);
379 logErrorBetweenModelAndVicon(
data);
380 logRawTorqueTicks(
data);
381 logTorqueValues(
data);
382 logFTSensorValues(
data);
385 void DHParameterOptimizationLogger::logViconMarkerPositions(std::vector<float>&
data)
389 if (!_viconLoggingActive)
392 size_t sum = 3 * (_neckMarker.size() + _handMarker.size());
393 for (
size_t i = 0; i < sum; i++)
395 data.push_back(0.0f);
401 StringVector3fMap
values = waitBlockingForAllMarkersFiltered();
404 size_t sum = 3 * (_neckMarker.size() + _handMarker.size());
405 for (
size_t i = 0; i < sum; i++)
407 data.push_back(0.0f);
412 values = transformViconMarkerPositionsIntoRobotRootFrame(
values);
413 for (
auto& m : _neckMarker)
419 for (
auto& m : _handMarker)
426 _filteredViconMarkerPositions_robotRootFrame =
values;
427 _filteredViconMarkerPositionsUpdated =
true;
432 void DHParameterOptimizationLogger::logJointValues(std::vector<float>&
data)
const
437 for (
auto& node : _kc->getAllRobotNodes())
439 data.push_back(node->getJointValue());
441 data.push_back(localRobot->getRobotNode(
"TorsoJoint")->getJointValue());
444 void DHParameterOptimizationLogger::logForwardKinematicToViconMarker(std::vector<float>&
data)
const
446 ARMARX_DEBUG <<
"Logging forwardKinematicToViconMarker";
449 VirtualRobot::RobotNodePtr
hand = getHandMarkerRootNode();
451 for (VirtualRobot::SensorPtr
const& sensor :
hand->getSensors())
453 Eigen::Matrix4f m = sensor->getTransformationFrom(localRobot->getRootNode());
454 data.push_back(m(0, 3));
455 data.push_back(m(1, 3));
456 data.push_back(m(2, 3));
460 void DHParameterOptimizationLogger::logErrorBetweenModelAndVicon(std::vector<float>&
data)
464 if (!_viconLoggingActive || !_filteredViconMarkerPositionsUpdated)
466 data.push_back(0.0f);
467 data.push_back(0.0f);
472 VirtualRobot::RobotNodePtr neck = getNeckMarkerRootNode();
473 VirtualRobot::RobotNodePtr
hand = getHandMarkerRootNode();
474 VirtualRobot::RobotNodePtr root = localRobot->getRootNode();
476 _lastObservedHandRootPose = _newObservedHandRootPose;
481 Eigen::MatrixXf observedNeckPositions_robotRootFrame(3, _neckMarker.size());
482 Eigen::MatrixXf modelNeckPositions_neckFrame(3, _neckMarker.size());
489 for (
auto& it : _neckMarker)
491 observedNeckPositions_robotRootFrame(0, i) = _filteredViconMarkerPositions_robotRootFrame.at(it).e0;
492 observedNeckPositions_robotRootFrame(1, i) = _filteredViconMarkerPositions_robotRootFrame.at(it).e1;
493 observedNeckPositions_robotRootFrame(2, i) = _filteredViconMarkerPositions_robotRootFrame.at(it).e2;
495 modelNeckPositions_neckFrame(0, i) = neck->getSensor(_neckMarkerMapping.at(it))->getTransformationFrom(neck)(0, 3);
496 modelNeckPositions_neckFrame(1, i) = neck->getSensor(_neckMarkerMapping.at(it))->getTransformationFrom(neck)(1, 3);
497 modelNeckPositions_neckFrame(2, i) = neck->getSensor(_neckMarkerMapping.at(it))->getTransformationFrom(neck)(2, 3);
500 Eigen::Vector3f position = (root->getGlobalPose() * observedNeckPositions_robotRootFrame).block<3, 1>(0, i);
501 debugDrawerPrx->setSphereVisu(
"markerVisu", it,
new Vector3(position), DrawColor {0, 1, 1, 1}, 7);
509 Eigen::MatrixXf observedHandPositions_robotRootFrame(3, _handMarker.size());
510 Eigen::MatrixXf modelHandPositions_handFrame(3, _handMarker.size());
514 for (
auto& it : _handMarker)
516 observedHandPositions_robotRootFrame(0, i) = _filteredViconMarkerPositions_robotRootFrame.at(it).e0;
517 observedHandPositions_robotRootFrame(1, i) = _filteredViconMarkerPositions_robotRootFrame.at(it).e1;
518 observedHandPositions_robotRootFrame(2, i) = _filteredViconMarkerPositions_robotRootFrame.at(it).e2;
520 modelHandPositions_handFrame(0, i) =
hand->getSensor(_handMarkerMapping.at(it))->getTransformationFrom(hand)(0, 3);
521 modelHandPositions_handFrame(1, i) =
hand->getSensor(_handMarkerMapping.at(it))->getTransformationFrom(hand)(1, 3);
522 modelHandPositions_handFrame(2, i) =
hand->getSensor(_handMarkerMapping.at(it))->getTransformationFrom(hand)(2, 3);
525 Eigen::Vector3f position = (root->getGlobalPose() * observedHandPositions_robotRootFrame).block<3, 1>(0, i);
526 debugDrawerPrx->setSphereVisu(
"markerVisu", it,
new Vector3(position), DrawColor {0, 1, 0, 1}, 7);
534 Eigen::Matrix4f observedHandRootPose_robotRootFrame = registration3d(modelHandPositions_handFrame.transpose(), observedHandPositions_robotRootFrame.transpose());
536 _newObservedHandRootPose = observedHandRootPose_robotRootFrame;
537 _observedHandPosesUpdated =
true;
539 debugDrawerPrx->setPoseVisu(
"MarkerVisu",
"observedTCP",
new Pose(root->getGlobalPose() * observedHandRootPose_robotRootFrame));
540 debugDrawerPrx->setPoseVisu(
"MarkerVisu",
"kinematicTCP",
new Pose(
hand->getGlobalPose()));
543 float errorPos = (
hand->getPoseInRootFrame().block<3, 1>(0, 3) - observedHandRootPose_robotRootFrame.block<3, 1>(0, 3)).
norm();
548 Eigen::Matrix3f oriDir =
hand->getPoseInRootFrame().block<3, 3>(0, 0) * observedHandRootPose_robotRootFrame.block<3, 3>(0, 0).inverse();
549 Eigen::AngleAxisf aa(oriDir);
550 float errorOri = aa.angle();
552 ARMARX_INFO <<
"Error (Hand) in rootFrame between model and vicon ---- Position-Error: " << errorPos <<
" mm, Orientation-Error: " << errorOri <<
" rad";
554 data.push_back(errorPos);
555 data.push_back(errorOri);
557 _filteredViconMarkerPositionsUpdated =
false;
561 void DHParameterOptimizationLogger::logRawTorqueTicks(std::vector<float>&
data)
const
565 if (robotUnitObserver->existsChannel(
"SensorDevices"))
567 for (
auto& node : _jointNames)
569 if (robotUnitObserver->existsDataField(
"SensorDevices", node +
"_SensorValueArmar6Actuator_torqueTicks"))
571 data.push_back((
float) robotUnitObserver->getDatafieldByName(
"SensorDevices", node +
"_SensorValueArmar6Actuator_torqueTicks")->getInt());
575 data.push_back(0.0f);
581 for (
size_t i = 0; i < _jointNames.size(); i++)
583 data.push_back(0.0f);
588 void DHParameterOptimizationLogger::logTorqueValues(std::vector<float>&
data)
const
592 if (robotUnitObserver->existsChannel(
"SensorDevices"))
594 for (
auto& node : _jointNames)
596 if (robotUnitObserver->existsDataField(
"SensorDevices", node +
"_SensorValueArmar6Actuator_torque"))
598 data.push_back(robotUnitObserver->getDatafieldByName(
"SensorDevices", node +
"_SensorValueArmar6Actuator_torque")->getFloat());
602 data.push_back(0.0f);
608 for (
size_t i = 0; i < _jointNames.size(); i++)
610 data.push_back(0.0f);
615 void DHParameterOptimizationLogger::logFTSensorValues(std::vector<float>&
data)
const
619 std::string datafieldName ;
620 if (_kc->getTCP()->getName() ==
"Hand R TCP")
622 datafieldName =
"FT R_FTSensorDataValue_torque";
624 else if (_kc->getTCP()->getName() ==
"Hand L TCP")
626 datafieldName =
"FT L_FTSensorDataValue_torque";
629 if (robotUnitObserver->existsChannel(
"SensorDevices"))
631 if (robotUnitObserver->existsDataField(
"SensorDevices", datafieldName))
633 TimedVariantPtr tv = TimedVariantPtr::dynamicCast(robotUnitObserver->getDatafieldByName(
"SensorDevices", datafieldName));
637 data.push_back(
v->x);
638 data.push_back(
v->y);
639 data.push_back(
v->z);
643 data.push_back(0.0f);
644 data.push_back(0.0f);
645 data.push_back(0.0f);
650 data.push_back(0.0f);
651 data.push_back(0.0f);
652 data.push_back(0.0f);
656 StringVector3fMap DHParameterOptimizationLogger::waitBlockingForAllMarkersFiltered()
659 std::map<std::string, armarx::rtfilters::AverageFilter> viconDataFilters;
662 viconDataFilters.clear();
663 for (
auto&
s : _neckMarker)
669 for (
auto&
s : _handMarker)
677 StringVector3fMap viconValues;
678 bool viconValuesSet =
false;
679 while (_viconLoggingActive && !viconValuesSet)
682 if (!_viconBufferUpdated)
688 _viconBufferUpdated =
false;
691 std::unique_lock lock(_bufferMutex);
692 viconValues = _viconMarkerBuffer;
695 for (
const std::string&
s : _neckMarker)
698 Vector3f
v = viconValues.at(
s);
699 if (
v.e0 == 0.0f &&
v.e1 == 0.0f &&
v.e2 == 0.0f)
704 for (
const std::string&
s : _handMarker)
707 Vector3f
v = viconValues.at(
s);
708 if (
v.e0 == 0.0f &&
v.e1 == 0.0f &&
v.e2 == 0.0f)
715 for (
const std::string&
s : _neckMarker)
717 Vector3f
v = viconValues.at(
s);
718 viconDataFilters.at(
s +
"_x").update(_timeStampLastViconDataUpdate,
v.e0);
719 viconDataFilters.at(
s +
"_y").update(_timeStampLastViconDataUpdate,
v.e1);
720 viconDataFilters.at(
s +
"_z").update(_timeStampLastViconDataUpdate,
v.e2);
722 for (
const std::string&
s : _handMarker)
724 Vector3f
v = viconValues.at(
s);
725 viconDataFilters.at(
s +
"_x").update(_timeStampLastViconDataUpdate,
v.e0);
726 viconDataFilters.at(
s +
"_y").update(_timeStampLastViconDataUpdate,
v.e1);
727 viconDataFilters.at(
s +
"_z").update(_timeStampLastViconDataUpdate,
v.e2);
729 if (i >= _filterSize)
732 for (
const std::string&
s : _neckMarker)
734 float x = viconDataFilters.at(
s +
"_x").calculate();
735 float y = viconDataFilters.at(
s +
"_y").calculate();
736 float z = viconDataFilters.at(
s +
"_z").calculate();
741 viconValues.insert(std::make_pair(
s,
v));
744 for (
const std::string&
s : _handMarker)
746 float x = viconDataFilters.at(
s +
"_x").calculate();
747 float y = viconDataFilters.at(
s +
"_y").calculate();
748 float z = viconDataFilters.at(
s +
"_z").calculate();
753 viconValues.insert(std::make_pair(
s,
v));
755 viconValuesSet =
true;
764 TIMING_END(waitBlockingForAllMarkersFiltered);
765 viconDataFilters.clear();
769 VirtualRobot::RobotNodePtr DHParameterOptimizationLogger::getNeckMarkerRootNode()
const
772 VirtualRobot::RobotNodePtr node;
773 if (_kc->getTCP()->getName() ==
"Hand R TCP")
775 node = localRobot->getRobotNode(
"DHParameterOptimization_NeckRight");
777 else if (_kc->getTCP()->getName() ==
"Hand L TCP")
779 node = localRobot->getRobotNode(
"DHParameterOptimization_NeckLeft");
783 ARMARX_ERROR <<
"Kinematic chain does not have a tcp with name 'Hand R TCP' or 'Hand L TCP'. Maybe the used robot is not Armar-6?!";
788 VirtualRobot::RobotNodePtr DHParameterOptimizationLogger::getHandMarkerRootNode()
const
791 VirtualRobot::RobotNodePtr node;
792 if (_kc->getTCP()->getName() ==
"Hand R TCP")
794 node = localRobot->getRobotNode(
"DHParameterOptimization_HandRight");
796 else if (_kc->getTCP()->getName() ==
"Hand L TCP")
798 node = localRobot->getRobotNode(
"DHParameterOptimization_HandLeft");
802 ARMARX_ERROR <<
"Kinematic chain does not have a tcp with name 'Hand R TCP' or 'Hand L TCP'. Maybe the used robot is not Armar-6?!";
807 std::pair<float, float> DHParameterOptimizationLogger::calculateRepeatError()
809 if (!_observedHandPosesUpdated)
811 ARMARX_WARNING <<
"Pose of HandViconMarker have not been updated. No repeat accuracy calculated. (writes -1.0 in log)";
812 return std::make_pair(-1.0f, -1.0f);
814 _observedHandPosesUpdated =
false;
820 Eigen::Vector3f errorV = oldPose.block<3, 1>(0, 3) - newPose.block<3, 1>(0, 3);
821 float errorPos = errorV.norm();
823 Eigen::Matrix3f oriDir = oldPose.block<3, 3>(0, 0) * newPose.block<3, 3>(0, 0).inverse();
824 Eigen::AngleAxisf aa(oriDir);
825 float errorOri = aa.angle();
827 ARMARX_INFO <<
"Repeat Accuracy: Error between old pose and new pose ---- Position-Error: " << errorPos <<
" mm, Orientation-Error: " << errorOri <<
" rad";
829 return std::make_pair(errorPos, errorOri);
833 Eigen::Matrix4f DHParameterOptimizationLogger::registration3d(
const Eigen::MatrixXf&
A,
const Eigen::MatrixXf& B)
const
838 assert(
A.rows() == B.rows());
839 assert(
A.cols() == B.cols());
841 assert(
A.cols() == 3 ||
A.cols() == 4);
842 assert(
A.rows() >= 3);
845 Eigen::Vector3f centroid_A =
A.block(0, 0,
A.rows(), 3).colwise().mean();
846 Eigen::Vector3f centroid_B = B.block(0, 0,
A.rows(), 3).colwise().mean();
848 Eigen::MatrixXf AA =
A.block(0, 0,
A.rows(), 3).rowwise() - centroid_A.transpose();
849 Eigen::MatrixXf BB = (B.block(0, 0,
A.rows(), 3).rowwise() - centroid_B.transpose());
851 Eigen::MatrixXf H = (AA).
transpose() * BB;
854 Eigen::JacobiSVD<Eigen::MatrixXf> svd(H, Eigen::ComputeThinU | Eigen::ComputeThinV);
862 if (R.determinant() < 0)
868 R = V * U.transpose();
872 Eigen::Vector3f t = -R * centroid_A + centroid_B;
875 T.block(0, 0, 3, 3) = R;
876 T.block(0, 3, 3, 1) = t;
881 StringVector3fMap DHParameterOptimizationLogger::transformViconMarkerPositionsIntoRobotRootFrame(
const StringVector3fMap&
values)
const
884 VirtualRobot::RobotNodePtr neck = getNeckMarkerRootNode();
886 Eigen::MatrixXf observedNeckPositions_viconRootFrame(3, _neckMarker.size());
887 Eigen::MatrixXf modelNeckPositions_neckFrame(3, _neckMarker.size());
890 for (
auto& it : _neckMarker)
892 observedNeckPositions_viconRootFrame(0, i) =
values.at(it).e0;
893 observedNeckPositions_viconRootFrame(1, i) =
values.at(it).e1;
894 observedNeckPositions_viconRootFrame(2, i) =
values.at(it).e2;
895 modelNeckPositions_neckFrame(0, i) = neck->getSensor(_neckMarkerMapping.at(it))->getTransformationFrom(neck)(0, 3);
896 modelNeckPositions_neckFrame(1, i) = neck->getSensor(_neckMarkerMapping.at(it))->getTransformationFrom(neck)(1, 3);
897 modelNeckPositions_neckFrame(2, i) = neck->getSensor(_neckMarkerMapping.at(it))->getTransformationFrom(neck)(2, 3);
901 Eigen::Matrix4f observedNeckRootPose_viconRootFrame = registration3d(modelNeckPositions_neckFrame.transpose(), observedNeckPositions_viconRootFrame.transpose());
902 localRobot->setGlobalPoseForRobotNode(neck, observedNeckRootPose_viconRootFrame);
903 Eigen::Matrix4f realRobotToVicon = localRobot->getGlobalPose().inverse();
920 StringVector3fMap transformedValues;
923 std::string markerName = it.first;
924 armarx::Vector3f pos_viconRootFrame = it.second;
927 mat(0, 3) = pos_viconRootFrame.e0;
928 mat(1, 3) = pos_viconRootFrame.e1;
929 mat(2, 3) = pos_viconRootFrame.e2;
931 mat = realRobotToVicon * mat;
937 transformedValues[markerName] = r;
940 return transformedValues;