Manipulating coordinate values#

import numpy as np
import scipy as sp

import matplotlib.pyplot as plt
import seaborn as sns

from ktch.datasets import load_outline_mosquito_wings
data_outline_mosquito_wings = load_outline_mosquito_wings(as_frame=True)
coords = data_outline_mosquito_wings.coords.to_numpy().reshape(-1,100,2)

Translation#

You can translate the coordinate values of outline data using list comprehension.

rng = np.random.default_rng()
dx = rng.normal(0, 1, size=coords.shape[0::2])
coords_translated = [coords[i] + dx[i] for i in range(len(coords))]
idx = 0

fig, ax = plt.subplots()
sns.lineplot(x=coords[idx][:,0], y=coords[idx][:,1], sort= False,estimator=None,ax=ax)
sns.lineplot(x=coords_translated[idx][:,0], y=coords_translated[idx][:,1], sort= False,estimator=None,ax=ax)
ax.set_aspect('equal')
print("displacement: ", dx[idx])
displacement:  [-0.82475076  0.48264143]
../../_images/e3d8abece96732a9ba7c139ab3c3daeaf448fb887ec12fcd58a48297a1ac94bb.png

If the coordinate values are stored as np.ndarray, you can simply add the displacement vectors after reshape(n_spesimens, 1, n_dim).

coords_translated_array = coords + dx.reshape(dx.shape[0], 1, dx.shape[1])

idx = 0

fig, ax = plt.subplots()
sns.lineplot(x=coords[idx][:,0], y=coords[idx][:,1], sort= False,estimator=None,ax=ax)
sns.lineplot(x=coords_translated_array[idx][:,0], y=coords_translated_array[idx][:,1], sort= False,estimator=None,ax=ax)
ax.set_aspect('equal')
print("displacement: ", dx[idx])
displacement:  [-0.82475076  0.48264143]
../../_images/e3d8abece96732a9ba7c139ab3c3daeaf448fb887ec12fcd58a48297a1ac94bb.png

Scaling#

s = rng.uniform(0.5,1.5, size=coords.shape[0])
coords_scaled = [s[i]*coords[i] for i in range(len(coords))]
idx = 0

fig, ax = plt.subplots()
sns.lineplot(x=coords[idx][:,0], y=coords[idx][:,1], sort= False,estimator=None,ax=ax)
sns.lineplot(x=coords_scaled[idx][:,0], y=coords_scaled[idx][:,1], sort= False,estimator=None,ax=ax)
ax.set_aspect('equal')
print("scale: ", s[idx])
scale:  1.0240422569647822
../../_images/0de1ee5a369c9d3f8b3d4c069d4cbace0e4246b72582d2511198ee02fa7317ed.png

If the coordinate values are stored as np.ndarray, you can simply add the displacement vectors after reshape(n_spesimens, 1, 1).

coords_scaled_arr = s.reshape(s.shape[0], 1, 1)*coords

idx = 0

fig, ax = plt.subplots()
sns.lineplot(x=coords[idx][:,0], y=coords[idx][:,1], sort= False,estimator=None,ax=ax)
sns.lineplot(x=coords_scaled_arr[idx][:,0], y=coords_scaled_arr[idx][:,1], sort= False,estimator=None,ax=ax)
ax.set_aspect('equal')
print("scale: ", s[idx])
scale:  1.0240422569647822
../../_images/0de1ee5a369c9d3f8b3d4c069d4cbace0e4246b72582d2511198ee02fa7317ed.png

Rotation#

We provide rotation_matrix_2d helper function to generate the rotation matrix.

Alternatively, you can use scipy.spatial.transform.Rotation or define by yourself.

from ktch.outline import rotation_matrix_2d
from scipy.spatial.transform import Rotation as R
/tmp/ipykernel_2422/3066678367.py:1: FutureWarning: The 'outline' module is deprecated and will be removed in version 0.9. Use 'harmonic' instead.
  from ktch.outline import rotation_matrix_2d
theta = np.pi/4
print(rotation_matrix_2d(theta))
print(R.from_rotvec(theta*np.array([0,0,1])).as_matrix()[0:2,0:2])
print(np.array([[np.cos(theta),-np.sin(theta)],[np.sin(theta), np.cos(theta)]]))
[[ 0.70710678 -0.70710678]
 [ 0.70710678  0.70710678]]
[[ 0.70710678 -0.70710678]
 [ 0.70710678  0.70710678]]
[[ 0.70710678 -0.70710678]
 [ 0.70710678  0.70710678]]
theta = rng.uniform(0, 2*np.pi, size=coords.shape[0])
coords_rotated = [(rotation_matrix_2d(theta[i]) @ coords[i].T).T for i in range(len(coords))]
idx = 0

fig, ax = plt.subplots()
sns.lineplot(x=coords[idx][:,0], y=coords[idx][:,1], sort= False,estimator=None,ax=ax)
sns.lineplot(x=coords_rotated[idx][:,0], y=coords_rotated[idx][:,1], sort= False,estimator=None,ax=ax)
ax.set_aspect('equal')
print("theta: ", theta[idx])
theta:  4.332608212194336
../../_images/5d4bb82995bb8f2fbb8df059fde1f74921890a3874a0dba32415bf1c9398d530.png

When the coordinate values are stored as np.ndarray, the rotated array is calculated by applying the matmul (@) operator on the array of rotation matrixes transposed to n_samples x n_dim x n_dim and the array of the coordinate values transposed to n_samples x n_dim x n_coordinates.

coords_rotated_arr = (rotation_matrix_2d(theta).transpose(2, 0, 1) @ coords.transpose(0, 2, 1)).transpose(0, 2, 1)

idx = 0

fig, ax = plt.subplots()
sns.lineplot(x=coords[idx][:,0], y=coords[idx][:,1], sort= False,estimator=None,ax=ax)
sns.lineplot(x=coords_rotated_arr[idx][:,0], y=coords_rotated_arr[idx][:,1], sort= False,estimator=None,ax=ax)
ax.set_aspect('equal')
print("scale: ", s[idx])
scale:  1.0240422569647822
../../_images/5d4bb82995bb8f2fbb8df059fde1f74921890a3874a0dba32415bf1c9398d530.png

Changing the starting point#

The starting point (arclength parameter \(t = 0\) ) is often adjusted for aligning the outlines.

You can use the numpy.roll function for the purpose.

new_staring_points = np.array([rng.integers(0,len(coord)) for coord in coords], dtype=int)
coords_changed_start = [np.roll(coords[i], -new_staring_points[i], axis=0) for i in range(len(coords))]
idx = 0

fig, ax = plt.subplots(1,2, figsize=(12,7))
sns.lineplot(x=coords[idx][:,0], y=coords[idx][:,1], sort= False,estimator=None,ax=ax[0])
sns.lineplot(x=coords_changed_start[idx][:,0], y=coords_changed_start[idx][:,1], 
             sort= False,color="C1",estimator=None,ax=ax[1])
ax[0].set_aspect('equal')
ax[1].set_aspect('equal')
print("starting point: ", new_staring_points[idx])
starting point:  26
../../_images/124c2f6e6d1f40d0f72faee3aeed2f697d66f0c1e97c50a9b398122208692ac1.png