Generalized Procrustes analysis from TPS file#

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.decomposition import PCA

from ktch.landmark import GeneralizedProcrustesAnalysis
from ktch.io import read_tps

Reading TPS file#

df_triangles = read_tps("./landmarks_triangle.tps", as_frame=True)
df_triangles
x y
specimen_id coord_id
0 0 -0.531046 -0.280265
1 -0.177303 1.107969
2 0.850131 1.064210
1 0 -0.480121 -0.145592
1 -0.172134 0.871833
... ... ... ...
48 1 -0.153270 0.980145
2 0.692266 0.797412
49 0 0.286409 -0.353412
1 0.213187 0.819529
2 0.905297 1.175739

150 rows × 2 columns

fig, ax = plt.subplots()
sns.scatterplot(
    data = df_triangles,
    x="x",y="y", 
    hue="specimen_id", style="coord_id",ax=ax
    )
ax.set_aspect('equal')
ax.legend(loc="upper left", bbox_to_anchor=(1, 1))
<matplotlib.legend.Legend at 0x7f9ab2b9c750>
../../_images/948fd2f65f1f26ace149f6a3b437012e790d0aee5c4e1ecf440fad73e1e6666c.png

GPA#

gpa = GeneralizedProcrustesAnalysis().set_output(transform="pandas")
df_coords = df_triangles.unstack().swaplevel(1, 0, axis=1).sort_index(axis=1)
df_coords.columns = [dim +"_"+ str(landmark_idx) for landmark_idx,dim in df_coords.columns]
df_coords

