Partial morphing

I understand what you are trying to do. You want to, effectively, morph the distal end of the AnyBody bone to look like the target. This requires:

  • keeping the shape of proximal, middle parts to look as AnyBody bone,
  • morph only the distal part

This can be done by making a "fake" set of target landmarks, where it consists of non-deformed proximal (+middle) landmarks of AnyBody and distal landmarks of the target (I can see you already registered the bones, so it overlays nicely).

So the solution I suggested is the one that you want, but it might be missing a few more landmarks to control the behavior. You can add some more landmarks distally in case you need better morphing at the distal end.

In case you plan to do some sort of contact analysis, you could actually use the real surface by adding it to the segment and using only for contact. The morphing is needed to adjust muscle attachment and other nodes.

P.S. Yes, femur.anysurf3 is from the AMMR
P.P.S. I started writing the message after seeing the deleted post

Thank you very much again Pavel for your prompt response. I think it is clear for me now.

Best regards,
Omar

1 Like

I need you help with regard of morphing the TibiaFibula bone:
Here is a link of the files: TibiaFibulaMorphing – Google Drive

Similar to the femur, I am trying to do the same for the TibiaFibula bone. I might have done something wrong but I am not sure what it is!

Your help again is appreciated.

Best regards,
Omar

As the femur was nicely registered, I am having the issue with the tibia.

@pgalibarov I ended up having the following:


Yellow ref frame is for the target! I do not know if the reference frame is supposed to be at the tibia plateau but for the source bone (grey), the ref frame in red is at the most distal part (ankle joint).

When I tried to visualize the morphing for my model, the source femur in blue showed up above the knee joint while the target femur (white) was registered at the tibia segment as follows:

Your assistance and insights are highly appreciated.

Note: I used the registration transform to register the bones
Scaling script for the Shank:
AnyFolder Shank =
{
#if InverseDynamicModel== 0 | INV_DYN_EXCLUDE_RIGHT_LEG == 0
AnyFunTransform3D &TSeg2ScaleFrame = ...BodyModel.Right.Leg.Seg.Shank.Scale.T0;

  AnyFunTransform3DLin ScaleFunction = {
    ScaleMat = {{1,0,0},{0,1,0},{0,0,1}};
    Offset = {0,0,0};
    PreTransforms = {&.RBFTransform, &.ReverseTransform};
  };
  
  AnyFunTransform3DLin2 AffineTransform = 
  {
    Points0 = .TSeg2ScaleFrame(Points0unscaled);
    AnyMatrix Points0unscaled = 
    {
      {2.4553533, 322.60641, 38.435726}*0.001,          //
      {-4.6470823, 317.59225, -35.667347}*0.001,       //
      {-0.78102088, 331.89825, 19.865219}*0.001,       // 
      {3.2023861, 328.15952, -23.625978}*0.001,        //
      {1.9201577, 336.08969, -0.26364601}*0.001,       //
      {32.696163, 300.36832, 10.931908}*0.001,         //
      {-23.028795, 319.39224, -5.6062117}*0.001
      {8.24599, 9.09129, -6.17265}*0.001 // ankle landmark selected from the source bone
  };         
    Points1 = 
    {
      {-0.93816972, -6.8387461, 37.621883}*0.001, 
      {-17.663643, -16.249313, -39.197701}*0.001, 
      {-6.1415749, 3.1979933, 16.636206}*0.001, 
      {-8.9352255, -0.69453013, -25.310684}*0.001, 
      {-9.3684425, 7.5700684, -4.0201902}*0.001, 
      {29.332415, -25.041574, 3.6474059}*0.001, 
      {-33.782616, -11.918186, -2.6702728}*0.001
      {8.24599, 9.09129, -6.17265}*0.001 //Ankle source (fake point)
    };       
  Mode = VTK_LANDMARK_AFFINE;
  };

  AnyFunTransform3DLin2 ReverseTransform = {
    Points0 = .AffineTransform.Points1;
    Points1 = .AffineTransform.Points0;
    Mode = VTK_LANDMARK_RIGIDBODY;
  };

  AnyFunTransform3DLin2 RegistrationTransform = {
    Points0 = .AffineTransform.Points1;
    Points1 = Main.Studies.HumanModel.BodyModel.Right.Leg.Seg.Shank.Scale(.AffineTransform.Points0unscaled);
   Mode = VTK_LANDMARK_RIGIDBODY;
  };
  
  AnyFunTransform3DRBF RBFTransform = {
  PreTransforms = {&.AffineTransform};
  RBFDef.Type = RBF_Triharmonic; 
  //RBFDef.Param = 0.2; //1 (You can comment it)
  PolynomDegree = 1;
  Points0 = .AffineTransform.Points0; // same source points as in the affine transform
  Points1 = .AffineTransform.Points1; // same target points as in the affine transform

  BoundingBoxOnOff = On; //You may change the settings
  BoundingBox.Type = BB_Cartesian;
  BoundingBox.ScaleXYZ= {2,2,2}; // originaly was {1,2,2}*1.2;
  BoundingBox.DivisionFactorXYZ ={1,1,1}*5; // originaly was *3
}; 

#endif
};

