Grouping muscle activations
- Ana de Sousa
- Posts: 67
- Joined: Thu Apr 07, 2016 4:21 pm
Grouping muscle activations
Hi everyone!
I'm working on a project, and I need some advice. I want to group muscles in a specific area to share the same activation. Is there a way to do this in OpenSim Moco, or does it need to be handled in OpenSim itself (by modelling)?
For example, my model has two muscles: the rectus femoris and the vastus lateralis. Each muscle has its activation variable, so when I run MocoInverse, I get separate results for each muscle. I want to combine these two muscles into one group - let's call it 'quadriceps' - so that there is just one activation variable for the entire group, and I get a unified solution.
Does anyone know how to achieve this in Moco, or are there any constraints or techniques that might help?
Thanks in advance for any suggestions!
I'm working on a project, and I need some advice. I want to group muscles in a specific area to share the same activation. Is there a way to do this in OpenSim Moco, or does it need to be handled in OpenSim itself (by modelling)?
For example, my model has two muscles: the rectus femoris and the vastus lateralis. Each muscle has its activation variable, so when I run MocoInverse, I get separate results for each muscle. I want to combine these two muscles into one group - let's call it 'quadriceps' - so that there is just one activation variable for the entire group, and I get a unified solution.
Does anyone know how to achieve this in Moco, or are there any constraints or techniques that might help?
Thanks in advance for any suggestions!
- Pasha van Bijlert
- Posts: 227
- Joined: Sun May 10, 2020 3:15 am
Re: Grouping muscle activations
EDIT: my suggestion was based on the incorrect assumption that you can add multiple muscles to a "Controller" to achieve the desired result, but this was not correct, Nick suggested later in the thread to use the newly added SynergyController().
/EDIT
Hi Ana,
If you load a model into a problem that you solve with Moco, I think Moco automatically adds a controller to each actuator that doesn't already have one. I think if you first manually define a controller, then add the muscles you want it to control, you circumvent this. The code would look something like this (but I haven't tested it):
You may have to SafeDownCast the muscles first, and/or check whether the other (uncontrolled) actuators still get their own controller automatically.
Cheers,
Pasha
/EDIT
Hi Ana,
If you load a model into a problem that you solve with Moco, I think Moco automatically adds a controller to each actuator that doesn't already have one. I think if you first manually define a controller, then add the muscles you want it to control, you circumvent this. The code would look something like this (but I haven't tested it):
Code: Select all
mycontroller = Controller()
mycontroller.setActuator(muscle_one)
mycontroller.setActuator(muscle_two)
model.addController(mycontroller)
model.initSystem()
Cheers,
Pasha
Last edited by Pasha van Bijlert on Tue Sep 03, 2024 11:32 am, edited 2 times in total.
- Ana de Sousa
- Posts: 67
- Joined: Thu Apr 07, 2016 4:21 pm
Re: Grouping muscle activations
Hi Pasha, thanks a lot for the response!
I need to understand more about OpenSim's structure to implement this properly. For instance, I'm a bit confused about the roles of muscles, actuators, and controllers in the framework.
I was playing around with your code today:
1. I cannot seem to be able to use Controller directly, so I tried ControllerSet() instead.
2. It seems that setActuator does not exist, so I tried setActuators.
The problem is that setActuators() only works with actuators, not muscles. With this code:
I get this error:
What am I missing or doing wrong here?
I need to understand more about OpenSim's structure to implement this properly. For instance, I'm a bit confused about the roles of muscles, actuators, and controllers in the framework.
I was playing around with your code today:
1. I cannot seem to be able to use Controller directly, so I tried ControllerSet() instead.
2. It seems that setActuator does not exist, so I tried setActuators.
The problem is that setActuators() only works with actuators, not muscles. With this code:
Code: Select all
muscle_one = model.getMuscles().get("rect_fem_r")
mycontroller = osim.ControllerSet()
mycontroller.setActuators(muscle_one)
Code: Select all
TypeError: in method 'ControllerSet_setActuators', argument 2 of type 'OpenSim::Set< OpenSim::Actuator > &'
- Pasha van Bijlert
- Posts: 227
- Joined: Sun May 10, 2020 3:15 am
Re: Grouping muscle activations
HI Ana,
Looking at the documentation again, it looks like it should have been addActuator(muscle_one) etc, not setActuator (which doesn't exist, like you said).
Muscles are actuators (in the sense that they are a subclass of actuators). The error you're getting is because setActuators requires an ActuatorSet (=a set of more than one actuator) as an input, and instead you were providing it with a single Actuator (muscle) as an input.
Bear in mind, I've never manually added controllers like this, there may be other things you have to do like bounding the control values between 0 - 1 in the problem.
Cheers,
Pasha
Looking at the documentation again, it looks like it should have been addActuator(muscle_one) etc, not setActuator (which doesn't exist, like you said).
Code: Select all
mycontroller = Controller()
mycontroller.addActuator(muscle_one)
mycontroller.addActuator(muscle_two)
model.addController(mycontroller)
model.initSystem()
Bear in mind, I've never manually added controllers like this, there may be other things you have to do like bounding the control values between 0 - 1 in the problem.
Cheers,
Pasha
- Ana de Sousa
- Posts: 67
- Joined: Thu Apr 07, 2016 4:21 pm
Re: Grouping muscle activations
Hey Pasha, thanks again for your suggestions!
Unfortunately, I can't create a Controller because it's an abstract class and can't be instantiated. I tried a quick workaround using PrescribedController:
However, it turns out that MocoCasADiSolver doesn't support models with controllers.
Do you have any other suggestions?
Unfortunately, I can't create a Controller because it's an abstract class and can't be instantiated. I tried a quick workaround using PrescribedController:
Code: Select all
my_controller = osim.PrescribedController()
my_controller.addActuator(muscle_rect_fem_r)
model.addController(my_controller)
Do you have any other suggestions?
- Nicholas Bianco
- Posts: 1045
- Joined: Thu Oct 04, 2012 8:09 pm
Re: Grouping muscle activations
Hi Ana,
What you looking for is a "synergy" controller. Luckily for you, this has just been added via OpenSim 4.5.1! I'll probably make a forum-wide post about the update soon, but if you subscribe to the OpenSim announcement mailing list then you should have already received an email about it.
Best,
Nick
What you looking for is a "synergy" controller. Luckily for you, this has just been added via OpenSim 4.5.1! I'll probably make a forum-wide post about the update soon, but if you subscribe to the OpenSim announcement mailing list then you should have already received an email about it.
Best,
Nick
- Pasha van Bijlert
- Posts: 227
- Joined: Sun May 10, 2020 3:15 am
Re: Grouping muscle activations
Hi Nick,
What would adding a regular Controller with multiple muscles added to it as I described achieve?
Cheers,
Pasha
What would adding a regular Controller with multiple muscles added to it as I described achieve?
Cheers,
Pasha
- Nicholas Bianco
- Posts: 1045
- Joined: Thu Oct 04, 2012 8:09 pm
Re: Grouping muscle activations
Hi Pasha,
The "Controller" class is an abstract class, so you can't use it directly in a model. You can use classes that derive from Controller (e.g., PrescribedController) since these classes implement the Controller interface.
If you add a controller per Ana's code snippet,
then the control for "muscle_rect_fem_r" will be removed from the MocoProblem and prescribed directly based on the PrescribedController.
Best,
Nick
The "Controller" class is an abstract class, so you can't use it directly in a model. You can use classes that derive from Controller (e.g., PrescribedController) since these classes implement the Controller interface.
If you add a controller per Ana's code snippet,
Code: Select all
my_controller = osim.PrescribedController()
my_controller.addActuator(muscle_rect_fem_r)
model.addController(my_controller)
Best,
Nick
- Pasha van Bijlert
- Posts: 227
- Joined: Sun May 10, 2020 3:15 am
Re: Grouping muscle activations
Ah got it, thanks! I'll edit my previous post to point out I was wrong.
Cheers,
Pasha
Cheers,
Pasha
- Ana de Sousa
- Posts: 67
- Joined: Thu Apr 07, 2016 4:21 pm
Re: Grouping muscle activations
Hi Nick!
Thank you so much for your response, it couldn't have come at a better time for my project! The recent release was exactly what I needed.
I just updated my Conda environment to the latest version and downloaded the new Moco inverse example from the official OpenSim documentation. Then, I adapted the code to fit the specific needs of my application.
To make things more manageable, I created a more straightforward example (for my application), which you can check out here: https://github.com/anacsousa1/moco-tuto ... rs.py#L300
In this example, I define muscle groups that work together as synergies.
Here's a quick summary of what I did:
First, I defined the muscle groups (I can also exclude some later):
I built a list to store the names of all muscles in the model and then exported the control signals (muscle activations). Afterwards, I looped over each muscle group, combining them with the controllers.
Later, one of the more challenging parts was applying Non-negative Matrix Factorization (NNMF) to generate the synergies. This is new to me, but I think I got somewhere interesting. My approach is a bit different from the official example because I used muscle groups and excluded certain groups from activation:
Since it's not easy to share code in full here, I've explained the process in greater detail here: https://anacsousa.notion.site/Moco-inve ... 6ba?pvs=74
I hope this gives you a better idea of how I implemented these if others need it.
Anyway, what I don't understand is that after updating to OpenSim 4.5.1, I've started getting the following error, even in examples that don't use controllers:
While this error doesn't seem to affect the optimisation process (at least from what I can tell), I'm curious why it's happening.
Thanks again for your help!
Thank you so much for your response, it couldn't have come at a better time for my project! The recent release was exactly what I needed.
I just updated my Conda environment to the latest version and downloaded the new Moco inverse example from the official OpenSim documentation. Then, I adapted the code to fit the specific needs of my application.
To make things more manageable, I created a more straightforward example (for my application), which you can check out here: https://github.com/anacsousa1/moco-tuto ... rs.py#L300
In this example, I define muscle groups that work together as synergies.
Here's a quick summary of what I did:
First, I defined the muscle groups (I can also exclude some later):
Code: Select all
muscle_groups = {
"hamst": ["/forceset/bifemlh_r", "/forceset/bifemsh_r"],
"glut": ["/forceset/glut_max2_r"],
"quads": ["/forceset/rect_fem_r", "/forceset/vas_int_r"],
"psoas": ["/forceset/psoas_r"],
"gast": ["/forceset/med_gas_r", "/forceset/soleus_r"],
"tibialis": ["/forceset/tib_ant_r"],
}
# Specify the muscle groups to be excluded from activation
exclude_groups = {"glut", "tibialis", "psoas"}
# The number of synergies is equal to the number of muscle groups.
numSynergies = len(muscle_groups)
I built a list to store the names of all muscles in the model and then exported the control signals (muscle activations). Afterwards, I looped over each muscle group, combining them with the controllers.
Later, one of the more challenging parts was applying Non-negative Matrix Factorization (NNMF) to generate the synergies. This is new to me, but I think I got somewhere interesting. My approach is a bit different from the official example because I used muscle groups and excluded certain groups from activation:
Code: Select all
# Apply Non-negative Matrix Factorization (NNMF) to the aggregated control data to find synergies.
nmf = NMF(n_components=numSynergies, init='random', random_state=0)
W = nmf.fit_transform(A) # Synergy excitations over time
H = nmf.components_ # Synergy vectors that combine muscle controls
# Scale the synergies for normalisation.
scaleVec = 0.5 * np.ones(H.shape[1])
for i in range(numSynergies):
scale_r = np.linalg.norm(scaleVec) / np.linalg.norm(H[i, :])
H[i, :] *= scale_r
W[:, i] /= scale_r
# Create a SynergyController to control the muscles based on synergies.
right_controller = osim.SynergyController()
right_controller.setName("synergy_controller_right_leg")
# Link each muscle in the right leg to the SynergyController.
for name in right_control_names:
right_controller.addActuator(osim.Muscle.safeDownCast(model.getComponent(name)))
# Expand synergy vectors to match the number of actuators
for i in range(numSynergies):
synergy_vector = osim.Vector(len(right_control_names), 0.0)
idx = 0
for group_name, muscle_list in muscle_groups.items():
if group_name in exclude_groups:
# Zero out the synergy vector for excluded groups
continue
for muscle in muscle_list:
if muscle in right_control_names:
synergy_vector.set(right_control_names.index(muscle), H[i, idx])
idx += 1
right_controller.addSynergyVector(synergy_vector)
I hope this gives you a better idea of how I implemented these if others need it.
Anyway, what I don't understand is that after updating to OpenSim 4.5.1, I've started getting the following error, even in examples that don't use controllers:
Code: Select all
[error] Model unable to assemble: SimTK Exception thrown at Optimizer.h:133:
Value out of range in OptimizerSystem Constructor: expected 1 <= number of parameters <= 2.14748e+09 but number of parameters=0..Model relaxing constraints and trying again.
While this error doesn't seem to affect the optimisation process (at least from what I can tell), I'm curious why it's happening.
Thanks again for your help!