df_shapes = gpa.fit_transform(df_coords)
df_shapes
x_0 y_0 x_1 y_1 x_2 y_2
specimen_id
0 -0.308597 -0.638893 -0.182918 0.294741 0.491515 0.344152
1 -0.372170 -0.487439 -0.196930 0.072799 0.569100 0.414639
10 -0.397810 -0.561799 -0.035105 0.124281 0.432915 0.437517
11 -0.373001 -0.613285 -0.047049 0.202706 0.420049 0.410578
12 -0.252883 -0.620455 -0.273924 0.334570 0.526807 0.285885
13 -0.254776 -0.594599 -0.302713 0.305677 0.557490 0.288922
14 -0.382938 -0.569850 0.082585 0.152466 0.300353 0.417384
15 -0.219379 -0.552450 -0.364963 0.300367 0.584341 0.252083
16 -0.320254 -0.494104 -0.293554 0.133409 0.613809 0.360695
17 -0.339839 -0.603099 -0.181722 0.224615 0.521561 0.378484
18 -0.164834 -0.213849 -0.460238 0.018214 0.625072 0.195635
19 -0.210419 -0.539588 -0.379704 0.296860 0.590123 0.242728
2 -0.366470 -0.605654 -0.106607 0.200327 0.473077 0.405327
20 -0.258137 -0.504275 -0.364340 0.209567 0.622478 0.294708
21 -0.277779 -0.566517 -0.306550 0.252162 0.584329 0.314355
22 -0.225383 -0.676497 -0.160397 0.424545 0.385780 0.251952
23 -0.258136 -0.676376 -0.146729 0.388877 0.404865 0.287499
24 -0.280657 -0.525423 -0.330598 0.207082 0.611255 0.318341
25 -0.215283 -0.505609 -0.397859 0.256911 0.613142 0.248698
26 -0.298488 -0.529990 -0.307053 0.192821 0.605542 0.337170
27 -0.278208 -0.639379 -0.222520 0.327345 0.500728 0.312034
28 -0.279793 -0.576087 -0.296568 0.259852 0.576362 0.316235
29 -0.338916 -0.453648 -0.273892 0.073030 0.612808 0.380618
3 -0.322704 -0.611777 -0.202289 0.251470 0.524992 0.360307
30 -0.274640 -0.499224 -0.349921 0.186841 0.624561 0.312383
31 -0.298390 -0.477677 -0.329797 0.139820 0.628186 0.337857
32 -0.176230 -0.605882 -0.325374 0.402546 0.501605 0.203336
33 -0.172471 -0.478251 -0.435125 0.275359 0.607597 0.202892
34 -0.210237 -0.662379 -0.213822 0.425334 0.424059 0.237045
35 -0.361559 -0.629282 -0.000719 0.232784 0.362278 0.396499
36 -0.325999 -0.656920 -0.093643 0.296540 0.419643 0.360380
37 -0.219459 -0.656613 -0.228236 0.408965 0.447695 0.247648
38 -0.215290 -0.490939 -0.405274 0.241976 0.620563 0.248962
39 -0.191609 -0.543643 -0.385955 0.321385 0.577564 0.222258
4 -0.305071 -0.667015 -0.110763 0.329092 0.415834 0.337923
40 -0.180713 -0.544016 -0.389828 0.333606 0.570541 0.210410
41 -0.292477 -0.527482 -0.315848 0.196632 0.608325 0.330850
42 -0.339789 -0.505137 -0.257395 0.124155 0.597184 0.380983
43 -0.280431 -0.667834 -0.154254 0.355587 0.434685 0.312248
44 -0.152249 -0.556962 -0.384447 0.378016 0.536697 0.178947
45 -0.188147 -0.585896 -0.348036 0.368714 0.536182 0.217182
46 -0.097869 -0.420238 -0.481151 0.297762 0.579020 0.122476
47 -0.143479 -0.576299 -0.363282 0.407693 0.506761 0.168606
48 -0.085238 -0.549413 -0.377233 0.444315 0.462472 0.105097
49 -0.337442 -0.635403 -0.130729 0.261232 0.468171 0.374171
5 -0.272869 -0.612984 -0.265899 0.305389 0.538768 0.307596
6 -0.318659 -0.540386 -0.273455 0.182147 0.592114 0.358240
7 -0.302466 -0.635791 -0.197977 0.297888 0.500443 0.337903
8 -0.231475 -0.657097 -0.223183 0.396410 0.454658 0.260687
9 -0.162853 -0.625506 -0.292329 0.437954 0.455182 0.187552
df_shapes_vis = df_shapes.copy()
df_shapes_vis.columns = pd.MultiIndex.from_tuples([[int(landmark_idx), dim] for dim, landmark_idx in [idx.split("_") for idx in df_shapes_vis.columns]], names=["coord_id","dim"])
df_shapes_vis.sort_index(axis=1, inplace=True)
df_shapes_vis = df_shapes_vis.swaplevel(0,1,axis=1).stack(level=1)
df_shapes_vis
/tmp/ipykernel_1967/3231015902.py:4: FutureWarning: The previous implementation of stack is deprecated and will be removed in a future version of pandas. See the What's New notes for pandas 2.1.0 for details. Specify future_stack=True to adopt the new implementation and silence this warning.
  df_shapes_vis = df_shapes_vis.swaplevel(0,1,axis=1).stack(level=1)
dim x y
specimen_id coord_id
0 0 -0.308597 -0.638893
1 -0.182918 0.294741
2 0.491515 0.344152
1 0 -0.372170 -0.487439
1 -0.196930 0.072799
... ... ... ...
8 1 -0.223183 0.396410
2 0.454658 0.260687
9 0 -0.162853 -0.625506
1 -0.292329 0.437954
2 0.455182 0.187552

150 rows × 2 columns

fig, ax = plt.subplots()
sns.scatterplot(
    data = df_shapes_vis,
    x="x",y="y", 
    hue="specimen_id", style="coord_id",ax=ax
    )
ax.set_aspect('equal')
ax.legend(loc="upper left", bbox_to_anchor=(1, 1))
<matplotlib.legend.Legend at 0x7f9ab09e8310>
../../_images/75480c25fc3569849bbdb25bd9414247a079d5bc4ae90e64c8a3ff32ac8dba1a.png