Drawing the shank source and target on the model:

Main.Studies.HumanModel.BodyModel.Right.Leg.Seg.Shank = {
AnyFunTransform3D &CustomMarkerScaling = Main.Studies.HumanModel.SubjectSpecificScaling.Right.Shank.RegistrationTransform;
AnyFunTransform3D &CustomMarkerScaling1 = Main.Studies.HumanModel.SubjectSpecificScaling.Right.Shank.RegistrationTransform;// Just used to visualize the bones
AnyRefNode ShankBone = {
AnySurfSTL TargetShank =
{
FileName = "C:\Users\Omar Bahig\Desktop\Morphing template\TibiaFibula\Tibiafibula_target.stl";
ScaleXYZ = {1, 1, 1}/1000;
AnyFunTransform3D &scale = ..CustomMarkerScaling1;
AnyDrawSurf stldraw = {
FileName = .FileName;
ScaleXYZ = {1, 1, 1}/1000;
AnyFunTransform3D &scale = .scale;
RGB = {1,1,1};
Opacity = .....BonesOpacity.Shank;

  }; 
  AnyDrawRefFrame TargShankRefFrame = {
    ScaleXYZ = {1, 1, 1}*0.12; // Adjust scale as needed
    RGB = {1, 1, 1}; // Color of the reference frame (red)
};
};

AnySurfSTL OriginalSourceShank= 
{
  FileName = "C:\Users\Omar Bahig\Desktop\Morphing template\TibiaFibula\Tibiafibula_source.stl";
  ScaleXYZ = {1, 1, 1}/1000;
  AnyFunTransform3D &scale = ..CustomMarkerScaling1;        
  AnyDrawSurf stldraw = {
    FileName = .FileName;
    ScaleXYZ = {1, 1, 1}/1000;
    AnyFunTransform3D &scale = .scale;
    
    RGB = {0,0,1};
    Opacity = .....BonesOpacity.Shank;
    
  }; 
  AnyDrawRefFrame OrgShankRefFrame = {
    ScaleXYZ = {1, 1, 1}*0.1; // Adjust scale as needed
    RGB = {0, 0, 1}; // Color of the reference frame (red)
};
};  

};
}

Regards,
Omar

Omar,

I think you did not understand the method. In case of the femur you have your "real" landmarks next to the condyles of AnyBody femur, meaning that the deformation controlled by them will be local. The other landmarks just repeat the AnyBody femur and effectively morph it into itself.

For the tibia I can see that you were not lucky and/or did not register condylar landmarks to the source condylar landmarks, and your "fake" ankle landmark is now effectively on the condyles. This set of target landmarks leads to the squashed morphed shape:

You can also see that your green landmarks look like a tibia, but your purple like something rather short and flat, not looking like anything. You create a new bone using those landmarks and they should be in the right places. So when you construct your target landmark set (fake + real landmarks) it should be representing the target shape, not just some random landmarks put together without any meaning.

Regards,
Pavel

Dear Pavel @pgalibarov

Thank you for the above advice. I read the comments/ feedback too many times and I implemented them accordingly. I however might have missed something based on my understanding to the comments or maybe I did not get the trick :frowning: . I also tried to follow the steps discuss in the following post:Scaling a part of the bone but I am still lost!

I am struggling because AnyBody is new for me. So, please bear with me.

I now have a partial target femur. Here is a link to the files: PartialBonesMorphing – Google Drive
You can find the STLs (partial target femur, and the source femur), AnyScript draft for morphing and the picked points (using Meshlab) for morphing.

Here are the selected landmarks at the distal part of the partial femur. You can also view them through Anyscript file in the google drive.

Similarly, I selected the same landmarks + two fake landmarks (Fovea capitis and GTroch) on the source bone to control the proximal side:

When I run the script (there might be something wrong in the script), I got really weird morphed bones!!!

When I do not use the fake landmarks, I get something like this:
image

I appreciate your guidance.

Thank you in advance and thank you for your valuable time.

Best regards,
Omar

Hi Omar,

First of all, please fill information regarding organization you work/study in. This is a professional forum and we would like to understand who we provide help to.

Creating a morphing function using landmarks means the following. The function will transform the source bone in a such manner that landmarks A1...An will become B1...Bn. This means the source set of landmarks needs to be representative of the source bone, target set representative of the target bone.

