--- a +++ b/feasible_joint_stiffness/opensim_utils.py @@ -0,0 +1,308 @@ +# \brief A variety of useful OpenSim utilities. +# +# @author Dimitar Stanev (jimstanev@gmail.com) +# +import os +import opensim +from tqdm import tqdm +import numpy as np +from util import plot_sto + + +def calculate_muscle_data(model_file, ik_file): + """This function calculates the moment arm and maximum muscle force provided an + OpenSim model and a motion from inverse kinematics. + + Parameters + ---------- + model_file: string + OpenSim .osim file + ik_file: string + .mot from inverse kinematics + + Returns + ------- + tuple: + (moment arm[time x coordinates x muscles], max_force[time x muscles]) + """ + model = opensim.Model(model_file) + state = model.initSystem() + + # model coordinates dictionary + model_coordinates = {} + for i in range(0, model.getNumCoordinates()): + model_coordinates[model.getCoordinateSet().get(i).getName()] = i + + # model muscles dictionary + model_muscles = {} + for i in range(0, model.getNumControls()): + model_muscles[model.getMuscles().get(i).getName()] = i + + # process the motion + motion = opensim.Storage(ik_file) + labels = motion.getColumnLabels() + isInDeg = motion.isInDegrees() + + # calculate moment arm and max force + max_force = np.empty([motion.getSize(), len(model_muscles)], float) + moment_arm = np.empty([motion.getSize(), + len(model_coordinates), + len(model_muscles)], + float) + for t in tqdm(range(0, motion.getSize())): + state_vector = motion.getStateVector(t) + state_data = state_vector.getData() + + # update model pose + for j in range(0, state_data.getSize()): + coordinate = model_coordinates[labels.get(j + 1)] # time is 0 + if isInDeg: + value = np.deg2rad(state_data.get(j)) + else: + value = state_data.get(j) + + model.updCoordinateSet().get(coordinate).setValue(state, value) + + model.realizePosition(state) + + # calculate muscle moment arm + for coordinate_index in model_coordinates.values(): + for muscle_index in model_muscles.values(): + coordinate = model.getCoordinateSet().get(coordinate_index) + muscle = model.getMuscles().get(muscle_index) + ma = muscle.computeMomentArm(state, coordinate) + moment_arm[t, coordinate_index, muscle_index] = ma + + # calculate max force (TODO use force-length/velocity relations) + for muscle_index in model_muscles.values(): + muscle = model.getMuscles().get(muscle_index) + max_force[t, muscle_index] = muscle.getMaxIsometricForce() + + return (moment_arm, max_force) + + +def construct_ik_task_set(model, marker_data, task_set): + """Construct OpenSim Inverse Kinematics task set. + + In older versions of OpenSim (e.g. 3.3) IK will not execute when there are + virtual markers that do not exist in the marker data. + + """ + virtual_markers = model.getMarkerSet() + marker_names = marker_data.getMarkerNames() + for i in range(0, marker_names.getSize()): + marker_name = marker_names.get(i) + exists = False + for j in range(0, virtual_markers.getSize()): + if virtual_markers.get(j).getName() == marker_name: + task = opensim.IKMarkerTask() + task.setName(marker_name) + task.setApply(True) + task.setWeight(1) + task_set.adoptAndAppend(task) + exists = True + break + + if not exists: + task = opensim.IKMarkerTask() + task.setName(marker_name) + task.setApply(False) + task.setWeight(1) + task_set.adoptAndAppend(task) + + +def perform_ik(model_file, trc_file, results_dir): + """Performs Inverse Kinematics using OpenSim. + + Parameters + ---------- + model_file: str + OpenSim model (.osim) + trc_file: str + the experimentally measured marker trajectories (.trc) + results_dir: str + directory to store the results + + """ + model = opensim.Model(model_file) + model.initSystem() + marker_data = opensim.MarkerData(trc_file) + name = os.path.basename(trc_file)[:-4] + ik_tool = opensim.InverseKinematicsTool() + task_set = ik_tool.getIKTaskSet() + construct_ik_task_set(model, marker_data, task_set) + ik_tool.setName(name) + ik_tool.setModel(model) + ik_tool.setStartTime(marker_data.getStartFrameTime()) + ik_tool.setEndTime(marker_data.getLastFrameTime()) + ik_tool.setMarkerDataFileName(trc_file) + ik_tool.setResultsDir(results_dir) + ik_file = results_dir + name + '_ik.mot' + ik_tool.setOutputMotionFileName(ik_file) + ik_tool.run() + return ik_file + + +def visualize_ik_results(ik_file, ik_errors=None): + """A utility for visualizing the Inverse Kinematics results. + + Parameters + ---------- + ik_file: str + coordinate results from IK (.mot) + ik_errors: str, optional + marker errors (.sto) + """ + plot_sto(ik_file, 4) + if ik_errors is not None: + plot_sto(ik_errors, 3) + + +def perform_so(model_file, ik_file, grf_file, grf_xml, reserve_actuators, + results_dir): + """Performs Static Optimization using OpenSim. + + Parameters + ---------- + model_file: str + OpenSim model (.osim) + ik_file: str + kinematics calculated from Inverse Kinematics + grf_file: str + the ground reaction forces + grf_xml: str + xml description containing how to apply the GRF forces + reserve_actuators: str + path to the reserve actuator .xml file + results_dir: str + directory to store the results + """ + # model + model = opensim.Model(model_file) + + # prepare external forces xml file + name = os.path.basename(grf_file)[:-8] + external_loads = opensim.ExternalLoads(model, grf_xml) + external_loads.setExternalLoadsModelKinematicsFileName(ik_file) + external_loads.setDataFileName(grf_file) + external_loads.setLowpassCutoffFrequencyForLoadKinematics(6) + external_loads.printToXML(results_dir + name + '.xml') + + # add reserve actuators + force_set = opensim.ForceSet(model, reserve_actuators) + force_set.setMemoryOwner(False) # model will be the owner + for i in range(0, force_set.getSize()): + model.updForceSet().append(force_set.get(i)) + + # construct static optimization + motion = opensim.Storage(ik_file) + static_optimization = opensim.StaticOptimization() + static_optimization.setStartTime(motion.getFirstTime()) + static_optimization.setEndTime(motion.getLastTime()) + static_optimization.setUseModelForceSet(True) + static_optimization.setUseMusclePhysiology(True) + static_optimization.setActivationExponent(2) + static_optimization.setConvergenceCriterion(0.0001) + static_optimization.setMaxIterations(100) + model.addAnalysis(static_optimization) + + # analysis + analysis = opensim.AnalyzeTool(model) + analysis.setName(name) + analysis.setModel(model) + analysis.setInitialTime(motion.getFirstTime()) + analysis.setFinalTime(motion.getLastTime()) + analysis.setLowpassCutoffFrequency(6) + analysis.setCoordinatesFileName(ik_file) + analysis.setExternalLoadsFileName(results_dir + name + '.xml') + analysis.setLoadModelAndInput(True) + analysis.setResultsDir(results_dir) + analysis.run() + so_force_file = results_dir + name + '_StaticOptimization_force.sto' + so_activations_file = results_dir + name + \ + '_StaticOptimization_activation.sto' + return (so_force_file, so_activations_file) + + +def visualize_so_results(activations_file, forces_file=None): + """A utility for visualizing the Static Optimization results. + + Parameters + ---------- + activations_file: str + activations results from SO (.sto) + forces_file: str, optional + forces (.sto) + """ + plot_sto(activations_file, 8, '_r') + if forces_file is not None: + plot_sto(forces_file, 8, '_r') + + +def perform_jra(model_file, ik_file, grf_file, grf_xml, reserve_actuators, + muscle_forces_file, results_dir, prefix=''): + """Performs Static Optimization using OpenSim. + + Parameters + ---------- + model_file: str + OpenSim model (.osim) + ik_file: str + kinematics calculated from Inverse Kinematics + grf_file: str + the ground reaction forces + grf_xml: str + xml description containing how to apply the GRF forces + reserve_actuators: str + path to the reserve actuator .xml file + muscle_forces_file: str + path to the file containing the muscle forces from SO + results_dir: str + directory to store the results + prefix: str + prefix of the resultant joint reaction loads + """ + # model + model = opensim.Model(model_file) + + # prepare external forces xml file + name = os.path.basename(grf_file)[:-8] + external_loads = opensim.ExternalLoads(model, grf_xml) + external_loads.setExternalLoadsModelKinematicsFileName(ik_file) + external_loads.setDataFileName(grf_file) + external_loads.setLowpassCutoffFrequencyForLoadKinematics(6) + external_loads.printToXML(results_dir + name + '.xml') + + # TODO this may not be needed + # add reserve actuators (must not be appended when performing JRA) + # force_set = opensim.ForceSet(model, reserve_actuators) + # force_set.setMemoryOwner(False) # model will be the owner + # for i in range(0, force_set.getSize()): + # model.updForceSet().append(force_set.get(i)) + # # model.addForce(force_set.get(i)) + + # construct joint reaction analysis + motion = opensim.Storage(ik_file) + joint_reaction = opensim.JointReaction(model) + joint_reaction.setName('JointReaction') + joint_reaction.setStartTime(motion.getFirstTime()) + joint_reaction.setEndTime(motion.getLastTime()) + joint_reaction.setForcesFileName(muscle_forces_file) + model.addAnalysis(joint_reaction) + model.initSystem() + + # analysis + analysis = opensim.AnalyzeTool(model) + analysis.setName(prefix + name) + analysis.setModel(model) + analysis.setModelFilename(model_file) + analysis.setInitialTime(motion.getFirstTime()) + analysis.setFinalTime(motion.getLastTime()) + analysis.setLowpassCutoffFrequency(6) + analysis.setCoordinatesFileName(ik_file) + analysis.setExternalLoadsFileName(results_dir + name + '.xml') + analysis.setLoadModelAndInput(True) + analysis.setResultsDir(results_dir) + analysis.run() + jra_file = results_dir + name + '_JointReaction_ReactionLoads.sto' + return jra_file