23
23
24
24
25
25
// INCLUDES
26
- #include < string>
27
26
#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>
29
30
#include < OpenSim/Simulation/Model/Model.h>
31
+ #include < OpenSim/Simulation/OrientationsReference.h>
32
+ #include < OpenSim/Simulation/InverseKinematicsSolver.h>
30
33
#include < OpenSim/Tools/InverseKinematicsTool.h>
31
34
#include < OpenSim/Tools/IKTaskSet.h>
32
35
#include < OpenSim/Auxiliary/auxiliaryTestFunctions.h>
@@ -37,20 +40,56 @@ using namespace std;
37
40
void testMarkerWeightAssignments (const std::string& ikSetupFile);
38
41
void checkMarkersReferenceConsistencyFromTool (InverseKinematicsTool& ik);
39
42
43
+ void testInverseKinematicsSolverWithOrientations ();
44
+ void testInverseKinematicsSolverWithEulerAnglesFromFile ();
45
+
40
46
int main ()
41
47
{
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
+
42
59
try {
60
+ ++itc;
61
+ testInverseKinematicsSolverWithEulerAnglesFromFile ();
62
+ }
63
+ catch (const std::exception& e) {
64
+ cout << e.what () << endl;
65
+ failures.push_back (" testInverseKinematicsSolverWithEulerAnglesFromFile" );
66
+ }
43
67
68
+ try {
69
+ ++itc;
44
70
testMarkerWeightAssignments (" subject01_Setup_InverseKinematics.xml" );
71
+ }
72
+ catch (const std::exception& e) {
73
+ cout << e.what () << endl;
74
+ failures.push_back (" testMarkerWeightAssignments" );
75
+ }
45
76
77
+ Storage standard (" std_subject01_walk1_ik.mot" );
78
+ try {
46
79
InverseKinematicsTool ik1 (" subject01_Setup_InverseKinematics.xml" );
47
80
ik1.run ();
48
- Storage result1 (ik1.getOutputMotionFileName ()), standard ( " std_subject01_walk1_ik.mot " ) ;
81
+ Storage result1 (ik1.getOutputMotionFileName ());
49
82
CHECK_STORAGE_AGAINST_STANDARD (result1, standard,
50
83
std::vector<double >(24 , 0.2 ), __FILE__, __LINE__,
51
84
" testInverseKinematicsGait2354 failed" );
52
85
cout << " testInverseKinematicsGait2354 passed" << endl;
86
+ }
87
+ catch (const std::exception& e) {
88
+ cout << e.what () << endl;
89
+ failures.push_back (" testInverseKinematicsGait2354" );
90
+ }
53
91
92
+ try {
54
93
InverseKinematicsTool ik2 (" subject01_Setup_InverseKinematics_NoModel.xml" );
55
94
Model mdl (" subject01_simbody.osim" );
56
95
mdl.initSystem ();
@@ -61,16 +100,30 @@ int main()
61
100
std::vector<double >(24 , 0.2 ), __FILE__, __LINE__,
62
101
" testInverseKinematicsGait2354 GUI workflow failed" );
63
102
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
+ }
64
108
109
+ try {
65
110
InverseKinematicsTool ik3 (" constraintTest_setup_ik.xml" );
66
111
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" );
68
117
}
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;
71
123
return 1 ;
72
124
}
73
- cout << " Done" << endl;
125
+
126
+ cout << " Done. All cases passed." << endl;
74
127
return 0 ;
75
128
}
76
129
@@ -88,7 +141,7 @@ void testMarkerWeightAssignments(const std::string& ikSetupFile)
88
141
tasks[i].setWeight (double (i));
89
142
}
90
143
91
- cout << tasks.dump (true ) << endl;
144
+ cout << tasks.dump () << endl;
92
145
// update tasks used by the IK tool
93
146
ik.getIKTaskSet () = tasks;
94
147
@@ -104,7 +157,7 @@ void testMarkerWeightAssignments(const std::string& ikSetupFile)
104
157
tasks2.adoptAndAppend (tasks[i].clone ());
105
158
}
106
159
107
- cout << tasks2.dump (true ) << endl;
160
+ cout << tasks2.dump () << endl;
108
161
ik.getIKTaskSet () = tasks2;
109
162
110
163
// perform the check
@@ -128,7 +181,7 @@ void testMarkerWeightAssignments(const std::string& ikSetupFile)
128
181
tasks.adoptAndAppend (newMarker);
129
182
tasks.setName (" Added superfluous tasks" );
130
183
131
- cout << tasks.dump (true ) << endl;
184
+ cout << tasks.dump () << endl;
132
185
// update the tasks of the IK Tool
133
186
ik.getIKTaskSet () = tasks;
134
187
@@ -168,3 +221,256 @@ void checkMarkersReferenceConsistencyFromTool(InverseKinematicsTool& ik)
168
221
}
169
222
}
170
223
}
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