You target set is meaningless at this point, because you throw landmarks together that represent 2 different bones. And because of that your morphing looks bad - it tries to deform the source bone in 2 bones at the same time. If you only use the distal landmarks your morph 1 bone into 1 bone, but you are lacking control over proximal region. The function will extrapolate and may come up with a good morphing or not.

To control the morphing on the whole bone, you need to create a target set of landmarks that represent the expected target bone surface. In your original model you had distal target femur aligned with the distal source femur, which allowed us to use the proximal source femur landmarks as "fake" proximal landmarks of the target to do a deformation.

When you use an abstract femur condyles not related to the source bone - you effectively try to morph the proximal source bone into itself, and the distal into a different unrelated condylar shape of another bone.

You need to create a set of consistent landmarks that relate to a single femur. My suggestion is:

  • use a rigid body transformation to bring your distal landmarks into the source bone position. Use distal source landmarks as target, and target distal landmarks as source to orient your target in the source bone position.
  • secondly, augment this new registered set of target landmarks with proximal landmarks of the source bone.

This way you will have a proximal shape of the source femur, and the distal shape of the target femur, but in the position of the source.

    AnySeg TargetFemurRegistered = 
    {
      Mass = 0; Jii = {0, 0, 0};
      AnyDrawSurf TargetFemur = 
      {
        FileName = "Femur preop_mirrored.stl";
        RGB = {256,256,0}/256;
        ScaleXYZ={1,1,1}*0.001;
        AnyFunTransform3D &ref = ..RegisterDistalLandmarks;
      };
    };

  
    AnyFunTransform3DLin2 RegisterDistalLandmarks = {
      Points1 =  
      {
        {1.4667782, -7.5853477, 40.188442}*0.001, //A
        {-1.3872523, -7.2713323, -42.379215}*0.001,  //B
        {10.828313, -25.093184, 28.204765}*0.001,  //C
        {-1.2590836, -28.866749, -33.724697}*0.001, //D
        {-32.232922, -6.8122239, -26.237719}*0.001, //E
        {-24.200073, -7.6667924, 21.916771}*0.001, //F
        {8.2392998, -20.800543, -3.7793131}*0.001, //G
        {37.0884, 3.49247, 16.5524}*0.001, //LatAntCondy
        {29.2783, -3.43503, -12.832}*0.001, //MedAntCondy

      };
      Points0 =  
       { 
         { -109.7923, 1.8660842, -181.06305 }*0.001, //A
         { -21.972107, -0.70646125, -185.23708 }*0.001, //B
         { -100.04708, -5.844811, -204.27739 }*0.001, //C
         { -30.220669, -0.4548822, -207.81369 }*0.001, //D
         { -41.645058, 34.963486, -185.07999 }*0.001, //E
         { -92.680977, 32.281628, -184.17714 }*0.001, //F
         { -67.408867, -1.9156438, -200.03989 }*0.001, //G
         {-87.4147, -37.7725, -168.43}*0.001,  //LatAntCondy
         {-55.6023, -33.2571, -180.494}*0.001, //MedAntCondy
       };
      Mode = VTK_LANDMARK_RIGIDBODY;
    
    };
  
  
    AnyFunTransform3DLin2 Affine =
    {
      Points0 =  
      {
        {1.4667782, -7.5853477, 40.188442}*0.001, //A
        {-1.3872523, -7.2713323, -42.379215}*0.001,  //B
        {10.828313, -25.093184, 28.204765}*0.001,  //C
        {-1.2590836, -28.866749, -33.724697}*0.001, //D
        {-32.232922, -6.8122239, -26.237719}*0.001, //E
        {-24.200073, -7.6667924, 21.916771}*0.001, //F
        {8.2392998, -20.800543, -3.7793131}*0.001, //G
        {37.0884, 3.49247, 16.5524}*0.001, //LatAntCondy
        {29.2783, -3.43503, -12.832}*0.001, //MedAntCondy
        {-10.852421, 364.76874, -16.379839}*0.001, // Fovea capitis
        {-18.9449, 363.053, 46.8883}*0.001 // Greater trochanter

      };
      
      Points1 =  
       { 
         .RegisterDistalLandmarks({ -109.7923, 1.8660842, -181.06305 }*0.001), //A
         .RegisterDistalLandmarks({ -21.972107, -0.70646125, -185.23708 }*0.001), //B
         .RegisterDistalLandmarks({ -100.04708, -5.844811, -204.27739 }*0.001), //C
         .RegisterDistalLandmarks({ -30.220669, -0.4548822, -207.81369 }*0.001), //D
         .RegisterDistalLandmarks({ -41.645058, 34.963486, -185.07999 }*0.001), //E
         .RegisterDistalLandmarks({ -92.680977, 32.281628, -184.17714 }*0.001), //F
         .RegisterDistalLandmarks({ -67.408867, -1.9156438, -200.03989 }*0.001), //G
         .RegisterDistalLandmarks({-87.4147, -37.7725, -168.43}*0.001),  //LatAntCondy
         .RegisterDistalLandmarks({-55.6023, -33.2571, -180.494}*0.001), //MedAntCondy
         {-10.852421, 364.76874, -16.379839}*0.001, //Fake points(Fovea Capitis)
         {-18.9449, 363.053, 46.8883}*0.001 // Fake point (GTroch)
         
       };
      Mode = VTK_LANDMARK_AFFINE;
    };

