Local frames of references mismatching

Hello,

I used patient-specific CT scan for the femur and I managed to scale Anybody’s femur according to it based on the scaling and FE tutorials. I introduced a local frame of reference for the target femur and used reverse transformation to construct it on the scaled femur in the model.

My problem is that when I draw both local frames of references (source and target), I found that they had the same axis direction but doesn’t share the same origin point!!

I am attaching the model which include the STL files for bothe the source and the target femur, StandingModelScalingDisply model that I used, the custom scaling script, and MyscalingLaw script.

Please advise,

Best,

Shady

Hi Shady,

First of all please do not start a new thread since this all still relates to the previous question.

Secondly, p0 should be an origin (0,0,0) transformed using reverse transformation.

Kind regards,
Pavel

Thanks Pavel for your answer and I apologize for this mistake.

I defined the origin p0 as (0,0,0) as you suggested, but still both origins are mismatching!!

What would be the reason for that?

Shady

Hi Shady,



AnySeg &LeFemur = Main.HumanModel.BodyModel.Left.Leg.Seg.Thigh;
LeFemur ={
  
  AnyRefNode pa = {sRel = Main.HumanModel.Scaling.GeometricalScaling.Left.Thigh.MyScalingLaw.ReverseTransform({0.0,0.0,0.0});};
  AnyRefNode pb = {sRel = Main.HumanModel.Scaling.GeometricalScaling.Left.Thigh.MyScalingLaw.ReverseTransform({1.0,0.0,0.0});};
  AnyRefNode pc = {sRel = Main.HumanModel.Scaling.GeometricalScaling.Left.Thigh.MyScalingLaw.ReverseTransform({0.0,1.0,0.0});};
  
  AnyRefNode Sorloclfr={
    AnyDrawSurf Surface = 
    {
      FileName = "Model/Femur_francesco_Left_scaled.stl";
      ScaleXYZ = {1,1,1};
    };
    sRel = .pa.sRel;
    ARel = RotMat(sRel,.pb.sRel,.pc.sRel);
  };
};

This is a piece of code which would put your ‘femur_franceso_Left_scaled.stl’ into the segmental ref. frame.

It is not working most likely because there is something funny about your scaling. It looks somewhat correct from the organizational point view, but i will let you check the actual landmarks.

What you should have:

Points0 - landmarks that correspond to a generic AnyBody left femur in an undeformed state.
Points1 - should be the landmarks that correspond to the target femur.

Judging from the name of the second STL - you probably got something wrong.

Hope this helps, but if you are trying to achieve something else - please explain it better.

Kind regards,
Pavel

P.S. In the tutorials we also use a TSeg2ScaleFrame transformation to preprocess source landmarks. It is missing in your code - please check the tutorials once again.

Hi Pavel,

I am sorry for my vague explanation. To make it clear, the file “Femu_Left_190.stl” is the source femur (Anybody’s generic model femur) and the file “femur_franceso_Left_scaled.stl” is the target femur. As you mentioned, I used points0 to define landmarks of the source femur and points1 to define the correspondent landmarks of the target femur.

I created two local frames of references for both femurs (source and target) that was supposed to have correspondent orientation and origin. I used AnyDrawRefFrame to confirm that both of them are conformable. However, I found that the local reference frame of the target femur in the correct location, while that of the source femur was far away from the expected location, see attachment (the green coordinate is the target local frame of reference and the cyan coordinate is the source local frame of reference).

I am not sure what I did wrong, but I am speculating that it could be in the place where I defined the local frame of reference of the source femur in the main script??

Waiting for your kindly reply,

Shady

P.S. I didn’t find TSeg2ScaleFrame transformation used in the scaling tutorial and I didn’t find any explanation for it in Anyscript Reference Manual!

