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_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_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_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 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_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", )