This should produce a picture like this:

I hope you my explanation is clear enough.

Kind regards,
Pavel

Thank you so much Pavel.

Regarding your first inquiry, I did share my organization information with Dave. I also changed my email to the corresponding organizational email so you know whom you are providing help to.

On top of that, thank you for the suggestion which fixes the issue I am having. I might contact you in the future when I implement it in my complete model :slight_smile:

Thank you for your continuous assistance.

Best regards,
Omar

1 Like

Dear Pavel @pgalibarov

I was wondering how the implementation of such morphing will be. I had a script of a template that morph complete bones. You can directly check the femur part because I started with it and am trying to figure out how to implement the morphing process. The above script (lets call it the template script) works for the model template and produced a nice morphing:

The morphing script (which we have been discussing for the past couple of weeks) resulted in the following transformation:


White =Target femur, blue= source femur, red =affineTransform, and green =RBFTransform.

I adjusted the template script to fit my case (distal femur only) following the steps in the tutorial (I might have missed something though!) but I am not quite sure if this how it should be!

AnyFolder Thigh =
{
AnyFunTransform3D &TSeg2ScaleFrame = ...BodyModel.Right.Leg.Seg.Thigh.Scale.T0;
AnyFunTransform3DLin ScaleFunction = {
ScaleMat = {{1,0,0},{0,1,0},{0,0,1}};
Offset = {0,0,0};
PreTransforms = {&.RBFTransform, &.ReverseTransform};
};
AnyFunTransform3DLin2 RegisterDistalLandmarks = {

    Points1 =  
    {      // Landmarks of the distal part of the source bone      
      {1.4667782, -7.5853477, 40.188442}*0.001, //A
      {-1.3872523, -7.2713323, -42.379215}*0.001, //B
      {10.828313, -25.093184, 28.204765}*0.001, //C
      {-1.2590836, -28.866749, -33.724697}*0.001, //D
      {-32.232922, -6.8122239, -26.237719}*0.001, //E
      {-24.200073, -7.6667924, 21.916771}*0.001, //F
      {8.2392998, -20.800543, -3.7793131}*0.001, //G
      {37.3533, 2.90245, 16.3532}*0.001, //H
      {30.2449, -6.11178, -14.9986}*0.001 //I
    };
    
  Points0 =  
  {  // Landmarks of the distal part of the target bone 
    {-2.9408944, -11.066492, 42.313583}*0.001, 
     {-8.5555677, -8.8688841, -45.743553}*0.001, 
     {-3.8385994, -35.679928, 34.283401}*0.001, 
     {-8.4392681, -39.179478, -36.761299}*0.001, 
     {-39.834358, -12.413182, -27.635822}*0.001, 
     {-34.514637, -10.991515, 23.638144}*0.001, 
     {-3.0491183, -30.168438, -1.5138963}*0.001,
     {35.6345, -2.0671, 13.8541}*0.001,
     {29.8057, -14.0313, -14.7399}*0.001
  };
  Mode = VTK_LANDMARK_RIGIDBODY;     
}; 
  AnyFunTransform3DLin2 AffineTransform = 
  {
    Points0 = .TSeg2ScaleFrame(Points0unscaled);
    AnyMatrix Points0unscaled = 
    { // landmarks on the distal part of the source bone + two source proximal points (fake points)
      {1.4667782, -7.5853477, 40.188442}*0.001, 
    {-1.3872523, -7.2713323, -42.379215}*0.001, 
    {10.828313, -25.093184, 28.204765}*0.001, 
    {-1.2590836, -28.866749, -33.724697}*0.001, 
    {-32.232922, -6.8122239, -26.237719}*0.001, 
    {-24.200073, -7.6667924, 21.916771}*0.001, 
    {8.2392998, -20.800543, -3.7793131}*0.001,
    {37.3533, 2.90245, 16.3532}*0.001,
    {30.2449, -6.11178, -14.9986}*0.001,
    {-10.852421, 364.76874, -16.379839}*0.001,  // Fovea capitis
    {-19.912188, 362.50815, 45.109447}*0.001   // GTroch
    };       
    
    Points1 = 
    { // landmarks of the registered distal part of the target bone + two source proximal points (fake points)
     .RegisterDistalLandmarks({-2.9408944, -11.066492, 42.313583}*0.001),     //A
     .RegisterDistalLandmarks({-8.5555677, -8.8688841, -45.743553}*0.001),  //B
     .RegisterDistalLandmarks({-3.8385994, -35.679928, 34.283401}*0.001),    //C
     .RegisterDistalLandmarks({-8.4392681, -39.179478, -36.761299}*0.001),   //D
     .RegisterDistalLandmarks({-39.834358, -12.413182, -27.635822}*0.001),    //E
     .RegisterDistalLandmarks({-34.514637, -10.991515, 23.638144}*0.001),    //F
     .RegisterDistalLandmarks({-3.0491183, -30.168438, -1.5138963}*0.001),   //G
     .RegisterDistalLandmarks({35.6345, -2.0671, 13.8541}*0.001), //H
     .RegisterDistalLandmarks({29.8057, -14.0313, -14.7399}*0.001), //I
     {-10.852421, 364.76874, -16.379839}*0.001,  // Fovea capitis
     {-19.912188, 362.50815, 45.109447}*0.001   // GTroch

    };       
    Mode = VTK_LANDMARK_AFFINE;
  };
  
  AnyFunTransform3DLin2 ReverseTransform = { 
    Points0 = .AffineTransform.Points1;
    Points1 = .AffineTransform.Points0;
    Mode = VTK_LANDMARK_RIGIDBODY;
  };
  
  AnyFunTransform3DLin2 RegistrationTransform = { // Used to register any target landmark/ nodes
    Points0 = .AffineTransform.Points1;
    Points1 = Main.Studies.HumanModel.BodyModel.Right.Leg.Seg.Thigh.Scale(.AffineTransform.Points0unscaled);
   Mode = VTK_LANDMARK_RIGIDBODY;
  };

   AnyFunTransform3DRBF RBFTransform= 
  {
    PreTransforms = {&.AffineTransform};
    RBFDef.Type = RBF_Triharmonic;
    RBFDef.Param = 0.2;
    Points0 = .AffineTransform.Points0;
    Points1 = .AffineTransform.Points1;
    BoundingBox.ScaleXYZ = 5*{1, 1, 1};
    BoundingBox.DivisionFactorXYZ = 5*{1, 1, 1};
    BoundingBoxOnOff = On;
  };      

};

