Changing force plates in Foot MoCAP model

Hi Søren, thank you for your reply,

Does the timestep refer the the time of the starting frame, or the duration of the tStart to tEnd divided by the number of steps?

I see the repo model uses tStart as the initial heel contact at the starting point and runs for 685ms, but the pressure data has only 650ms of data.

Also, in the Environment.any…

#if FirstRun == 1
    AnyFunEx PressureFun = 
    {
      AnyVector Return = .FootPressureNodeCoeffVecInitial;
      AnyFunExMonoPy PressureFunction =
      {
        ModuleFile = "Final2.py";
        ArgList = 
        {
          AnyMatrix Mat = ...FootPressureNodeMatInitial;
          AnyFloat Time = 0;
          AnyString path = " ";
        };
      };
    };
  
    AnyVector FootPressureNodeCoeffVec =  PressureFun(FootPressureNodeMat, Main.Studies.InverseDynamicStudy.t - Main.Studies.InverseDynamicStudy.tStart, Main.FootPrintFolderPath + "/Input/" + Main.TrialSpecificData.NameOfFile + "-PressureData.txt");
   

what does the Time in ArgList refer to? does it allow me to off set the start time of the pressure data by say 0.001 or 1 ms so it reads 11ms 21ms 31ms instead of 10ms 20ms 30ms?

Lastly, for some of the vectors/nodes in anybody - they seem to have an offset in the python scripting which is consistent throughout which effects location of, for example the StrikeNode in the Environment.any file

The offset is either

0.049+(Rb+0.5+i)*CellL

or

0.4 -0.049 -64*CellW + (Rl+0.5+i)*CellW

Which in final2.py is also applied to

cell0 = [0.4 -0.049 -64*CellW + (Rl+0.5)*CellW, 0.049 + (Rb+0.5)*CellL, 0] #theoretical value

