Force dependent kinematics - ligament calibration

Dear AnyBody community
As part of the implementation of the Force Dependent Kinematics workflow for the knee joint using a novel dataset, I calibrated ligament properties as follows:

// Ligament calibration
AnyDesEvalStudy EvaluateLigamentLength = {
  AnyKinStudy InitStudyForLigamentLengthEvaluation = 
  {
    Replay.OPERATION_DISPLAY_PRIORITY(PriorityLow);
    InitialConditions.OPERATION_DISPLAY_PRIORITY(PriorityLow);
    Kinematics.OPERATION_DISPLAY_PRIORITY(PriorityLow);
    
    AnyComponentDefinition obj = {};
    Gravity = {0,0,0};/// Not relevant for AnyKinStudy
    AnyFolder& BodyModel = Main.HumanModel.BodyModelWithoutMuscles ; 
    AnyFolder &EnvironmentModel = Main.EnvironmentModel;
    
    AnyFolder ModelEnvironmentConnection = 
    {    
      //#include "Inputs_InitStudy.any" 
      AnyFolder &MarkerDrivers = Main.ModelSetup.MocapDrivers;
      AnyFolder &ExtraDrivers = Main.ModelSetup.MocapExtraDrivers;
    };
    
    AnyFolder& Anthropometrics = Main.HumanModel.Anthropometrics;
    #include "KinematicStudyForLigCalibration.any"   
    
    AnyFolder OutputFunctions_Length = {};   
    
  };

With KinematicStudyForLigamentCalibration:


InitialConditions = 
{
  SmallStepAssumptionOnOff = On;
  PosAnalysisOnlyOnOff = On;
  KinematicTol=1e-5; 
  SolverType = KinSolOverDeterminate;
  PreOperation = 
  { 
    AnyOperation &SetView = Main.ModelSetup.Views.SetViewMacros.KinematicView; 
  };
};

Kinematics = 
{
  SmallStepAssumptionOnOff = On;
  PosAnalysisOnlyOnOff = On;
  KinematicTol=1e-5; 
  SolverType = KinSolOverDeterminate;
};


 // Define statements allow us to overide tEnd/tStart/nStep to 
 // when calling AnyBody from external programs.  
 #ifndef N_STEP_PARAM_OPT
 #ifndef N_STEP 
   #define N_STEP_PARAM_OPT iffun(eqfun(Main.ModelSetup.TrialSpecificData.nStep,1),1, floor(max({20, Main.ModelSetup.TrialSpecificData.nStep/6})))
 #else
   #define N_STEP_PARAM_OPT N_STEP
 #endif
 #endif
 
 #ifndef T_END
 #define T_END Main.ModelSetup.TrialSpecificData.tEnd
 #endif
 
 #ifndef T_START
 #define T_START Main.ModelSetup.TrialSpecificData.tStart
 #endif
 
 tStart = T_START;
 tEnd = T_END;
 nStep = N_STEP_PARAM_OPT;

Ligament calibration was currently performed in an extended knee position without contact between the femoral and tibial cartilage. This resulted in an overestimation of initial ligament lengths. Consequently, the inverse dynamics analysis exhibited larger-than-expected joint mobility, which was partially mitigated by scaling all initial ligament lengths by 0.8.

image

To perform ligament calibration in a more realistic position, I included a separate driver for the right and left knee (defined in the “Inputs_InitStudy.any" file) to artificially create a contact between the two cartilage layers. Please find the example of the right knee below:

AnyFolder Drivers_Init = 
{
    AnyKinEqSimpleDriver Knee_R = 
  {
    AnyKinLinear lin = 
    {
      AnyRefFrame & ref0 = Main.HumanModel.BodyModel.Right.Leg.Seg.Shank.KneeJoint;
      AnyRefFrame & ref1 = Main.HumanModel.BodyModel.Right.Leg.Seg.Thigh.KneeJoint;       
      Ref = 0;
    };
    AnyKinRotational rot = 
    {
      AnyRefFrame & ref0 = Main.HumanModel.BodyModel.Right.Leg.Seg.Shank.KneeJoint;
      AnyRefFrame & ref1 = Main.HumanModel.BodyModel.Right.Leg.Seg.Thigh.KneeJoint; 
      Type = PlanarAngles;
    };
    DriverPos = {0, -3*10^(-3), 0,  0, 2/180*pi, 0};
    DriverVel = {0, 0, 0, 0, 0, 0};
    Reaction.Type = {Off, Off, Off, Off, Off, Off};
    CType = {Hard,Hard,Hard,Hard,Hard,Hard};
  };
};

Although the ligament calibration functions perfectly without adding these two drivers. The following error occurs two times when the drivers are added:

Kinematic solver fallback: Restarting solution of kinematic optimality conditions and hard constraints.Kinematic solver fallback: Restarting solution of kinematic optimality conditions and hard constraints.Kinematic solver fallback: Restarting solution of kinematic optimality conditions and hard constraints.Kinematic solver fallback: Restarting solution of kinematic optimality conditions and hard constraints.Kinematic solver fallback: Restarting solution of kinematic optimality conditions and hard constraints.Kinematic solver fallback: Restarting solution of kinematic optimality conditions and hard constraints.Kinematic solver fallback: Restarting solution of kinematic optimality conditions and hard constraints.Kinematic solver fallback: Restarting solution of kinematic optimality conditions and hard constraints.Kinematic solver fallback: Restarting solution of kinematic optimality conditions and hard constraints.Failed to solve kinematic optimality conditions and hard constraints after 10 fallback attempts.
Constraint violations for study 'Main.EvaluateLigamentLength.InitStudyForLigamentLengthEvaluation' :
Constraint #23 is above tolerance 1e-05, error = 0.008339, constr. #2 in 'Main.HumanModel.BodyModel.Trunk.JointsLumbar.SpineRhythmDrv'.
Constraint #26 is above tolerance 1e-05, error = 0.108940, constr. #5 in 'Main.HumanModel.BodyModel.Trunk.JointsLumbar.SpineRhythmDrv'.
Constraint #29 is above tolerance 1e-05, error = 0.047181, constr. #8 in 'Main.HumanModel.BodyModel.Trunk.JointsLumbar.SpineRhythmDrv'.
Constraint #32 is above tolerance 1e-05, error = 0.067884, constr. #11 in 'Main.HumanModel.BodyModel.Trunk.JointsLumbar.SpineRhythmDrv'.
Constraint #35 is above tolerance 1e-05, error = 0.087569, constr. #14 in 'Main.HumanModel.BodyModel.Trunk.JointsLumbar.SpineRhythmDrv'.
Constraint #38 is above tolerance 1e-05, error = 0.107162, constr. #17 in 'Main.HumanModel.BodyModel.Trunk.JointsLumbar.SpineRhythmDrv'.
Constraint #62 is above tolerance 1e-05, error = 0.000953, constr. #0 in 'Main.HumanModel.BodyModel.Trunk.JointsCervicalSpine.C1C0Jnt.Constraints'.
Constraint #63 is above tolerance 1e-05, error = 0.003816, constr. #1 in 'Main.HumanModel.BodyModel.Trunk.JointsCervicalSpine.C1C0Jnt.Constraints'.
Constraint #67 is above tolerance 1e-05, error = 0.038476, constr. #0 in 'Main.HumanModel.BodyModel.Trunk.JointsCervicalSpine.Flexion.RhythmDriver'.
Constraint #97 is above tolerance 1e-05, error = 0.033762, constr. #3 in 'Main.HumanModel.BodyModel.Right.Leg.Jnt.PatellaFemur.Constraints'.
Constraint #98 is above tolerance 1e-05, error = 0.036296, constr. #4 in 'Main.HumanModel.BodyModel.Right.Leg.Jnt.PatellaFemur.Constraints'.
Constraint #99 is above tolerance 1e-05, error = 0.014645, constr. #0 in 'Main.HumanModel.BodyModel.Right.Leg.Jnt.PatellaMovement'.
Constraint #121 is above tolerance 1e-05, error = 0.036296, constr. #3 in 'Main.HumanModel.BodyModel.Left.Leg.Jnt.PatellaFemur.Constraints'.
Constraint #122 is above tolerance 1e-05, error = 0.033762, constr. #4 in 'Main.HumanModel.BodyModel.Left.Leg.Jnt.PatellaFemur.Constraints'.
Constraint #123 is above tolerance 1e-05, error = 0.014645, constr. #0 in 'Main.HumanModel.BodyModel.Left.Leg.Jnt.PatellaMovement'.
Constraint #145 is above tolerance 1e-05, error = 0.002999, constr. #1 in 'Main.EvaluateLigamentLength.InitStudyForLigamentLengthEvaluation.ModelEnvironmentConnection.Drivers_Init.Knee_R'.
Constraint #148 is above tolerance 1e-05, error = 0.034905, constr. #4 in 'Main.EvaluateLigamentLength.InitStudyForLigamentLengthEvaluation.ModelEnvironmentConnection.Drivers_Init.Knee_R'.
Constraint #151 is above tolerance 1e-05, error = 0.003000, constr. #1 in 'Main.EvaluateLigamentLength.InitStudyForLigamentLengthEvaluation.ModelEnvironmentConnection.Drivers_Init.Knee_L'.
Constraint #154 is above tolerance 1e-05, error = 0.034905, constr. #4 in 'Main.EvaluateLigamentLength.InitStudyForLigamentLengthEvaluation.ModelEnvironmentConnection.Drivers_Init.Knee_L'.
ERROR(OBJ.MCH.KIN3) : EvaluateLigamentLength_Straight.any(4) : InitStudyForLigamentLengthEvaluation.InitialConditions : Kinematic analysis failed in time step 0 : Position analysis is not completed
0.1.0.0) Kinematic analysis...

To address the “Constraint #XXX is above tolerance” errors, I manually added additional drivers to the model. This successfully resolved those errors. However, the 'Kinematic analysis failed in time step 0' message remained unresolved.

Are there any suggestions on how to effectively incorporate cartilage contact into the ligament calibration process, or are there alternative approaches to achieve accurate ligament calibration?

Thank you in advance and best regards

Aline

Hi Aline,

I did not understand if you removed some constraints when you added the additional drivers to simulate cartilage contact. You had a study that was solving before, and it's understandable that there are errors when you add additional drivers without removing some existing driver/constraints. You have over constrained the model. I will suggest you try excluding corresponding constraints from your study using AnyMechObjectExcluder when you add these additional drivers.

Best regards,
Dave

Hi Dave

Thank you for this advice. Indeed, by excluding the initial knee drivers and replacing them with new drivers the ligament calibration was performed.
Unfortunately, I came across a new problem. It came to my attention that for different positions of the femur relative to the tibia (obtained by changing the driver), the results of the ligament lengths following calibration did not change. Although a change in slack length is expected as the origin and insertions are brought closer together. Looking into the details of the ligament lengths, the initial length of for example the ACL ligament equals 0.36 meters, which is very long. The length of the ligaments is calculated as shown below:

  1. ModelParameters: the origin and insertion of the cruciate ligament is determined based on the femur and tibia, exported in the local frame.
AnyFolder Thigh = {
     // Ligaments' origin & insertion coordinates (Femur)  
    AnyVec3   ACL_F = {-0.01216110,-0.004981290,0.006744680}*.TF';
}

  AnyFolder Shank = {
    // Ligaments' origin & insertion coordinates (Tibia)
    AnyVec3   ACL_T = { 0.008097710,0.335660,-0.007250220}*.TF';
}
  1. Seg: the nodes are created on the shank and the thigh
AnyRefNode ACL_T =
  { 
     sRel = .Scale(.StdPar.ACL_T);
     AnyDrawNode DrwNode = {
     ScaleXYZ = {0.003, 0.003, 0.003};
     RGB = {1,0,0};
     };
     DrwNode.Visible=On;
  };

AnyRefNode ACL_F =
  {
    sRel = .Scale(.StdPar.ACL_F);
    AnyDrawNode DrwNode = {
      ScaleXYZ = {0.003, 0.003, 0.003};
      RGB = {0,1,0};
    };
    DrwNode.Visible=On;
  };
  1. Ligament_properties
AnyFolder Ligaments_Right = 
  {
    AnyFolder& RefSegFolder = Main.HumanModel.BodyModel.Right.Leg.Seg;
    
  
    AnyFolder ACL_R = 
    {
      AnySeg& RefSeg0 = .RefSegFolder.Thigh;
      AnySeg& RefSeg1 = .RefSegFolder.Shank;
      
      AnyVec3 PosInSeg0 = Main.HumanModel.BodyModel.Right.Leg.Seg.Thigh.ACL_F.sRel;
      AnyVec3 PosInSeg1 = Main.HumanModel.BodyModel.Right.Leg.Seg.Shank.ACL_T.sRel;
     
      AnyVar InitLength = vnorm(PosInSeg0 - PosInSeg1);   
      AnyVar RefLength = DesignVar(InitLength);
      
      AnyVar Stiffness = DesignVar(k_ACL);
      AnyVar RefStrain = DesignVar(er_ACL);     
    };
};

When plotting the coordinates that are used to calculate the ligament length (PosInSeg0 & PosInSeg1), it appears that the coordinates in the local coordinate systems are used rather than the coordinates in the global coordinate system (see image below), however the scaling is introduced in the “Seg” file and the visualization of the ligament is correct.

Are you aware of the specific cause of the issue in the code, and could the initial ligament length being excessively long explain why the calibration results remain unchanged?

Thank you again and best regards
Aline

Hi Aline,

You are right in your observation that the PosInSeg0 and PosInSeg1 use the local coordinate system. This is because you have made these equal to the corresponding sRel values. sRel is the "relative" position of the reference node with respect to its parent reference frame, which could be a segment, another node, and so on.

So, the thigh and shank do not share the same reference system, and therefore, it will not be the correct way to calculate ligament properties. You should use the object r inside the reference node. r gives the position in global system. However, it can potentially break the code depending on how you have used PosInSeg0 and PosInSeg1 subsequently. r is an output of the kinematic analysis, while sRel is a constant and known in advance.

Hopefully, this will help you get going.

Best regards,
Dave

Hi Dave

Thank you for your response! I tried replacing the 'sRel' by 'r' before but this did indeed break my code. Do you have any suggestions on how to proceed? Is it a possibility to switch the order or redefine the ligament lengths once 'r' was given a value?

Thank you again and best regards
Aline

Hi Aline,

It is possible to analytically calculate r0 values for your model. You can see how this is done in the human body, for example, take the thigh and shank segment and see how their r0, Axes0 values are calculated. I hope that will get you going forward.

Best regards,
Dave

Hi Aline,

Another way to calibrate the ligaments is to use the ligament models and the calibration study for ligaments. It's similar to muscle calibration. Then, you won't need to analytically calculate the ligament length. See this tutorial.

Best regards,
Dave

Hi Dave
Thank you for your response. If I am not mistaken, I already applied the calibration study for ligaments. However, when artificially altering the initial length as shown below to 0.0225 (which is closer to the expected ligament length), the result of the ligament calibration changes.

AnyVec3 PosInSeg0 = Main.HumanModel.BodyModel.Right.Leg.Seg.Thigh.ACL_F.sRel + {0, 0.35, 0};
      AnyVec3 PosInSeg1 = Main.HumanModel.BodyModel.Right.Leg.Seg.Shank.ACL_T.sRel;

Without altering the ligament length of the ACL, the starting length equaled 0.365 meters. Following the ligament calibration, the length equaled 0.029 meters. With a starting length of 0.022 meters, the ligament length following calibration equaled 0.37 meters. Therefore, I am a bit unsure whether the ligament calibration is working as it should be.

I got the idea that the initial ligament length influences the result of the ligament calibration. Therefore, I wanted to optimize the staring ligament length. Can it be that the ligament calibration is influenced by the initial ligament length? And if so, do I then require to analytically calculate the r0’s as you suggested before?

I have included the ligament calibration below. Based on the tutorial, I changed the AnyKinStudy to an AnyBodyCalibrationStudy, however this did not change the outcomes. The "Inputs_InitStudy.any" file contains the exclusion of the existing knee drivers and the introduction of new knee drivers as discussed before:

AnyDesEvalStudy EvaluateLigamentLength = {

  AnyBodyCalibrationStudy InitStudyForLigamentLengthEvaluation = 
  {
    Replay.OPERATION_DISPLAY_PRIORITY(PriorityLow);
    InitialConditions.OPERATION_DISPLAY_PRIORITY(PriorityLow);
    Kinematics.OPERATION_DISPLAY_PRIORITY(PriorityLow);
    
    AnyComponentDefinition obj = {};
    Gravity = {0,0,0};/// Not relevant for AnyKinStudy
    AnyFolder& BodyModel = Main.HumanModel.BodyModelWithoutMuscles ; 
    AnyFolder &EnvironmentModel = Main.EnvironmentModel;
    
    AnyFolder ModelEnvironmentConnection = 
    {    
      AnyFolder &MarkerDrivers = Main.ModelSetup.MocapDrivers;
      AnyFolder &ExtraDrivers = Main.ModelSetup.MocapExtraDrivers;
    };
    #include "Inputs_InitStudy.any" 
    
    AnyFolder& Anthropometrics = Main.HumanModel.Anthropometrics;
    #include "KinematicStudyForLigCalibration.any"   
    
    AnyFolder OutputFunctions_Length = {};   
    
  };

KinematicStudyForLigCalibration:

InitialConditions = 
{
  SmallStepAssumptionOnOff = On;
  PosAnalysisOnlyOnOff = On;
  KinematicTol=1e-5; 
  SolverType = KinSolOverDeterminate;
  PreOperation = 
  { 
    AnyOperation &SetView = Main.ModelSetup.Views.SetViewMacros.KinematicView; 
  };
};


Kinematics = 
{
  SmallStepAssumptionOnOff = On;
  PosAnalysisOnlyOnOff = On;
  KinematicTol=1e-5; 
  SolverType = KinSolOverDeterminate;
};

// Define statements allow us to overide tEnd/tStart/nStep to 
 // when calling AnyBody from external programs.  
 #ifndef N_STEP_PARAM_OPT
 #ifndef N_STEP 
   #define N_STEP_PARAM_OPT iffun(eqfun(Main.ModelSetup.TrialSpecificData.nStep,1),1, floor(max({20, Main.ModelSetup.TrialSpecificData.nStep/6})))
 #else
   #define N_STEP_PARAM_OPT N_STEP
 #endif
 #endif
 
 #ifndef T_END
 #define T_END Main.ModelSetup.TrialSpecificData.tEnd
 #endif
 
 #ifndef T_START
 #define T_START Main.ModelSetup.TrialSpecificData.tStart
 #endif
 
 tStart = T_START;
 tEnd = T_END;
 nStep = N_STEP_PARAM_OPT;

Additionally, I made an attempt to analytically calculate the r0’s using the code below (based on the code in the HumanModel and r0 for the femoral origin of the ACL, r1 for the tibial insertion):

AnyVec3 r0 = Main.HumanModel.BodyModel.Right.Leg.Seg.Shank.ACL_T.sRel*Main.HumanModel.BodyModel.Right.Leg.Seg.Shank.Axes0'+ Main.HumanModel.BodyModel.Right.Leg.Seg.Shank.r0 - Main.HumanModel.BodyModel.Right.Leg.Seg.Thigh.ACL_F.sRel*Main.HumanModel.BodyModel.Right.Leg.Seg.Thigh.Axes0';
AnyVec3 r1 = Main.HumanModel.BodyModel.Right.Leg.Seg.Thigh.ACL_F.sRel*Main.HumanModel.BodyModel.Right.Leg.Seg.Thigh.Axes0'+ Main.HumanModel.BodyModel.Right.Leg.Seg.Thigh.r0 - Main.HumanModel.BodyModel.Right.Leg.Seg.Shank.ACL_T.sRel*Main.HumanModel.BodyModel.Right.Leg.Seg.Shank.Axes0';

However, when plotting the points, they are not correctly located on the femoral and tibial bone in the global frame. Could you verify if the implementation of the function is correct?

Thank you in advance and kind regards
Aline

Hi Aline,

I realized a bit too late that you are working using the knee simulator example. So, I assume you have adapted all the relevant code and class templates used to create ligaments in that model. I am sorry about that and then you don't need to use the AnyBodyCalibrationStudy (more on that later).

So, you were right in using sRel for PosInSeg0 and PosInSeg1. That is how it should be. These variables are used to create the two reference nodes on the femur and shank and a kinematic measure (AnyKinPLine or AnyKinSPLine) to measure the length of the ligament. At this point you should check if these nodes are correctly located on the segments.

When you load the model, you see a large value of the ligament in RefLength and that is ok as it is just an initial guess. It is a DesignVar and it will be updated during the calibration sequence. The value from AnyKinPLine will be used to overwrite RefLength (that is the measured distance between the 2 reference nodes defining the ligament).

Changing the initial length (InitLength) of the ligament should not have any influence on the calibrated length. However, when you change PosInSeg0 and PosInSeg1, you actually update the reference nodes that are used to create the kinematic measure and this will change the measured length as the reference nodes have moved.

So, when you run the calibration sequence, I want you to check the value of RefLength, and corresponding AnyKinPLine for that RefLength. After you have run the calibration sequence, make sure the values are updated (it should normally be a part of the LigamentCalibrationSequence, but in case it's not, right click on Main in model tree and click Update Values). You should see the RefLength as the maximum of the measure of AnyKinPLine.

Best regards,
Dave

p.s: Regarding the tutorial on AnyBodyCalibrationStudy and using ligaments, it's just another way of achieving the same outcome. In the knee simulator example, the ligament model is implemented manually by measuring the length, and applying a force. Instead, you can use AnyLigament and AnyLigamentModel with AnyBodyCalibrationStudy to do the same.

Hi Dave

Thank you for your response and pointing out to have a look at the AnyKinPLine! Indeed, the value of the AnyKinPLine changed following the ligament calibration (for which I wrote an AnyOperationSequence). However, the RefLength did not. I discovered that when running the EvaluateLigamentLength.UpdateDesign rather than the AnyOperationSequence, it did update the RefLength. I therefore updated the AnyOperationSequence in a way that I now does update the RefLength.

Visually, it looks like I have correctly implemented the FDK in the knee joint (see video).

Schermopname 2025-03-05 163204
Schermopname 2025-03-05 163434

However, I have one final question regarding the output. I want to output the joint reaction force of the knee joint. Online, I already found to have a look at the selected output. However, in the selected output of my model, I only have Knee_AxialMoment and Knee_LateralMoment (see figure below). The forces (Mediolateral, proximodistal and anteroposterior), as shown in the example, are not included in the selected output. Can you help me with where to find the joint reaction force output of the FDK knee?

Schermafbeelding 2025-03-06 093955

Thank you in advance and kind regards
Aline

1 Like