When I run the script for the femur I got something like this:
image


White = Target femur, Blue = Original model source, Yellowish = morphed model bone

So, comparing the two processes (left = a draft morphing script, right= complete model morphing implementation), I do not feel morphing was successfully implemented in my complete model:

Your advice is appreciated.

Best regards,
Omar

Hi Omar,

I don't have a complete understanding of the problem as I have not looked into the details.

My understanding is that the only difference that should be there when you compare what you called a draft morphing script and a complete model morphing implementation is the use of TSeg2ScaleFrame that is used to transform the source points into the Scaling frame.

I think you have added the extra function RegisterDistalLandmarks from your draft model script to the complete model. I may be wrong here, but this RegisterDistalLandmarks should also pass the points on the source bone through the TSeg2ScaleFrame.

Please try this and see if it helps. Like I said, I am not a 100% sure if it's the right solution as I don't have a complete understanding of the problem.

Best regards,
Dave

1 Like

Thank you for your response Dave.

I would like to update you on what I tried.

  1. Your suggestion: adding TSeg2ScaleFrame to the source points in RegisterDistalLandmarks:

Here is part of the script for the thigh:
AnyFolder Thigh =
{
#if InverseDynamicModel== 0 | INV_DYN_EXCLUDE_RIGHT_LEG == 0
AnyFunTransform3D &TSeg2ScaleFrame = ...BodyModel.Right.Leg.Seg.Thigh.Scale.T0;

  AnyFunTransform3DLin ScaleFunction = {
    ScaleMat = {{1,0,0},{0,1,0},{0,0,1}};
    Offset = {0,0,0};
    PreTransforms = {&.RBFTransform,  &.ReverseTransform};
  };
  
 AnyFunTransform3DLin2 RegisterDistalLandmarks = { //Switching the points (Points1=LandmarksOnTheSource. Points0=LandmarksOnTheTarget)
  Points1 = .TSeg2ScaleFrame ( //cuz it is Rigid transform, Points1 = source points
  {     // Source femur points of the distal end 
    {1.4667782, -7.5853477, 40.188442}*0.001, //A
    {-1.3872523, -7.2713323, -42.379215}*0.001, //B
    {10.828313, -25.093184, 28.204765}*0.001, //C
    {-1.2590836, -28.866749, -33.724697}*0.001, //D
    {-32.232922, -6.8122239, -26.237719}*0.001, //E
    {-24.200073, -7.6667924, 21.916771}*0.001, //F
    {8.2392998, -20.800543, -3.7793131}*0.001, //G
    {37.3533, 2.90245, 16.3532}*0.001, //H
    {30.2449, -6.11178, -14.9986}*0.001 //I
  });
  
  Points0 = 
  {     // Target femur points of the distal end 
    {-2.9408944, -11.066492, 42.313583}*0.001, 
    {-8.5555677, -8.8688841, -45.743553}*0.001, 
    {-3.8385994, -35.679928, 34.283401}*0.001, 
    {-8.4392681, -39.179478, -36.761299}*0.001, 
    {-39.834358, -12.413182, -27.635822}*0.001, 
    {-34.514637, -10.991515, 23.638144}*0.001, 
    {-3.0491183, -30.168438, -1.5138963}*0.001,
    {35.6345, -2.0671, 13.8541}*0.001,
    {29.8057, -14.0313, -14.7399}*0.001
  };
  Mode = VTK_LANDMARK_RIGIDBODY;     
};

  AnyFunTransform3DLin2 AffineTransform = 
  {
    Points0 = .TSeg2ScaleFrame(Points0unscaled);
    AnyMatrix Points0unscaled = 
    {
      {1.4667782, -7.5853477, 40.188442}*0.001, 
      {-1.3872523, -7.2713323, -42.379215}*0.001, 
      {10.828313, -25.093184, 28.204765}*0.001, 
      {-1.2590836, -28.866749, -33.724697}*0.001, 
      {-32.232922, -6.8122239, -26.237719}*0.001, 
      {-24.200073, -7.6667924, 21.916771}*0.001, 
      {8.2392998, -20.800543, -3.7793131}*0.001,
      {37.3533, 2.90245, 16.3532}*0.001,
      {30.2449, -6.11178, -14.9986}*0.001,
      {-10.852421, 364.76874, -16.379839}*0.001,  // Fovea capitis
      {-19.912188, 362.50815, 45.109447}*0.001   // GTroch
    };
    
    Points1 = 
    { 
      .RegisterDistalLandmarks({-2.9408944, -11.066492, 42.313583}*0.001),     //A
      .RegisterDistalLandmarks({-8.5555677, -8.8688841, -45.743553}*0.001),  //B
      .RegisterDistalLandmarks({-3.8385994, -35.679928, 34.283401}*0.001),    //C
      .RegisterDistalLandmarks({-8.4392681, -39.179478, -36.761299}*0.001),   //D
      .RegisterDistalLandmarks({-39.834358, -12.413182, -27.635822}*0.001),    //E
      .RegisterDistalLandmarks({-34.514637, -10.991515, 23.638144}*0.001),    //F
      .RegisterDistalLandmarks({-3.0491183, -30.168438, -1.5138963}*0.001),   //G
      .RegisterDistalLandmarks({35.6345, -2.0671, 13.8541}*0.001),
      .RegisterDistalLandmarks({29.8057, -14.0313, -14.7399}*0.001),
      {-10.852421, 364.76874, -16.379839}*0.001,  // Fovea capitis
      {-19.912188, 362.50815, 45.109447}*0.001   // GTroch
    };
    Mode = VTK_LANDMARK_AFFINE;
  };
 
  AnyFunTransform3DLin2 ReverseTransform = {
    Points0 = .AffineTransform.Points1;
    Points1 = .AffineTransform.Points0;
    Mode = VTK_LANDMARK_RIGIDBODY;
  };
  
  AnyFunTransform3DLin2 RegistrationTransform = {
    Points0 = .AffineTransform.Points1;
    Points1 = Main.Studies.HumanModel.BodyModel.Right.Leg.Seg.Thigh.Scale(.AffineTransform.Points0unscaled);
   Mode = VTK_LANDMARK_RIGIDBODY;
  };

  AnyFunTransform3DRBF RBFTransform = {
  PreTransforms = {&.AffineTransform};
  RBFDef.Type = RBF_Triharmonic;  
  PolynomDegree = 1;
  Points0 = .AffineTransform.Points0;
  Points1 = .AffineTransform.Points1;
  BoundingBoxOnOff = On;
  BoundingBox.Type = BB_Cartesian;
  BoundingBox.ScaleXYZ={1,2,2}*1.2;
  BoundingBox.DivisionFactorXYZ ={1,1,1}*3;
}; 

#endif
};