In the Environment.any I’ve rotated each of the location nodes, as the foot location was facing the wrong way on the platform like so:

 AnyFolder &GlobalRef = .GlobalRef;
    GlobalRef = {
      AnyRefNode StrikeFootRef = {
        sRel = {..FootLocationCooVec[0] -0.65, ..FootLocationCooVec[1] -0.88, 0}*RotMat(-pi,y)*RotMat(-pi,x);
        AnyDrawNode Draw = {ScaleXYZ=0.005*{1,1,1}; RGB={1,0,0};};
      };

As these foot location nodes have the offset of



 #First time step CoP, correspond to heel strike location
    #******************************************************* Step1Vec = Data[0][1:]
    
    LongWeightedStep1Vec = []
    for i in range(Rh):
        LongCoo = 0.049+(Rb+0.5+i)*CellL
        for j in range(Rw):
            LongWeightedStep1Vec.append(Step1Vec[i*Rw+j]*LongCoo)
    
    LongCooStrike = sum(LongWeightedStep1Vec)/sum(Step1Vec)
    
    LatWeightedStep1Vec = []
    for i in range(Rw):
        LatCoo = 0.4 -0.049 -64*CellW + (Rl+0.5+i)*CellW
        for j in range(Rh):
            LatWeightedStep1Vec.append(Step1Vec[j*Rw+i]*LatCoo)
    
    LatCooStrike = sum(LatWeightedStep1Vec)/sum(Step1Vec)

So i was wondering how i can apply the same logic to

Final2.py…



 # Create the matrix of pressure cells global coordinates (size [i][3])
    #*********************************************************************
    cell = []
    cell0 = [0.4 -0.049 -64*CellW + (Rl+0.5)*CellW, 0.049 + (Rb+0.5)*CellL, 0] #theoretical value
    #cell0 = [0.4 -0.049 -64*CellW + (Rl+0.5)*CellW, 0.049 -0.01 + (Rb+0.5)*CellL, 0] #with offset
    for h in range(Rh):
        for w in range(Rw):
            cell.append([cell0[0] + CellW*w, cell0[1] + CellL*h, cell0[2]])

and rotate/translate this to match the rotation and translation i’ve applied to the location nodes, but I am not quite sure how.

I am able to run a complete inverse dynamics simulation, but again the pressure nodes are not visible. to do this I change:



 # Create the matrix of pressure cells global coordinates (size [i][3])
    #*********************************************************************
    cell = []
    cell0 = [0.4 -0.049 -64*CellW + (Rl+0.5)*CellW, 0.049 + (Rb+0.5)*CellL, 0] #theoretical value
    #cell0 = [0.4 -0.049 -64*CellW + (Rl+0.5)*CellW, 0.049 -0.01 + (Rb+0.5)*CellL, 0] #with offset
    for h in range(Rh):
        for w in range(Rw):
            cell.append([cell0[0] -0.2 + CellW*w, cell0[1] - 0.3 + CellL*h, cell0[2]])

Thank you very much for your time Søren, I feel like I am getting close to a working model…

Best Regards,
Zach

Hi Zach,

As i recall it the timestep is the time obtained from from the study, so the study ask the python code to return the loads associated with the current time value of the study.

In the experimental data there could be differences in the starting time between pressure and mocap. The pressure data always has first data at heel strike so there could sometimes be a need to adjust the starting time to macth the data.

The

AnyFloat Time =0;

is the initial value of the timestep argument it needs to be provided… it could also have been written as

AnyFloat timestep  =0;

it would have been the same.

The demo model has the following time settings:

C3D data: starts at 0.85 to 1.62 sec (0.77 sec total)
Pressure data: starts at 0 and goes to 0.65 sec (0.65 sec total)
Study: starts art 0.955 and goes to 1.6 sec (0.645 total ) (see the tstart and tend in modeltree of the loaded model)

The starting time of the study has been adjusted so that the analysis starts at heel strike, since this is when pressure data is availble… and it has been cut in the end since swing phase has no pressure.

The offsets you find in the files relates to the location of the pressure plate in the lab so it needs to be changed accordingly to match your settings.

I am not eaxtly sure how to do this in your case but i would think changing the offsets values in the same way in the .any and .py files should have been enough. Possibly it could be easier to make it in the same way in .any and .py if you avoided using the rotmat it is basically a grid layout, which needs to be located correctly :wink:

Best regards
Søren

Hi Soren, thank you very much for your knowledge.

It seems I have got the model reading the Pressure data per time step in the inverse dynamic study, with only one slight problem…

The pressure data is being distributed backwards in that the posterior foot strike (from the pressure file) is starting at the toe and then the toe off (from the pressure file) is ending at the foot.

When I invert the file so that the heel is at the bottom of the pressure matrix this does not work.

I was wondering if you could maybe explain how to rotate the C3D and force platform so that they are pointing in the same direction as the pressure matrix?

Or how to rotate the pressure matrix in the AMS? Although, I have wrestled with this for some months now.

This is obviously not ideal, but will be an interim solution until I am able to collect a new trial with GM marker set up in the gait lab.

Many Thanks,
Zach

Hi Zach,

To change the layout of the force plate you will need to alter the forceplate class and rotate the plate in there by changing the corner nodes.

The maker data is more difficult to change since the marker driver is essentially a distance measure between a local coordinate system in the marker and the 3d trajectory data. So the only option would be to actually change the c3d file data which is not so easy i guess.

To rotate the pressure plate you will need to modify the .py files as you already know.

This will have to be done in the Final2.py in these lines

# Create the matrix of pressure cells global coordinates (size [i][3])
    #*********************************************************************
    cell = []
    cell0 = [0.4 -0.049 -64*CellW + (Rl+0.5)*CellW, 0.049 + (Rb+0.5)*CellL, 0] #theoretical value
    #cell0 = [0.4 -0.049 -64*CellW + (Rl+0.5)*CellW, 0.049 -0.01 + (Rb+0.5)*CellL, 0] #with offset
    for h in range(Rh):
        for w in range(Rw):
            cell.append([cell0[0] + CellW*w, cell0[1] + CellL*h, cell0[2]])
    
    #print cell

Maybe in your case it should be


    
           cell.append([cell0[0] + CellW*w, cell0[1] - CellL*h, cell0[2]])

I can not remember all details anymore but there could other .py files where the same change would have to be done.

Best regards
Søren

Hi Søren,
It’s been a while since we last spoke.

After your reccomendations and insights to study times, I’ve successfully run the GM model scaling most of the bones individually (from MR images segmented with a shape model), using the GM marker protocol. Also i’ve used an AMTI force plate and also using EMED pressure inputs (this required me to construct a Type2Pressure plate + a few extra python scripts to fomat data from EMED to a text file readable by Anybody… Because the GM model didn’t have enough python scripts already…

I believe the combination of these three inputs is different from the original model - which scales based on surface scans, collects gait and force information using a kistler platform and uses RSScan for the pressure inputs as a Type3Pressure plate

One question I have is on the sampling rates of the force and pressure data. Could you maybe explain how the anybody system accounts for differences in sampling rates between the pressure data and the gait/force data? If it does at all?

Thank you for your time and reccomendations Søren, the model development seems to be progressing well.

Thanks,
Zach

Hi Zach,

Sounds really good that the modelling is progressing well, i know this is not an easy task.

Concerning the sampling rate, this is handled using interpolation functions all data are read into interpolation functions and when AnyBody is running it can be done with many or few nStep’s, independently of the sampling freq.

Best regards
Søren

Hi Søren,

It has been a challenging model to work with and I have a deep appreciation for the work that went into this model initially. It’s been fun integrating the inputs into the model - even with the accompanying headaches.

Do you mean that the force data and pressure data are interpolated? And when it is interpolated, how many points are generated? Does the model control for upsampling and /or downsampling?

I hope this makes sense.

Many Thanks,
Zach

Hi Zach,

In all AnyBody models the data are being used though interpolation functions.

Normally the interpolation functions will be constructed using the full number of data points recorded.

When running a study you will set the nStep you can choose to use the full number of nStep fromt he recoding, but normally we will choose to downsample the by using a lower nStep value. Note that the time steps in AnyBody are independent so the results for a given time value does not depend on the previous steps, though there is lower limit of nStep if the motion is large… because the previous positions are used as initial guesses for the starting position.

Best regards
Søren

Dear Zach and Soren,

thanks to your conversation I was able to solve many problems I had on the way to make my GM foot model run. I am already able to load my model with #define InverseDynamicModel and #define FirstRun switched to 1. However I still need to work on reading pressure data. I was trying to analyze Footlocation and Final2 python files but there are some places when I am not sure how to change the code to work nicely with my data. The biggest concerns are about the line

LongCoo = 0.049+(Rb+0.5+i)CellL
and
LatCoo = 0.4 -0.049 -64
CellW + (Rl+0.5+i)*CellW

I know the numbers in expressions concern foot location offset but do you have any hints how to set this numbers no work on my model? Does any number mean something exactly?

Hi Claire,

I will try explain though it is far away in mind …

The data expected is not all cells but a square, which covers the footprint…

The location of this square wrt to the full plate is given by Rb which stands for rectangle bottom, and Rl which is retangle left. these number are cell numbers… so an integer counted from the lower left corner of the plate.

CellW and CellL are cell dimensions.

The other values are some numbers which must be related to the specific setup in the lab, those you most likely need to change.

It is important that these locations are correct so i would try to visualize the data you create inside AnyBody together with the foot to see if it makes sense.

Hope it helped.

Best regards
Søren

Dear Soren,

thank you a lot for quick reply.

My biggest concerns were about the numbers 0.049, 0.4, -64. Do you know the meaning of them?

Best regards,
Barbara

Hi,
It's good to see that you are making progress. I'm glad the thread has been helpful to you, as it has been to me. Are you working at an academic institution?

LongCoo and LatCoo correspond with the StrikeFootRef.

When you say:

LongCoo = 0.049+(Rb+0.5+i)CellL
and
LatCoo = 0.4 -0.049 -64
CellW + (Rl+0.5+i)*CellW

Am I right in saying that you are referring to:

FootLocation4.py

...

    #First time step CoP, correspond to heel strike location
    #*******************************************************
    Step1Vec = Data[0][1:]
    
    LongWeightedStep1Vec = []
    for i in range(Rh):
        LongCoo = 0.049+(Rb+0.5+i)*CellL
        #print(LongCoo)
        for j in range(Rw):
            LongWeightedStep1Vec.append(Step1Vec[i*Rw+j]*LongCoo)
            #LongWeightedStep1Vec.append(Step1Vec[i*Rw+j]*LongCoo)

LongCooStrike = sum(LongWeightedStep1Vec)/sum(Step1Vec)
    #print(LongCooStrike)
    
    LatWeightedStep1Vec = []
    for i in range(Rw):
        LatCoo = 0.4 -0.049 -64*CellW + (Rl+0.5+i)*CellW
        for j in range(Rh):
            LatWeightedStep1Vec.append(Step1Vec[j*Rw+i]*LatCoo)
    
    LatCooStrike = sum(LatWeightedStep1Vec)/sum(Step1Vec)
    
    #print(Step1Vec)


If so, Step1Vec refers to the first frame in your pressure file. If you remove the pound sign (hashtag) from:

    #First time step CoP, correspond to heel strike location
    #*******************************************************
   Step1Vec = Data[0][1:]
    
    LongWeightedStep1Vec = []
    for i in range(Rh):
        LongCoo = 0.049+(Rb+0.5+i)*CellL
        #print(LongCoo)
        for j in range(Rw):
            LongWeightedStep1Vec.append(Step1Vec[i*Rw+j]*LongCoo)
            #LongWeightedStep1Vec.append(Step1Vec[i*Rw+j]*LongCoo)

LongCooStrike = sum(LongWeightedStep1Vec)/sum(Step1Vec)
    #print(LongCooStrike)
    
    LatWeightedStep1Vec = []
    for i in range(Rw):
        LatCoo = 0.4 -0.049 -64*CellW + (Rl+0.5+i)*CellW
        for j in range(Rh):
            LatWeightedStep1Vec.append(Step1Vec[j*Rw+i]*LatCoo)
    
    LatCooStrike = sum(LatWeightedStep1Vec)/sum(Step1Vec)
    
    print(Step1Vec)


This will print out the first frame directly from pressure.txt file on the python interpreter you are using. E.g. not the AnyBody system, but a separate python debugger. For me, this is really the first check to ensure that your pressure.txt data has been formatted correctly.

Also, when you run the model can you see the pressure nodes (below) on the force platform?

Environment.any:

     AnyFolder &GlobalRef = .GlobalRef;
    GlobalRef = {
      AnyRefNode StrikeFootRef = {
        sRel = {..FootLocationCooVec[0] , ..FootLocationCooVec[1], 0};
        AnyDrawNode Draw = {ScaleXYZ=0.005*{1,1,1}; RGB={1,0,0};};
      };
      AnyRefNode PosteriorFootRef = {
        sRel = {..FootLocationCooVec[2] , ..FootLocationCooVec[3], 0};
        AnyDrawNode Draw = {ScaleXYZ=0.005*{1,1,1}; RGB={0,1,0};};
      };
      AnyRefNode AnteriorMedialFootRef = {
        sRel = {..FootLocationCooVec[4] , ..FootLocationCooVec[5], 0};
        AnyDrawNode Draw = {ScaleXYZ=0.005*{1,1,1}; RGB={0,0,1};};
      };
      AnyRefNode AnteriorLateralFootRef = {
        sRel = {..FootLocationCooVec[6] , ..FootLocationCooVec[7] , 0};
        AnyDrawNode Draw = {ScaleXYZ=0.005*{1,1,1}; RGB={1,1,0};};
      };
      AnyRefNode DistalFootRef = {
        sRel = {..FootLocationCooVec[8], ..FootLocationCooVec[9], 0};
        AnyDrawNode Draw = {ScaleXYZ=0.005*{1,1,1}; RGB={0,0,0};};
      };
      AnyRefNode DistalPhal1Ref = {
        sRel = {..FootLocationCooVec[10] , ..FootLocationCooVec[11] , 0};
        AnyDrawNode Draw = {ScaleXYZ=0.005*{1,1,1}; RGB={0,0,0};};
      };
      AnyRefNode DistalPhal2Ref = {
        sRel = {..FootLocationCooVec[12] ..FootLocationCooVec[13], 0};
        AnyDrawNode Draw = {ScaleXYZ=0.005*{1,1,1}; RGB={0,0,0};};
      };

As Søren suggested these are integers reading from the left corner, but depending on the format of your pressure data, these could be floating point numbers, which might need changing in the python scripts when reading the in the pressure data.

Hope this helps,

Thanks,
Zach

Dear Zach and Soren,

thank you a lot for your answers.

Soren, do you know what’s the meaning of this “other values” you have mentioned? Acctualy I was able to deduct what does parameter Rb, Rv, CellW, CellL etc. mean but numbers 0.4, 0.049 and 64 are not described. Do they respond to any special dimensions in the laboratory?

Zach, I am PhD student at Warsaw University of Technology. Thank you a lot for nice description of the script. I was debbuging final2.py and FootLocation4.py and I was able to check If my data inputs properly. My biggest problem right now Is adjusting the numbers in the expression below and in other expressions where they occure.

LongCoo = 0.049+(Rb+0.5+i)CellL
and
LatCoo = 0.4 -0.049 -64
CellW + (Rl+0.5+i)*CellW

Could you tell me more how did you adjust this expressions to fit your laboratory setup?

Best regards,
Barbara

Hi Barbara,

The numbers you mention (to my knowledge) represent a systematic offset that is specific to the labratory. Change the offset to alter the location. This requires a lot of experimentation, at least for me anyway.
Zach

Dear Zach, thanks for your sugestions. Pressure nodes after loading my model are not on the forceplate, because I havn't adjusted the offset yet.
I have hovewer another worry. I think some of my nodes are not located correctly. For example I have problem with AnteriorMedialFootRef. The x coordinate was very high (ca -93) what is rather wrong (the node was to far from global ref to appear in model view). When I have looked into FootLocation code I found if function which may multiply the result of LatCoo by 1000 what must have happened in my case, and caused the parameter to be wrong calculated. Did you need to change the code of FootLocation to calculate Pressure Nodes correctly, or you have only changed the offset and rotation?

And have you changed the offset by proposing some changes and than looking at the effect on visualisation, or you had different method to do so?

One more time thank you a lot for your help!

Best regards,
Barbara

Hi Barbra,

If you are referring to specific code, perhaps it would be more beneficial to include it in your post.

Your issue could mean that the data is not being read in from the pressure.txt matrix correctly in which case I'd say it's a formatting issue. Depending on how you have formatted your pressure matrix, you can either change the offset of the pressure matrix or change the python script. I found the current repositry pressure set-up to be coincidental with the direction of the repositry mocap data - so you need to think about how your data relates and adjust accordingly.

you can see all the footprint variables if you run footlocation in a python interpreter:

FootLocation.py...

CooVec = [LatCooStrike,LongCooStrike,
    LatCooPosterior,LongCooPosterior,
    LatCooAnteriorMedial,LongCooAnteriorMedial,
    LatCooAnteriorLateral,LongCooAnteriorLateral,
    LatCooDistal,LongCooDistal,
    LatCooPhal1,LongCooPhal1,
    LatCooPhal2,LongCooPhal2]
    
    CooVecTuple = tuple(CooVec)

...

Check your variable/tuple list to see if it is formatted like the repositry model, if it is not then adjust the python script to make it concurrent throughout the model.
Zach

Dear Zach,
sorry for late reply but I wanted to check a couple of things in my model before I answer.

I was debuging final2 and footlocation python files and I know that my pressure data is read correctly. The problem Is that some of the functions inside final2.py contain if statements that cause FootPressureNodeCoeffVec to be filled with 0. In the result of that the force applied to the nodes is multiplied by 0 what in effect cause that 0 force is applied to the model. That is good reason why Inverse Dynamics fails with the error: [COLOR=darkred]ERROR(OBJ.MCH.MUS4) : D:/A…y/AMMR/A…n/Beta/M…a/M…l/InverseDynamics.any(22) : InverseDynamicStudy.InverseDynamics : Muscle recruitment solver : solver aborted due to singular KKT matrix

I was trying a lot to adjust the offset properly so that I get some valus in my [/COLOR][COLOR=Black]FootPressureNodeCoeffVec but unfortunately without sucess. I have decided to write my own function, which will create my [/COLOR]FootPressureNodeCoeffVec. Because I am much better in Matlab than in Python (I have started using Python when I had to analyze final2.py and footlocation.py) I have created mat file in matlab which is later imported into python. FootPressureNodeCoeffVec should be loaded to Anybody depending on the timestep. Now I can see force applied to the nodes but unfortunately the error:

ERROR(OBJ.MCH.MUS4) : D:/A…y/AMMR/A…n/Beta/M…a/M…l/InverseDynamics.any(22) : InverseDynamicStudy.InverseDynamics : Muscle recruitment solver : solver aborted due to singular KKT matrix

appeared again and I am completely lost what to do next. Do you have any suggestions?

I have attached my model below

Hello, sorry for the late reply

I’ve had a look at your model and have a few ideas that might be useful.

  1. Your MoCap Data starts around 3.82 you try could reframing from 0 this could make the numbers easier to deal with.

  2. Ensure the length of your mocap study (800ms) is same length as your pressure data input (817.38ms).

  3. In the footlocation.py (or your equivalent) I see your CooVec Tuple output to be

(0.7108815789473686, 1.782934210526317, 0.7101500000000001, 1.8015499999999984, 0.69925, 1.96725, 0.80975, 1.94175, 0.7077500000000001, 2.0267500000000003, 0.7188809523809522, 2.008333333333333, 0.765125, 2.0012499999999998)

these numbers correspond to the LongCooStrike, LatCooStrike etc and may/may not make sense based on your labratory set up. I would check these numbers against the original GM model.

As the issue seems to be the interpolation of the data (When I run the model), I would start by ensuring the length of the study is exactly the same as the length of the pressure data e.g. your study should be 817.38ms.

Hope this helps.
Zach

The attached image, Looks as if you need to relocate your foot location nodes as well as the above suggestions about the length of study.

I also changed

Environment.any from

AnyFolder FootLocation = {

AnyFunEx FootLocationFun = 
{
  AnyVector Return = {0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  AnyFunExMonoPy LocationFunction =
  {
    ModuleFile = "FootLocation4.py";
    ArgList = 
    {
      AnyString path = " ";
    };
  };
};

// AnyVector FootLocationCooVec = FootLocationFun(Main.FootPrintFolderPath + “/Input/” +Main.TrialSpecificData.NameOfFile+ “-PressureData.txt”);
AnyVector FootLocationCooVec = FootLocationFun(Main.FootPrintFolderPath + “/Input/stopaP.txt”);

to

AnyFolder FootLocation = {

AnyFunEx FootLocationFun = 
{
  AnyVector Return = {0,0,0,0,0,0,0,0,0,0,0,0,0,0};
  AnyFunExMonoPy LocationFunction =
  {
    ModuleFile = "FootLocation4.py";
    ArgList = 
    {
      AnyString path = " ";
    };
  };
};

// AnyVector FootLocationCooVec = FootLocationFun(Main.FootPrintFolderPath + “/Input/” +Main.TrialSpecificData.NameOfFile+ “-PressureData.txt”);
AnyVector FootLocationCooVec = FootLocationFun(Main.FootPrintFolderPath + “/Input/ciesnienie_Stare/stopaP.txt”);

As i thought that was what you were trying to do by using the matlab method of converting to a txt file for the Anybody script.

Try also changing foot location.py to

CellW = 0.005
CellL = 0.00762

offset_wys = -1.47;
offset_szer = 0.45;

Also, just a note, the scaling on your distal foot does not seem to look typical, but of course I could be wrong.

Thanks,
Zach

Dear Zach,

thank you a lot for deeply analysing my case. I must say that I am a bit confused right now.

I will start with scalling isue. As you have noticed the calcaneus bone seems to be too narrow… I have no idea why… I have analysed scaling in MeshLab one more time but all points seem to be marked properly (anyscrip2.png, anyscript3.png) and they are also imported into Anybody without problems… Do you think the orientation of the model in space may influence scaling?

I have also checked all other scaling parameters in AnyManRBF and I havn’t found anything what could cause the calcaneus bone to be not scaled properly…

Concerning pressure data:
That’s wird that when you open model on your computer foot location points and coordinates are so far from the model. I have different view of this:

Concerning timing:
because final2.py was not giving me good results concerning pressure distribution I have decided to do this on my own in Matlab. I have devided pressure mat into four regions and I assigned pressure value coefficient to the corresponding node. In this case I used wczytawsp function instead of final2py. This function is only to read the matrix created in matlab in the specific time. I I have interpolated newly created pressure data in Matlab so that it maches simulation time in AnyBody. Where have you checked that the times do not mach? Maybe I missed something?