Skip to content

Commit 495311c

Browse files
authored
Merge pull request #2412 from opensim-org/ik_orientations_tracking
Add Orientation tracking to InverseKinematicsSolver
2 parents 3cc8808 + dda6fef commit 495311c

File tree

12 files changed

+1368
-88
lines changed

12 files changed

+1368
-88
lines changed

Applications/IK/test/testIK.cpp

Lines changed: 316 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323

2424

2525
// INCLUDES
26-
#include <string>
2726
#include <OpenSim/Common/Storage.h>
28-
#include <OpenSim/Common/ScaleSet.h>
27+
#include "OpenSim/Common/STOFileAdapter.h"
28+
#include "OpenSim/Common/TRCFileAdapter.h"
29+
#include <OpenSim/Common/Reporter.h>
2930
#include <OpenSim/Simulation/Model/Model.h>
31+
#include <OpenSim/Simulation/OrientationsReference.h>
32+
#include <OpenSim/Simulation/InverseKinematicsSolver.h>
3033
#include <OpenSim/Tools/InverseKinematicsTool.h>
3134
#include <OpenSim/Tools/IKTaskSet.h>
3235
#include <OpenSim/Auxiliary/auxiliaryTestFunctions.h>
@@ -37,20 +40,56 @@ using namespace std;
3740
void testMarkerWeightAssignments(const std::string& ikSetupFile);
3841
void checkMarkersReferenceConsistencyFromTool(InverseKinematicsTool& ik);
3942

43+
void testInverseKinematicsSolverWithOrientations();
44+
void testInverseKinematicsSolverWithEulerAnglesFromFile();
45+
4046
int main()
4147
{
48+
int itc = 0;
49+
SimTK::Array_<std::string> failures;
50+
51+
try { ++itc;
52+
testInverseKinematicsSolverWithOrientations();
53+
}
54+
catch (const std::exception& e) {
55+
cout << e.what() << endl;
56+
failures.push_back("testInverseKinematicsSolverWithOrientations");
57+
}
58+
4259
try {
60+
++itc;
61+
testInverseKinematicsSolverWithEulerAnglesFromFile();
62+
}
63+
catch (const std::exception& e) {
64+
cout << e.what() << endl;
65+
failures.push_back("testInverseKinematicsSolverWithEulerAnglesFromFile");
66+
}
4367

68+
try {
69+
++itc;
4470
testMarkerWeightAssignments("subject01_Setup_InverseKinematics.xml");
71+
}
72+
catch (const std::exception& e) {
73+
cout << e.what() << endl;
74+
failures.push_back("testMarkerWeightAssignments");
75+
}
4576

77+
Storage standard("std_subject01_walk1_ik.mot");
78+
try {
4679
InverseKinematicsTool ik1("subject01_Setup_InverseKinematics.xml");
4780
ik1.run();
48-
Storage result1(ik1.getOutputMotionFileName()), standard("std_subject01_walk1_ik.mot");
81+
Storage result1(ik1.getOutputMotionFileName());
4982
CHECK_STORAGE_AGAINST_STANDARD(result1, standard,
5083
std::vector<double>(24, 0.2), __FILE__, __LINE__,
5184
"testInverseKinematicsGait2354 failed");
5285
cout << "testInverseKinematicsGait2354 passed" << endl;
86+
}
87+
catch (const std::exception& e) {
88+
cout << e.what() << endl;
89+
failures.push_back("testInverseKinematicsGait2354");
90+
}
5391

92+
try {
5493
InverseKinematicsTool ik2("subject01_Setup_InverseKinematics_NoModel.xml");
5594
Model mdl("subject01_simbody.osim");
5695
mdl.initSystem();
@@ -61,16 +100,30 @@ int main()
61100
std::vector<double>(24, 0.2), __FILE__, __LINE__,
62101
"testInverseKinematicsGait2354 GUI workflow failed");
63102
cout << "testInverseKinematicsGait2354 GUI workflow passed" << endl;
103+
}
104+
catch (const std::exception& e) {
105+
cout << e.what() << endl;
106+
failures.push_back("testInverseKinematicsGait2354_GUI_workflow");
107+
}
64108