I got the following output:

  1. I used TSeg2ScaleFrame on Points0 in RegisterDistalLandmarks but as I expected, I got more weird morphing (the femur got squashed).

  2. I included RegisterDistalLandmarks in the PreTransforms.
    AnyFunTransform3DLin ScaleFunction = {
    ScaleMat = {{1,0,0},{0,1,0},{0,0,1}};
    Offset = {0,0,0};
    PreTransforms = {&.RBFTransform,&.RegisterDistalLandmarks, &.ReverseTransform};
    };
    The idea as I understood from Pavel is to first align both bones at the source position/ orientation and then apply the affine transform. This allows us to use the proximal landmarks as control points to control extrapolation at the proximal part. With this, I also pass Points1 in RegisterDistalLandmarks through TSeg2ScaleFrame. I got the same output as in 1).

  3. Lastly, I did the same as 3) but without passing Points1 in RegisterDistalLandmarks through TSeg2ScaleFrame. I got the following:
    image

image

For me, 4) is the most reasonable but does not match the morphing I got in the external morphing script (figure on the left)


White= target, green= morphed bone in the morphing script, yellowish = morphed bone in the complete model

So, is it always the case that I should get the morphing in the complete model as the morphing i get in the external morphing script or is it possible that there might be slight discrepancies?

