def test_angles_creation(): dims = ("axis", "channel", "time") array = Angles() np.testing.assert_array_equal(x=array, y=xr.DataArray()) assert array.dims == dims array = Angles(MARKERS_DATA.values, time=MARKERS_DATA.time) is_expected_array(array, **EXPECTED_VALUES[57]) size = 10, 10, 100 array = Angles.from_random_data(size=size) assert array.shape == size assert array.dims == dims with pytest.raises(ValueError): Angles(ANALOGS_DATA)
def test_average_rt(): # TODO: investigate why this does not work # angles = Angles.from_random_data(size=(3, 1, 100)) # or # angles = Angles(np.arange(300).reshape((3, 1, 100))) angles = Angles(np.random.rand(3, 1, 100)) seq = "xyz" rt = Rototrans.from_euler_angles(angles, seq) rt_mean = Rototrans.from_averaged_rototrans(rt) angles_mean = Angles.from_rototrans(rt_mean, seq).isel(time=0) angles_mean_ref = Angles.from_rototrans(rt, seq).mean(dim="time") np.testing.assert_array_almost_equal(angles_mean, angles_mean_ref, decimal=2)
def test_average_rt(): seq = "xyz" angles_size = (3, 1, 100) ref_angles_from_rt_mean = [0.25265133, 0.57436872, 0.79133042] restart_seed() angles = Angles.from_random_data(size=angles_size) # min-max normalization to keep the angles low angles = angles.pipe(lambda x: (x - x.min()) / (x.max() - x.min())) rt = Rototrans.from_euler_angles(angles, seq) rt_mean = Rototrans.from_averaged_rototrans(rt) angles_from_rt_mean = Angles.from_rototrans(rt_mean, seq) np.testing.assert_almost_equal(angles_from_rt_mean.data.ravel(), ref_angles_from_rt_mean, decimal=4)
def rototrans_from_averaged_rototrans( caller: Callable, rt: xr.DataArray ) -> xr.DataArray: # arbitrary angle sequence seq = "xyz" target = rt.mean(dim="time").expand_dims("time", axis=-1) angles = Angles(np.ndarray((3, 1, 1))) def objective_function(x): angles[:3, 0, 0] = x rt = caller.from_euler_angles(angles, seq) return np.ravel(rt.meca.rotation - target.meca.rotation) initia_guess = Angles.from_rototrans(target, seq).squeeze() angles[:3, 0, 0] = least_squares(objective_function, initia_guess).x return caller.from_euler_angles(angles, seq, translations=target.meca.translation)
def test_construct_rt(): eye = Rototrans() np.testing.assert_equal(eye.time.size, 1) np.testing.assert_equal(eye.sel(time=0), np.eye(4)) eye = Rototrans.from_euler_angles() np.testing.assert_equal(eye.time.size, 1) np.testing.assert_equal(eye.sel(time=0), np.eye(4)) # Test the way to create a rt, but not when providing bot angles and sequence nb_frames = 10 random_vector = Angles(np.random.rand(3, 1, nb_frames)) # with angles rt_random_angles = Rototrans.from_euler_angles(angles=random_vector, angle_sequence="xyz") np.testing.assert_equal(rt_random_angles.time.size, nb_frames) np.testing.assert_equal(rt_random_angles[:-1, -1:, :], np.zeros((3, 1, nb_frames))) # Translation is 0 # with translation rt_random_translation = Rototrans.from_euler_angles( translations=random_vector) np.testing.assert_equal(rt_random_translation.time.size, nb_frames) np.testing.assert_equal( rt_random_translation[:3, :3, :], np.repeat(np.eye(3)[:, :, np.newaxis], nb_frames, axis=2), ) # rotation is eye3 np.arange(0, rt_random_angles.time.size / 0.5, 1 / 0.5) rt_with_time = Rototrans( rt_random_angles, time=np.arange(0, rt_random_angles.time.size / 100, 1 / 100), ) assert rt_with_time.time[-1] == 0.09 with pytest.raises(IndexError): Rototrans(data=np.zeros(1)) with pytest.raises(ValueError): Rototrans(data=np.zeros((4, 4, 1))) with pytest.raises(ValueError): Rototrans(data=np.ones((4, 4, 1))) with pytest.raises(IndexError): Rototrans.from_euler_angles( angles=random_vector[..., :5], translations=random_vector, angle_sequence="x", ) with pytest.raises(IndexError): Rototrans.from_euler_angles(angles=random_vector, angle_sequence="x") with pytest.raises(ValueError): Rototrans.from_euler_angles(angles=random_vector, angle_sequence="nop")
def test_euler2rot_rot2euleur(seq, angles=ANGLES, epsilon=EPSILON): if seq == "zyzz": angles_to_test = angles[:3, ...] else: angles_to_test = angles[:len(seq), ...] r = Rototrans.from_euler_angles(angles=angles_to_test, angle_sequence=seq) a = Angles.from_rototrans(rt=r, angle_sequence=seq) np.testing.assert_array_less((a - angles_to_test).meca.abs().sum(), epsilon)
def test_rototrans_creation(): dims = ("row", "col", "time") array = Rototrans() np.testing.assert_array_equal(x=array, y=xr.DataArray(np.eye(4)[..., np.newaxis])) assert array.dims == dims array = Rototrans(MARKERS_DATA.values, time=MARKERS_DATA.time) is_expected_array(array, **EXPECTED_VALUES[67]) size = 4, 4, 100 array = Rototrans.from_random_data(size=size) assert array.shape == size assert array.dims == dims with pytest.raises(ValueError): Angles(ANALOGS_DATA)
def test_rotate(): n_frames = 100 n_markers = 10 angles = Angles.from_random_data(size=(3, 1, n_frames)) rt = Rototrans.from_euler_angles(angles, "xyz") markers = Markers.from_random_data(size=(3, n_markers, n_frames)) rotated_markers = Markers.from_rototrans(markers, rt) expected_rotated_marker = np.ndarray((4, n_markers, n_frames)) for marker in range(n_markers): for frame in range(n_frames): expected_rotated_marker[:, marker, frame] = np.dot( rt.isel(time=frame), markers.isel(channel=marker, time=frame), ) np.testing.assert_array_almost_equal(rotated_markers, expected_rotated_marker, decimal=10) rotated_markers = Markers.from_rototrans(markers.isel(time=0), rt.isel(time=0)) expected_rotated_marker = np.ndarray(rotated_markers.shape) for marker in range(n_markers): expected_rotated_marker[:, marker] = np.dot( rt.isel(time=0), markers.isel(channel=marker, time=0)) np.testing.assert_array_almost_equal(rotated_markers, expected_rotated_marker, decimal=10) rotated_markers = Markers.from_rototrans(markers, rt.isel(time=0)) expected_rotated_marker = np.ndarray(rotated_markers.shape) for marker in range(n_markers): expected_rotated_marker[:, marker] = np.dot(rt.isel(time=0), markers.isel(channel=marker)) np.testing.assert_array_almost_equal(rotated_markers, expected_rotated_marker, decimal=10) with pytest.raises(ValueError): Markers.from_rototrans(markers.isel(time=0), rt)
def test_rt_transpose(): n_frames = 10 angles = Angles.from_random_data(size=(3, 1, n_frames)) rt = Rototrans.from_euler_angles(angles, angle_sequence="xyz") rt_t = Rototrans.from_transposed_rototrans(rt) rt_t_expected = np.zeros((4, 4, n_frames)) rt_t_expected[3, 3, :] = 1 for row in range(rt.row.size): for col in range(rt.col.size): for frame in range(rt.time.size): rt_t_expected[col, row, frame] = rt[row, col, frame] for frame in range(rt.time.size): rt_t_expected[:3, 3, frame] = -rt_t_expected[:3, :3, frame].dot( rt[:3, 3, frame]) np.testing.assert_array_almost_equal(rt_t, rt_t_expected, decimal=10)
def from_random_data(cls, distribution: str = "normal", size: tuple = (3, 1, 100), **kwargs) -> xr.DataArray: """ Create random data from a specified distribution (normal by default) using random walk. Arguments: distribution: Distribution available in [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions) size: Shape of the desired array kwargs: Keyword argument(s) to be passed to numpy.random.`distribution` Returns: Random rototrans `xarray.DataArray` sampled from a given distribution !!! example To instantiate a `Rototrans` with some random data sampled from a normal distribution: ```python from pyomeca import Rototrans n_frames = 100 size = 4, 4, n_frames rt = Rototrans.from_random_data(size=size) ``` You can choose any distribution available in [numpy.random](https://docs.scipy.org/doc/numpy-1.14.0/reference/routines.random.html#distributions): ```python rt = Rototrans.from_random_data(distribution="uniform", size=size, low=1, high=10) ``` """ return Rototrans.from_euler_angles( Angles.from_random_data(distribution, size=(3, 1, size[-1]), **kwargs), "xyz", )
def test_rototrans_creation(): dims = ("row", "col", "time") array = Rototrans() np.testing.assert_array_equal(x=array, y=xr.DataArray(np.eye(4)[..., np.newaxis])) assert array.dims == dims data = Markers(MARKERS_DATA.values) array = Rototrans.from_markers( origin=data.isel(channel=[0]), axis_1=data.isel(channel=[0, 1]), axis_2=data.isel(channel=[0, 2]), axes_name="xy", axis_to_recalculate="y", ) is_expected_array(array, **EXPECTED_VALUES[67]) size = 4, 4, 100 array = Rototrans.from_random_data(size=size) assert array.shape == size assert array.dims == dims with pytest.raises(ValueError): Angles(ANALOGS_DATA)
def rototrans_from_euler_angles( caller: Callable, angles: Optional[xr.DataArray] = None, angle_sequence: Optional[str] = None, translations: Optional[xr.DataArray] = None, ): if angles is None: angles = Angles() if translations is None: translations = Angles() if angle_sequence is None: angle_sequence = "" # Convert special zyzz angle sequence to zyz if angle_sequence == "zyzz": angles[2, :, :] -= angles[0, :, :] angle_sequence = "zyz" # If the user asked for a pure rotation if angles.time.size != 0 and translations.time.size == 0: translations = Angles(np.zeros((3, 1, angles.time.size))) # If the user asked for a pure translation if angles.time.size == 0 and translations.time.size != 0: angles = Angles(np.zeros((0, 1, translations.time.size))) # Sanity checks if angles.time.size != translations.time.size: raise IndexError( "Angles and translations must have the same number of frames. " f"You have translation = {translations.shape} and angles = {angles.shape}" ) if angles.axis.size != len(angle_sequence): raise IndexError( "Angles and angles_sequence must be the same size. " f"You have angles axis = {angles.axis.size} and angle_sequence length = {len(angle_sequence)}" ) if angles.time.size == 0: return caller() empty_rt = np.repeat(np.eye(4)[..., np.newaxis], repeats=angles.time.size, axis=2) rt = empty_rt.copy() for i in range(angles.axis.size): a = angles[i, ...] matrix_to_prod = empty_rt.copy() if angle_sequence[i] == "x": # [[1, 0 , 0 ], # [0, cos(a), -sin(a)], # [0, sin(a), cos(a)]] matrix_to_prod[1, 1, :] = np.cos(a) matrix_to_prod[1, 2, :] = -np.sin(a) matrix_to_prod[2, 1, :] = np.sin(a) matrix_to_prod[2, 2, :] = np.cos(a) elif angle_sequence[i] == "y": # [[ cos(a), 0, sin(a)], # [ 0 , 1, 0 ], # [-sin(a), 0, cos(a)]] matrix_to_prod[0, 0, :] = np.cos(a) matrix_to_prod[0, 2, :] = np.sin(a) matrix_to_prod[2, 0, :] = -np.sin(a) matrix_to_prod[2, 2, :] = np.cos(a) elif angle_sequence[i] == "z": # [[cos(a), -sin(a), 0], # [sin(a), cos(a), 0], # [0 , 0 , 1]] matrix_to_prod[0, 0, :] = np.cos(a) matrix_to_prod[0, 1, :] = -np.sin(a) matrix_to_prod[1, 0, :] = np.sin(a) matrix_to_prod[1, 1, :] = np.cos(a) else: raise ValueError( "angle_sequence must be a permutation of axes (e.g. 'xyz', 'yzx', ...)" ) rt = np.einsum("ijk,jlk->ilk", rt, matrix_to_prod) # Put the translations rt[:-1, -1:, :] = translations[:3, ...] return caller(rt)
from itertools import permutations import numpy as np import pytest from pyomeca import Angles, Rototrans, Markers SEQ = (["".join(p) for i in range(1, 4) for p in permutations("xyz", i)] + ["zyzz"] + ["zxz"]) SEQ = [s for s in SEQ if s not in ["yxz"]] EPSILON = 1e-12 ANGLES = Angles(np.random.rand(4, 1, 100)) @pytest.mark.parametrize("seq", SEQ) def test_euler2rot_rot2euleur(seq, angles=ANGLES, epsilon=EPSILON): if seq == "zyzz": angles_to_test = angles[:3, ...] else: angles_to_test = angles[:len(seq), ...] r = Rototrans.from_euler_angles(angles=angles_to_test, angle_sequence=seq) a = Angles.from_rototrans(rt=r, angle_sequence=seq) np.testing.assert_array_less((a - angles_to_test).meca.abs().sum(), epsilon) def test_construct_rt(): eye = Rototrans() np.testing.assert_equal(eye.time.size, 1) np.testing.assert_equal(eye.sel(time=0), np.eye(4))
rt_length=100) vtkModelMid = VtkModel(vtkWindow, markers_color=(0, 0, 1), markers_size=10.0, markers_opacity=0.5, rt_length=100) vtkModelFromC3d = VtkModel(vtkWindow, markers_color=(0, 1, 0), markers_size=10.0, markers_opacity=0.5, rt_length=100) # Create some RotoTrans attached to the first model all_rt_real = [] all_rt_real.append( Rototrans.from_euler_angles(angles=Angles(np.zeros((3, 1, 1))), angle_sequence="yxz", translations=d[:, [0], [0]])) all_rt_real.append( Rototrans.from_euler_angles(angles=Angles(np.zeros((3, 1, 1))), angle_sequence="yxz", translations=d[:, [0], [0]])) # Create some Rototrans attached to the second model one_rt = Rototrans.from_markers( origin=d[:, [3, 5], :].mean("channel", keepdims=True), axis_1=d[:, [4, 3], :], axis_2=d[:, [4, 5], :], axes_name="zx", axis_to_recalculate="z", )