Detailed description |
|
I see there are a number of ways in the API to go from a Storage file to a controller; whether it's an actual Controller or a ControlSet to specify control constraints while another controller (CMC) is actually in charge. Despite these great tools, I was not able to easily do what I wanted.
I wanted to take the joint torque output from RRA and apply that torque profile as a CoordinateActuator during CMC. So this is already perhaps a unique case, where the data in the Storage is from OpenSim itself; I presume the typical use case is that the user is making the Storage using their own non-OpenSim data, and they are using Storage just as a data file format (e.g., a CSV file). I was using Jython scripting to do this. I am more focused on converting Storage to ControlSet, but these issues my also be relevant for converting to a Controller.
In any case, here are the issues I ran into:
1) The names of the columns in the RRA-output Storage file containing joint torques (Actuator_force.sto) had column names that were NOT the name of my actuator (my actuators were ankle_robot_r, ankle_robot_l). The API makes the assumption that the Storage column names correspond to an actuator name. It would have been helpful for me if I could rename columns in the Storage file easily; it seems possible (by providing an edited Array of all the Storage column names?). I don't want to have to edit the Storage file (column names) manually.
2) The columns of the Storage file I wanted to use for Control were not necessarily adjacent. So, using '# of columns' and 'index of first column to use' were not sufficient for me to convert Storage directly to ControlSet.
3) I also felt uncomfortable using the default values for the resulting ControlLinear's that the ControlSet(Storage) constructor creates. I wanted to edit default_min/default_max to not be the values used for muscles.
The resolution that solved all 3 of these issues was to do the following:
sto = Storage(sto_fpath)
state_index = sto.getStateIndex('ankle_angle_r')
sto.mutiplyColumn(state_index, 0.5)
cset_r = ControlSet(sto, 1, state_index)
clin_r = ControlLinear.safeDownCast(cset_r.get(0))
clin_r.setName('ankle_robot_r.excitation')
clin_r.setDefaultParameterMin(-1000)
clin_r.setDefaultParameterMax(1000)
clin_r.setUseSteps(True)
clin_r.setExtrapolate(False)
actual_cset.cloneAndAppend(clin_r)
(I believe that having extrapolate as True could lead to unintended behavior.)
As you can see, I am basically using the ControlSet(Storage) constructor to generate a ControlLinear, that I then modify and place into the ControlSet I'm actually going to use. I do this again for the left actuator. So in my particular use case, since I'm using the API/scripting, it was preferable to have a ControlLinear(Storage, index) constructor instead of a ControlSet(Storage) instructor. But this is exactly what ControlSet::ExtractControl() does! So maybe one option is to move ExtractControl() to ControlLinear(Storage), or to a static function ControlLinear::fromStorage(), since the code is there anyway, just as a private function.
Also, why does ControlSet::ExtractControl() start with a capital "E"?
Something that did work well is that I wanted to manipulate the data in the Storage; I wanted to scale down the desired column by a constant number, and the Storage interface allowed me to do this before converting it into a ControlSet. So it's good these methods take a Storage object rather than a filename of a .sto/.mot file. |
|