Best regards,
Omar

Omar,

Yes, you created 2 different functions to morph the bone, so the results are not exactly identic, but if you were to test how those control points morphed - they would be in the right place. Btw, even though they do not look identic - they are quite a good match and probably are within a 1 mm error range. Your proximal reconstruction has the largest error.

As well as that, the RBF transform based on landmarks is less detailed than an STL (RBF) transform.

And if you were expecting to get a full reconstruction of the femur just from the distal part. AnyBody is not designed to do that. It only morphs our generic bone into what was provided (landmarks). So it is up to you to construct the most accurate representation of the target femur.

Kind regards,
Pavel

Dear Pavel

Thank you for your reply. I however have a remark regarding the morphing pipeline. A few days ago, I came across a video you explained how to perform partial bone bone (this is the video (minute 42:00): https://www.youtube.com/watch?v=rSplWqcl04o). In the video the workflow of the morphing is slightly different (especially by adding the AnthropometricScaling transformation function). So,

  1. which pipeline show I implement (the one I shared in the previous post or the one in your video?)

  2. I was curious, why did you use AnthropometricScaling in your script (in the video)?

  3. And is the AnthropometricScaling function based on the scaling factor (Length of the target bone/Length of the source) as in the following sub-script?

AnyFolder Thigh = {
AnyVar GeomScale = (...AnthroSegmentLengths.Right.ThighLength / ...StandardParameters.Right.Thigh.Length);
AnyFunTransform3DLin ScaleFunction = {
ScaleMat ={{.GeomScale,0,0},{0,.GeomScale,0},{0,0,.GeomScale}};
Offset = {0,0,0};
};
};

Thank you in advance.

Best regards,
Omar

The anthropometric part was to make sure we can also control the length of the bone, which does not have all the landmarks.

As you can see in the previous code, when we use unscaled AnyBody bone, your target bone becomes shorter than what you expect. So this can be controlled by scaling all landmarks linearly to get the size you want. Your options are:

  1. Scale all target landmarks incl. actual distal points (this will change the shape of distal part)
  2. Scale only the "fake" landmarks and keep the distal part intact, but you need to make sure that your anthropometric scaling makes sense. One way to ensure that is to translate all your landmarks to the centroid of distal landmarks, scale using anthropometric scaling factor (GeomScale, for example), and then translate everything back.

And yes, the antropometric scaling law uses proportions of AnyBody bones and target bones to define scaling factor for each segment like in the code:

...AnthroSegmentLengths.Right.ThighLength / ...StandardParameters.Right.Thigh.Length

Thank you Pavel again.

I followed the pipeline from the video you for both sides (right an left). Here is the script:
SubjectSpecificScaling.any (18.8 KB)

I also registered the target bones using the following script (similar to the one in the video):
// Right thigh
Main.Studies.HumanModel.BodyModel.Right.Leg.Seg.Thigh = {
AnyFunTransform3D &CustomMarkerScaling = Main.Studies.HumanModel.SubjectSpecificScaling.Right.Thigh.RegProximalLandmarks; // to register other Nodes

AnyRefNode new = 
{
  AnyFloat posvec = Main.Studies.HumanModel.SubjectSpecificScaling.Right.Thigh.RegProximalLandmarks({{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_femur= 
  {
    FileName = "C:\Users\Omar Bahig\Downloads\ORIGIN~1\TKA-PR~1\GRANDC~1\AMMRV1~1.6TL\AMMR-P~1\APPLIC~1\MyModels\MOCAPM~1\Model\target\Femur_target.anysurf3";
    ScaleXYZ ={1,1,1}*0.001;
    RGB={0,1,0};
  };
};

};

// Right shank
Main.Studies.HumanModel.BodyModel.Right.Leg.Seg.Shank = {
AnyFunTransform3D &CustomMarkerScaling = Main.Studies.HumanModel.SubjectSpecificScaling.Right.Shank.RegProximalLandmarks;

AnyRefNode new = 
{
  AnyFloat posvec = Main.Studies.HumanModel.SubjectSpecificScaling.Right.Shank.RegProximalLandmarks({{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 = "C:\Users\Omar Bahig\Downloads\ORIGIN~1\TKA-PR~1\GRANDC~1\AMMRV1~1.6TL\AMMR-P~1\APPLIC~1\MyModels\MOCAPM~1\Model\target\Tibiafibula_target.anysurf3";
    ScaleXYZ ={1,1,1}*0.001;
    RGB={0,1,0};
  };
};

};

It seems that the morphed femur source bone and the target bone (green) have the same length but not the for the shank!. On top of that, I did not get a good overlay of the morphed bone and the target bone.

Any idea why did this happened?

Best,
Omar

I will let you spend a bit of time to analyze your own code. Unfortunately I do not have a lot of time to debug your code. But typically if something does not align - either you are aligning wrong objects, or the alignment function is wrong. Try using this one:

AnyFunTransform3DLin2 RegistrationTransform = {
Points0 = .AffineTransform.Points1;
Points1 = Main.Studies.HumanModel.BodyModel.Right.Leg.Seg.Thigh.Scale(.AffineTransform.Points0unscaled);
Mode = VTK_LANDMARK_RIGIDBODY;
};

I totally understand that my script is a bit time consuming to check. My question at this point ,before I spend more time on debugging the code again (cuz I did many times :frowning:) , "Is the pipeline in the video (I shared the link: you can check minute 42 and after) the right method to morph the partial bones?

Best Regards,
Omar

Yes, it is the right method in the described conditions. But not the only one.

1 Like

Dear @pgalibarov

With your help I managed to perform partial morphing for the femur and the tibia bones.
Regarding the alignment of the target on the morphed source bone, I used the following and it works (I got about 95% match with the external morphing script) :slight_smile:
//To register any STL, nodes etc.. Only proximal landmarks are used because these points belong to each STL separately (Using P0 and P1 does not work):

  AnyFunTransform3DLin2 RegistrationTransform = {
    Points0 = Main.Studies.HumanModel.SubjectSpecificScaling.Right.Shank.proximal_tibial_landmarks_target; 
    Points1 = Main.Studies.HumanModel.BodyModel.Right.Leg.Seg.Shank.Scale(Main.Studies.HumanModel.SubjectSpecificScaling.Right.Shank.proximal_tibial_landmarks_source);
   Mode = VTK_LANDMARK_RIGIDBODY;
  };

image

//=======================================
Now, the next step is to redefine the femur and tibia coordinate system (CS) "Center and axes" for the knee joint. As I understand, the femur and the tibia centers would be almost at the same point (to build the revolute/ hinge joint and preserve the shape of the joint) like the following: (Red= femur CS of the knee joint, Yellow=Tibia CS of the knee joint) and similar scenario for the patellofemoral joint (femur and patella CS for the patellofemoral joint in green).
image.

the CSs you see in the figure were there in the model template I have and it uses Analytical surface fitting technique using MATLAB. I have a bit of an idea of how to find the the center of the femur in both joints (TF and PF) with the use of the data points to fit the cylinder (femur condyles for TF joint, and femur groove for PF).
image

image

However, there was nothing in the model template to help me find that CS of the tibia and patella bones. What data points should be picked for the tibia for instance to find its center for the knee joint (similar to the patella for PF joint)? and in what direction (mediolateral? anteroposterior? etc.)

Is there an easy and direct way/ software to redefine the knee joint sRel and ARel for the femur and tibia, and the PF joint sRel and ARel than surface fit technique?

Is there a way to do it in AMS?

Best wishes,
Omar

Hi Omar,

I think it would be best if you can check with the author of this model template that you have about how they defined the Tibia and Patella CS. Otherwise, there is probably some literature out there that can describe some methods on how to extract joint centers from the bone surface of tibia/patella only.

You can read more about the TLEM model in the AMMR documentation page and eventually in references listed on the page. In the AMMR, there is also an option to use joint definitions based on bony landmarks (using medial and lateral epicondyles on the femur).

If you want to define your own joint, then you must also come up with the logic to define this joint. The software can't come up with the logic of how the joint center must be defined. There are tools in the software that can help you in implementing whatever logic you choose (for example, AnySurfCylinderFit, that can fit a parametric cylinder to a point cloud).

Having said that, I think if you have a combined scan of the leg, then you just need the joints center on the femur. To be honest, the axis of rotation is the most important. You can define the medial-lateral location on the axis arbitrarily (or on some convention, e.g., midpoint of the epicondyles). Then, if you can place all the bones in this scanned position (and you know the joint angle in advance, e.g., the scan is in neutral position), you can measure the joint center on the femur in the tibia and patella segmental frame using AnyKinLinear and AnyKinRotational. And, then you simply define reference nodes on the tibia and patella using these values.

Best regards,
Dave