109+
try {
65110
InverseKinematicsTool ik3("constraintTest_setup_ik.xml");
66111
ik3.run();
67-
cout << "testInverseKinematicsCosntraintTest passed" << endl;
112+
cout << "testInverseKinematicsConstraintTest passed" << endl;
113+
}
114+
catch (const std::exception& e) {
115+
cout << e.what() << endl;
116+
failures.push_back("testInverseKinematicsConstraintTest");
68117
}
69-
catch (const Exception& e) {
70-
e.print(cerr);
118+
119+
if (!failures.empty()) {
120+
cout << "Done, with " << failures.size() << " failure(s) out of ";
121+
cout << itc << " test cases." << endl;
122+
cout << "Failure(s): " << failures << endl;
71123
return 1;
72124
}
73-
cout << "Done" << endl;
125+
126+
cout << "Done. All cases passed." << endl;
74127
return 0;
75128
}
76129

@@ -88,7 +141,7 @@ void testMarkerWeightAssignments(const std::string& ikSetupFile)
88141
tasks[i].setWeight(double(i));
89142
}
90143

91-
cout << tasks.dump(true) << endl;
144+
cout << tasks.dump() << endl;
92145
// update tasks used by the IK tool
93146
ik.getIKTaskSet() = tasks;
94147

@@ -104,7 +157,7 @@ void testMarkerWeightAssignments(const std::string& ikSetupFile)
104157
tasks2.adoptAndAppend(tasks[i].clone());
105158
}
106159

107-
cout << tasks2.dump(true) << endl;
160+
cout << tasks2.dump() << endl;
108161
ik.getIKTaskSet() = tasks2;
109162

110163
// perform the check
@@ -128,7 +181,7 @@ void testMarkerWeightAssignments(const std::string& ikSetupFile)
128181
tasks.adoptAndAppend(newMarker);
129182
tasks.setName("Added superfluous tasks");
130183

131-
cout << tasks.dump(true) << endl;
184+
cout << tasks.dump() << endl;
132185
// update the tasks of the IK Tool
133186
ik.getIKTaskSet() = tasks;
134187