Your source landmarks are not matching a generic AnyBody femur (should be non scaled:

Use this code for checking (‘femur’ is a nonscaled/nonreflected generic AnyBody STL):

Main = {
 AnyFixedRefFrame fr = {

     AnyMatrix test =  {{0.102507,0.603426,-0.117823},    // Intercondylar
        {0.0976133,0.590783,-0.0807493},    // MedialCondyle
        {0.135372,0.620346,-0.0982643},    // MedialAnterior
        {0.110872,0.601301,-0.161391},    // LateralCondyle
        {0.144899,0.625181,-0.147721},    // LateralAnterior
        {0.13562,0.62558,-0.118232},             //InterAnt
        {0.0625806,0.631929,-0.0965016},   //MedPost
        {0.0775485,0.636462,-0.142668},     //LatPost
        {0.0860138,0.6339,-0.119661},    //InterPost
        {0.0943826,0.625097,-0.0704438},  //Medial
        {0.103903,0.626277,-0.167714},    //Lateral
       // {0.0841512,1.05637,-0.0667025},    //FoveaCaptis
        //{0.0671884,1.05265,-0.156988},    //GreatTroch
        //{0.061784,0.972055,-0.103657},    //LessTroch
        //{0.0962192,1.03067,-0.167893},    //InterTroch
        //{0.0438965,1.03729,-0.127173}       //GreatTroch2
        {0.084767,1.06621,-0.0869051},    //Top
        {0.109611,1.0445,-0.0895425}, // HeadAnt
        {0.056819,1.05144,-0.0796532} //HeadPost
        };
        AnyDrawPointCloud ptcl = {
          Points = .test;
          Points3D = On;
          RGB={1,0,0};
        };
        
        AnyDrawSurf srf = {FileName="femur";ScaleXYZ={1,1,-1};};
 };

};

You might be right about TSeg2ScaleFrame - it is not on the website version of the tutorial, but should be in your local documentation. And it would also depend on the version of AMMR you are using. I just assumed you use the latest.

Regards,
Pavel

Hi pavel,

You are right, when i used your code the points were not matching the femur. It match it if i changed the sign of the z component in each landmark (which is weird). However, when i checked the points on the Anybody’s repository standing model, it worked good and was matching the femur precisely. In fact, the registration of the bone was done perfectly by using these points and it didn’t show any problem. So, i think that my problem of mismatching local coordinates between the target and source femur is due to another reason.

After some investigation, i think that the problem is that when i defined the origin of the local coordinate on the registered femur folder and used the reverse transformation to confirm that it will match the same point at the target femur, this origin was defined with respect to the generic local frame of reference of the segment (femur) not according to the global frame of reference.

What makes me sure about this is that when i defined this origin outside the segment (femur) folder, it worked good and the origin was exactly where i was expecting. However, in this case, this coordinate system will not move with the femur, which makes it useless.

Please let me know if i am right, and let me know your suggestions.

Best,

Shady

Hi Pavel,

I am sorry for bothering you with too many questions. I managed to solve the previous issue and it works well now.

I have another quick question: I needed to define a new segment folder to introduce my new local coordinates.

Anyseg SourceFemur = 
    {
      Mass = 0; Jii = {0, 0, 0};
     
      // reverse transforming of the three points that represent target LC
      AnyVec3 p0 = .MyTransform2({-0.109281,-0.156359,-1.02152});//This is the same origin point that you selected at the target
      AnyVec3 px = .MyTransform2({1.0,0.0,0.0});
      AnyVec3 py = .MyTransform2({0.0,1.0,0.0});      
      
      //Define the local coordinate orientation of the source that corresponds to the target LC
      AnyRefNode Sorloclfr={
        sRel = .p0;
        ARel = RotMat(sRel,.px,.py);
        AnyDrawRefFrame Sorlocalref = {ScaleXYZ = {1,1,1}*0.3;RGB={0,0,1};};
      };
      
    };//Source

However, when I run the inverse dynamics study for the standing model, it produced an error. Probably, this happened because this segment had no constraints (when I comment it out, the inverse dynamics works well).

Is there is any way around this issue?

Best,

Shady

Hi Shady,

I am sorry for not replying regularly, but it is good you found the problem.

Does it have to be a segment? You can AnyFixedRefFrame instead if you are not planning to move it.

Regards,
Pavel

Hi Pavel,

Well, I need to run a simulation and want to make sure that the local coordinates will follow the movement of the segment.

I guessed that the problem is that I am defining the local coordinates in the scaling law script. So I edited the folder of the tibia in the main script by adding the local coordinates using AnyFixedRefFrame function. However, when I run the simulation, it didn’t follow the movement of the segment!

AnySeg &LeTibia = Main.HumanModel.BodyModel.Right.Leg.Seg.Shank;
LeTibia ={
  
    AnyFixedRefFrame Sorloclfr = 
    {
      
      Origin =  {0.1280259, 0.5366795, 0.1408814};
      Axes =  {{-0.4411095, -0.540298, 0.7165895}, {0.640122, 0.3702263, 0.6731838}, {-0.6290201, 0.7556524, 0.1825463}};
      AnyDrawRefFrame Sorlocalref = {ScaleXYZ = {1,1,1}*0.3;RGB={0,0,1};};
    };
 

}; //LocalRefShank

The values of the origin and axes are the reverse transformed values of those of the target segment.

Any suggestions?

Shady

Hi Shady,

Yes, exactly that is the problem. You should be using a AnyRefNode object to be inside of AnySeg object for it to follow the motion. When you use an AnySeg - it adds 6 dof to the problem that you need to handle.

The code that i suggested is supposed to handle all of this. If you are still unsure - please have a look at the example from the webcast:

Personalize your musculoskeletal models

The green surface of the proximal tibia is aligned with the segment and would move together with it. So this example contains the code that you need - please copy from there.

Regards,
Pavel

Thanks Pavel for the code,

I used the code that you send and it worked good (the target tibia and the new defined local coordinates moves with the body in the simulation.

Main.HumanModel.BodyModel.Right.Leg.Seg.Shank = {
 AnyRefNode Sorloclfr = {
    AnyFloat posvec = Main.HumanModel.Scaling.GeometricalScaling.Right.Shank.MyScalingLaw.MyTransform2({{0,0,0},{1,0,0},{0,1,0}}*1.0);
    sRel = posvec[0];
    ARel = RotMat(posvec[0],posvec[1],posvec[2]);
    AnyDrawRefFrame drws = {ScaleXYZ = {1,1,1}*0.3;RGB={0,0.5,1};};
    AnyDrawSurf srf_partial_tibia = {
      FileName="Tibia_Ruth_R.stl";
      ScaleXYZ={1,1,1}*1;RGB={0,1,0};};
  };

};

However, they didn’t match the tibia in the generic model (see attached figure)!
I used the same reverse transformation (MyTransform2)that I used to return the registered bone to the source location and it worked good in returning the bone(the right tibia in the picture is scaled to my target tibia and returned perfectly to its original location as you see), but when I used it to return the points (origin and axes) it didn’t work well!

In conclusion, when I define the local coordinates inside the tibia folder, the coordinates moves with the tibia but it is not defined in the right location. On the other hand, when I use the same definition for the local coordinates outside the tibia folder, it is defined in the right location but it doesn’t move with the tibia during the simulation.

Honestly, I don’t know where is the problem :(??

Best,

Shady

Hi Shady,

The example should work. So you might have a some sort of inconsistency in the input - please make sure that your landmarks/surface/transformations are consistent, and that you use nondeformed/nonscaled generic AnyBody bones.

And secondly could we take a step back and try to understand what you are trying to do? Could you write your goal in one sentence, for example: “i want to replace generic AnyBody bone with the target tibial STL to be able to simulate contact in the knee joint during motion”? (the code i provided does exactly that).

I have a feeling that we are trying to address different issues.

Regards,
Pavel

Hi Pavel,

I want to replace generic AnyBody tibia bone with the target tibial STL to be able to extract the muscle forces during gait simulation and apply it as boundary conditions in an FE model (using FEbio software).

Towards that, I did the following:
1- I anthropometrically scaled the generic Anybody model to bring it as close as possible to the target tibial length. (to make sure that the bones in the model will be uniform).
2- I registered Anybody’s source tibia to the target tibia and returned it to its original location by using a transformation pipeline.
3- I created a local coordinate at the target tibia and reverse transformed it to the source tibia
4- I applied a simple simulation (Knee flexion) to check if the extracted forces will drive my FE model the same way as in Anybody or not.

I successfully finished point 1 and 2. However, at point 3, after I reverse transformed the local coordinates and draw it, it appears in another location than that in the target tibia!
I need to make sure that the extracted forces are in the same location that I defined in the target tibia to know how to introduce it in my FE model.

I wish this clarify the problem

P.S: I wrote a small script to test point 2 and 3 and it worked very good (attached), but for some reason when I​ applied it in the standing model, it produced the problem that I described in the previous post.

Best

Shady

Hi Shady,

Then i understand everything well, you want to do exactly what i provided in this example.

Your problem, as i said, is inconsistency of inputs (landmarks, surfaces). What i see when i load a generic AnyBody tibia from the AMMR (no anthro scaling, no deformation) into your model is that your landmarks are aligned with Tibia_190_R, but not the generic AnyBody tibia. This is why you get something strange.

In an ideal world you would take my code from the webcast, select your landmarks properly, and be happy with the results. Everything is done for you, no changes are needed. This is the code that you need:


// TODO!!!!!
// 1. Prepare AnyMatrix P0 = {{},{},..};
// 2. Prepare AnyMatrix P1 = {{},{},..};

AnyFunTransform3DLin2 reg = {
  Points0 = .TSeg2ScaleFrame(.P0);
  Points1 = .P1;
  Mode = VTK_LANDMARK_AFFINE;
};
AnyFunTransform3DRBF rbf = {    // this and an STL transforms are optional, please use them if needed
  PreTransforms = {&.reg};
  RBFDef.Type = RBF_Triharmonic;  
  PolynomDegree = 1;
  Points0 = .reg.Points0;
  Points1 = .reg.Points1;
  BoundingBoxOnOff = On;
  BoundingBox.Type = BB_Cartesian;
  BoundingBox.ScaleXYZ={2,2,2}*1.2;
  BoundingBox.DivisionFactorXYZ ={1,1,1}*3;
};
AnyFunTransform3DLin2 inv = {
  Points0 = .reg.Points1;
  Points1 = .reg.Points0;
  Mode = VTK_LANDMARK_RIGIDBODY;
};

AnyFunTransform3DIdentity ScalingFunction = {
  PreTransforms = {&.reg, &.inv};
};

Main.HumanModel.BodyModel.Right.Leg.Seg.Shank = {
  AnyRefNode FEAOutputNode = { // NODE TO BE USED FOR FEA OUTPUT
    AnyFloat posvec = Main.HumanModel.Scaling.GeometricalScaling.Right.Shank.inv({{0,0,0},{1,0,0},{0,1,0}}*1.0);
    sRel = posvec[0];
    ARel = RotMat(posvec[0],posvec[1],posvec[2]);
    AnyDrawSurf srf_partial_tibia = {
      FileName="Tibia_Ruth_R.stl ";
      ScaleXYZ={1,1,1}*0.001;
      RGB={0,1,0};
    };
  };
};


Please have a look at the webcast example: StandingModel\Demo.main.any how it is being included into a full body model.

Kind regards,
Pavel

Hi Pavel,

First, I apologize for asking again and bothering you with my questions, but I spent too much time to figure out this problem and I couldn’t do it.

I just want to clarify that my problem is specifically about defining the local coordinates, and I want to emphasize that the scaling process is working well.

I used the model that you used in the webcast “Personalize your musculoskeletal model” as it is, and added the section that you send in the last post for defining the local coordinate on the tibia. ( I didn’t add anything of my own model. I used your model as it is with its own landmarks). I only added a line to draw the local coordinates on the model to see where it lands.

AnyFunTransform3D &AnthropometricScaling =...Scaling.GeometricalScaling.Right.Shank.ScaleFunction;

AnyMatrix proximal_tibial_landmarks_source = {
{0.0421311, -0.417261	, 0.0076043},
{0.0524158, -0.415519	, -0.005842},
{0.0504895, -0.417367	, 0.0029397},
{0.0231782, -0.428228	, 0.0041235},
{0.0087279, -0.437196	, 0.0308129},
{0.0352268, -0.428144	, 0.0506333},
{0.0777616, -0.42567	, 0.0306338},
{0.093775 , -0.418601	, -0.006715},
{0.073548 , -0.427378	, -0.034583},
{0.0359883, -0.429557	, -0.021760},
{0.0326333, -0.446978	, 0.0076968},
{0.073622 , -0.443837	, -0.028317},
{0.0373438, -0.446294	, -0.014700},
{0.0120185, -0.445433	, 0.0244233},
{0.0133248, -0.448197	, 0.0397956},
{0.0461639, -0.447477	, 0.0548501},
{0.0886141, -0.440804	, 0.0198169},
{0.070131 , -0.482118	, 0.0434376},
{0.0796165, -0.454165	, 0.0434672},
{0.0874448, -0.480437	, 0.0280284}};

AnyMatrix distal_tibial_landmarks_source = {
{0.082874 , -0.556316	, 0.0316961},
{0.0727126, -0.655067	, 0.0307637},
{0.0533289, -0.557545	, 0.0201804},
{0.0792458, -0.55134	, -0.001744},
{0.0742106, -0.643431	, -0.001879},
{0.0527515, -0.647375	, 0.0155517},
{0.0509596, -0.763376	, 0.0197978},
{0.0428092, -0.796114	, 0.0034402},
{0.0468567, -0.797333	, 0.0347876},
{0.0506022, -0.802705	, 0.0193561},
{0.0998085, -0.814651	, 0.0018608},
{0.0883566, -0.801911	, 0.0064930},
{0.0859482, -0.782631	, -0.011355},
{0.100225 , -0.782613	, 0.0137955},
{0.0868853, -0.75546	, 0.0179042},
{0.0771755, -0.756288	, -0.002916},
{0.07928  , -0.703497	, 0.0242642},
{0.0689144, -0.807856	, 0.0361676},
{0.0622607, -0.807041	, -0.004303}  
};

AnyMatrix proximal_tibial_landmarks_target = {
{-71.6207, 30.8218  , -776.802},
{-57.3601, 30.3031  , -775.329},
{-65.837 , 26.7602  , -775.763},
{-76.4937, 44.2466  , -785.001},
{-98.9736, 39.1999  , -789.424},
{-102.512, 14.8001  , -783.359},
{-69.2545, -2.08682 , -782.556},
{-40.595 , 3.81419  , -779.137},
{-30.273 , 32.5008  , -784.674},
{-51.1075, 50.1846  , -788.474},
{-72.5643, 40.7791  , -802.373},
{-31.2547, 30.1724  , -805.641},
{-54.547 , 47.6829  , -807.104},
{-95.1286, 40.8064  , -800.465},
{-102.402, 31.5811  , -805.18},
{-98.5779, -0.192415, -797.878},
{-59.01  , -8.30906 , -798.775},
{-82.3525, -3.45649 , -822.226},
{-76.8977, -12.3271 , -806.035},
{-64.141 , -13.7685 , -817.435}};

AnyFunTransform3DLin2 RegProximalLandmarks = {  // aligning proximal target bone with expected anthropometrically scaled generic bone
  Points0 = 0.001*.proximal_tibial_landmarks_target;
  Points1 = .AnthropometricScaling(.proximal_tibial_landmarks_source);
};

AnyMatrix P0 = arrcat(
                  distal_tibial_landmarks_source, 
                  proximal_tibial_landmarks_source
                  ); 
AnyMatrix P1 = arrcat(
                  AnthropometricScaling(distal_tibial_landmarks_source),
                  RegProximalLandmarks(0.001*proximal_tibial_landmarks_target)
                  );


AnyFunTransform3DLin2 reg = {
  PreTransforms={&.AnthropometricScaling};
  Points0 = .TSeg2ScaleFrame(.P0);
  Points1 = .P1;
  Mode = VTK_LANDMARK_AFFINE;
};
AnyFunTransform3DRBF rbf = {    // we will only use an RBF transformation, because full STL is not available
  PreTransforms = {&.reg};
  RBFDef.Type = RBF_Triharmonic;  
  PolynomDegree = 1;
  Points0 = .reg.Points0;
  Points1 = .reg.Points1;
  BoundingBoxOnOff = On;
  BoundingBox.Type = BB_Cartesian;
  BoundingBox.ScaleXYZ={2,2,2}*1.2;
  BoundingBox.DivisionFactorXYZ ={1,1,1}*3;
};
AnyFunTransform3DLin2 inv = {
  Points0 = .reg.Points1;
  Points1 = .reg.Points0;
  Mode = VTK_LANDMARK_RIGIDBODY;
};

AnyFunTransform3DIdentity ScalingFunction = {
  PreTransforms = {&.rbf, &.inv};
};

Main.HumanModel.BodyModel.Right.Leg.Seg.Shank = {
  AnyRefNode new = {
    AnyFloat posvec = Main.HumanModel.Scaling.GeometricalScaling.Right.Shank.inv({{0,0,0},{1,0,0},{0,1,0}}*1.0);
    sRel = posvec[0];
    ARel = RotMat(posvec[0],posvec[1],posvec[2]);
    AnyDrawRefFrame new = {ScaleXYZ = {1,1,1}*0.2;RGB={0,0,1};};
  };
};

The result is shown in the attached figure. The scaling was perfect. However, the newly defined tibia local coordinate was shown on the chest not at the tibia!! This is exactly the issue that i am facing for more than one month. Whenever I define a local coordinate inside the segment folder by using the inverse transformation, it appears in a different location than what I was expecting.

Can you please advise!

Best

Shady

This reference frame represents a CT(or MRI) scan coordinate system, which should correspond to your FE mesh coordinate system, and, thus, should be used for the FEA boundary conditions.

If you output force using AnyMechOutputFileForceExport and use this ref. frame as a target ref. frame you should get a correspondence of muscle attachments with your bone surface.

So even though it is in the chest region - it is connected physically and ideologically to the tibia segment and will move along.

Are you planning to do a simulation on a single bone?

Pavel

Thanks Pavel for your kind answer.

No, I will add a femur to the same FE model.

I understand that the reference frame is physically connected to the tibia in this way. However, I am still confused about why the origin of the coordinate system didn’t land at the landmark that corresponds to that of the target tibia??

The only difference is that the frame of reference was defined inside the shank folder, which makes all the measures with respect to the local coordinates of that segment. Unlike the reverse transformation of the bone itself which was with respect to the global coordinates. Because, in both cases, I used the same inverse transformation matrix. Is this correct?

Best,

Shady

Hi Shady,

It landed into the origin of the target STL coordinate system (represented in the Shank coordinate system). And since you will probably mesh this STL in the same coordinate system - we want to keep this one to export forces into. This is the reason why we use it.

If you want to use a 3rd ref. frame to move 2 bones against - then you would need to have special logic, which you need to think through yourself.

Kind regards,
Pavel

1 Like