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
/tmp/ipykernel_1982/3480311241.py:2: DeprecationWarning: 
Pyarrow will become a required dependency of pandas in the next major release of pandas (pandas 3.0),
(to allow more performant data types, such as the Arrow string type, and better interoperability with other libraries)
but was not found to be installed on your system.
If this would cause problems for you,
please provide us feedback at https://github.com/pandas-dev/pandas/issues/54466
        
  import pandas as pd

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 0x7f8ca65f1310>
../../_images/50ddb7ff8dd126004272e9088daec0d8f3a8ab2c75aa23f30a5cccbe3685b6c4.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_1982/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 0x7f8ca879cd10>
../../_images/db6e8c6a9c0675ab66a8848b4d413d2f84fcfa1e50f06f505f066a2bc5adebb1.png