@@ -168,3 +221,256 @@ void checkMarkersReferenceConsistencyFromTool(InverseKinematicsTool& ik)
168221
}
169222
}
170223
}
224+
225+
TimeSeriesTable_<SimTK::Rotation> convertMotionFileToRotations(
226+
Model& model,
227+
const std::string& motionFile)
228+
{
229+
SimTK::State& s0 = model.initSystem();
230+
auto anglesTable = STOFileAdapter::read(motionFile);
231+
232+
int nt = int(anglesTable.getNumRows());
233+
const auto& coordNames = anglesTable.getColumnLabels();
234+
// Vector of times is just the independent column of a TimeSeriesTable
235+
auto times = anglesTable.getIndependentColumn();
236+
237+
int dataRate = int(round(double(nt - 1) / (times[nt - 1] - times[0])));
238+
// get the coordinates of the model
239+
const auto& coordinates = model.getComponentList<Coordinate>();
240+
241+
// get bodies as frames that we want to "sense" rotations
242+
const auto& bodies = model.getComponentList<Body>();
243+
244+
// Coordinate values in the data file may not correspond to the order
245+
// of coordinates in the Model. Therefore it is necessary to build a
246+
// mapping between the data and the model order
247+
std::vector<int> mapDataToModel;
248+
// cycle through the coordinates in the model order and store the
249+
// corresponding column index in the table according to column name
250+
for (auto& coord : coordinates) {
251+
int index = -1;
252+
auto found = std::find(coordNames.begin(), coordNames.end(), coord.getName());
253+
if (found != coordNames.end())
254+
index = (int)std::distance(coordNames.begin(), found);
255+
mapDataToModel.push_back(index);
256+
}
257+
258+
cout << "Read in '" << motionFile << "' with " << nt << " rows." << endl;
259+
cout << "Num coordinates in file: " << coordNames.size() << endl;
260+
cout << "Num of matched coordinates in model: " << mapDataToModel.size() << endl;
261+
262+
std::vector<std::string> bodyLabels;
263+
for (auto& body : bodies) {
264+
bodyLabels.push_back(body.getName());
265+
}
266+
267+
int nc = int(bodyLabels.size());
268+
269+
// Store orientations as Rotation matrices
270+
SimTK::Matrix_<SimTK::Rotation> rotations{ int(nt), nc };
271+
272+
// Apply the read in coordinate values to the model.
273+
// Then get the rotation of the Bodies in the model and
274+
// store them as Rotations and Euler angles in separate tables.
275+
for (int i = 0; i < nt; ++i) {
276+
const auto& values = anglesTable.getRowAtIndex(i);
277+
int cnt = 0;
278+
for (auto& coord : coordinates) {
279+
if (mapDataToModel[cnt] >= 0) {
280+
if (coord.getMotionType() == Coordinate::MotionType::Rotational)
281+
coord.setValue(s0,
282+
SimTK::convertDegreesToRadians(values(mapDataToModel[cnt++])));
283+
else
284+
coord.setValue(s0, values(mapDataToModel[cnt++]));
285+
}
286+
}
287+
// pose model according to coordinate values and position constraints
288+
model.realizePosition(s0);
289+
290+
int j = 0;
291+
for (auto& body : bodies) {
292+
// extract the rotation of model bodies w.r.t. ground frame
293+
const SimTK::Rotation& rot = body.getTransformInGround(s0).R();
294+
rotations.updElt(i,j++) = rot;
295+
}
296+
}
297+
298+
TimeSeriesTable_<SimTK::Rotation> rotTable{ times, rotations, bodyLabels };
299+
rotTable.updTableMetaData()
300+
.setValueForKey("DataRate", std::to_string(dataRate));
301+
302+
return rotTable;
303+
}
304+
305+
TimeSeriesTableVec3 convertRotationsToEulerAngles(
306+
const TimeSeriesTable_<SimTK::Rotation>& rotTable)
307+
{
308+
auto labels = rotTable.getColumnLabels();
309+
auto& times = rotTable.getIndependentColumn();
310+
const auto& rotations = rotTable.getMatrix();
311+
312+
int nc = int(labels.size());
313+
int nt = int(times.size());
314+
315+
SimTK::Matrix_<SimTK::Vec3> eulerMatrix(nt, nc, SimTK::Vec3(SimTK::NaN));
316+
317+
for (int i = 0; i < nt; ++i) {
318+
for (int j = 0; j < nc; ++j) {
319+
eulerMatrix.updElt(i, j) =
320+
rotations(i, j).convertRotationToBodyFixedXYZ();
321+
}
322+
}
323+
TimeSeriesTableVec3 eulerData{ times, eulerMatrix, labels };
324+
eulerData.updTableMetaData()
325+
.setValueForKey("Units", std::string("Radians"));
326+
327+
return eulerData;
328+
}
329+
330+
void compareMotionTables(const TimeSeriesTable& report,
331+
const TimeSeriesTable& standard)
332+
{
333+
ASSERT(report.getNumRows() == standard.getNumRows());
334+
ASSERT(report.getNumColumns() == standard.getNumColumns());
335+
336+
auto reportLabels = report.getColumnLabels();
337+
auto stdLabels = standard.getColumnLabels();
338+
339+
std::vector<int> mapStdToReport;
340+
// cycle through the coordinates in the model order and store the
341+
// corresponding column index in the table according to column name
342+
for (auto& label : reportLabels) {
343+
int index = -1;
344+
auto found = std::find(stdLabels.begin(), stdLabels.end(), label);
345+
if (found != stdLabels.end()) {
346+
// skip any pelvis translations
347+
if (found->find("pelvis_t") == std::string::npos) {
348+
index = (int)std::distance(stdLabels.begin(), found);
349+
}
350+
}
351+
mapStdToReport.push_back(index);
352+
}
353+
354+
size_t nt = report.getNumRows();
355+
356+
//For simplicity, we ignore pelvis coordinates 0-5
357+
for (size_t i = 0; i < mapStdToReport.size(); ++i) {
358+
if (mapStdToReport[i] >= 0) {
359+
auto repVec = report.getDependentColumnAtIndex(i);
360+
auto stdVec = standard.getDependentColumnAtIndex(mapStdToReport[i]);
361+
auto error = SimTK::Real(SimTK_RTD)*repVec - stdVec;
362+
cout << "Column '" << reportLabels[i] << "' has RMSE = "
363+
<< sqrt(error.normSqr() / nt) << "degrees" << endl;
364+
SimTK_ASSERT1_ALWAYS((sqrt(error.normSqr() / nt) < 0.1),
365+
"Column '%s' FAILED to meet accuracy of 0.1 degree RMS.",
366+
reportLabels[i].c_str());
367+
}
368+
}
369+
}
370+
371+
void testInverseKinematicsSolverWithOrientations()
372+
{
373+
Model model("subject01_simbody.osim");
374+
375+
auto orientationsData = convertMotionFileToRotations(
376+
model, "std_subject01_walk1_ik.mot");
377+
378+
OrientationsReference oRefs(orientationsData);
379+
oRefs.set_default_weight(1.0);
380+
381+
const std::vector<double>& times = oRefs.getTimes();
382+
383+
MarkersReference mRefs{};
384+
385+
SimTK::Array_<CoordinateReference> coordinateRefs;
386+
387+
// Add a reporter to get IK computed coordinate values out
388+
TableReporter* ikReporter = new TableReporter();
389+
ikReporter->setName("ik_reporter");
390+
auto coordinates = model.getComponentList<Coordinate>();
391+
// Hookup reporter inputs to the individual coordinate outputs
392+
for (auto& coord : coordinates) {
393+
ikReporter->updInput("inputs").connect(
394+
coord.getOutput("value"), coord.getName());
395+
}
396+
model.addComponent(ikReporter);
397+
398+
399+
SimTK::State& s0 = model.initSystem();
400+
401+
// create the solver given the input data
402+
InverseKinematicsSolver ikSolver(model, mRefs, oRefs, coordinateRefs);
403+
ikSolver.setAccuracy(1e-4);
404+
405+
auto timeRange = oRefs.getValidTimeRange();
406+
cout << "Time range from: " << timeRange[0] << " to " << timeRange[1]
407+
<< "s."<< endl;
408+
409+
s0.updTime() = timeRange[0];
410+
ikSolver.assemble(s0);
411+
412+
for (double time : times) {
413+
s0.updTime() = time;
414+
cout << "time = " << time << endl;
415+
ikSolver.track(s0);
416+
// realize to report to get reporter to pull values from model
417+
model.realizeReport(s0);
418+
}
419+
420+
auto report = ikReporter->getTable();
421+
const auto standard = STOFileAdapter::read("std_subject01_walk1_ik.mot");
422+
423+
compareMotionTables(report, standard);
424+
}
425+
426+
void testInverseKinematicsSolverWithEulerAnglesFromFile()
427+
{
428+
Model model("subject01_simbody.osim");
429+
auto orientationsData = convertMotionFileToRotations(
430+
model, "std_subject01_walk1_ik.mot");
431+
432+
auto eulerData = convertRotationsToEulerAngles(orientationsData);
433+
STOFileAdapter_<SimTK::Vec3>::write(eulerData, "subject1_walk_euler_angles.sto");
434+
435+
// Add a reporter to get IK computed coordinate values out
436+
TableReporter* ikReporter = new TableReporter();
437+
ikReporter->setName("ik_reporter");
438+
auto coordinates = model.getComponentList<Coordinate>();
439+
// Hookup reporter inputs to the individual coordinate outputs
440+
for (auto& coord : coordinates) {
441+
ikReporter->updInput("inputs").connect(
442+
coord.getOutput("value"), coord.getName());
443+
}
444+
model.addComponent(ikReporter);
445+
446+
SimTK::State& s0 = model.initSystem();
447+
448+
MarkersReference mRefs{};
449+
OrientationsReference oRefs("subject1_walk_euler_angles.sto");
450+
SimTK::Array_<CoordinateReference> coordRefs{};
451+
452+
// create the solver given the input data
453+
const double accuracy = 1e-4;
454+
InverseKinematicsSolver ikSolver(model, mRefs, oRefs, coordRefs);
455+
ikSolver.setAccuracy(accuracy);
456+
457+
auto& times = oRefs.getTimes();
458+
459+
s0.updTime() = times[0];
460+
ikSolver.assemble(s0);
461+
//model.getVisualizer().show(s0);
462+
463+
for (auto time : times) {
464+
s0.updTime() = time;
465+
cout << "time = " << time << endl;
466+
ikSolver.track(s0);
467+
// realize to report to get reporter to pull values from model
468+
model.realizeReport(s0);
469+
}
470+
471+
auto report = ikReporter->getTable();
472+
STOFileAdapter::write(report, "ik_euler_tracking_results.sto");
473+
474+
const auto standard = STOFileAdapter::read("std_subject01_walk1_ik.mot");
475+
compareMotionTables(report, standard);
476+
}

0 commit comments

Comments
 (0)