From eac7ea773b84389c4d4433b7a3d0fc49201b853a Mon Sep 17 00:00:00 2001 From: Ben Wilhelm Date: Mon, 3 Feb 2025 14:36:22 +0100 Subject: [PATCH 1/9] Implemented rotate3D. Changed rotate to have the correct rotation direction when rotating around the y-axis. --- pypulseq/rotate3D.py | 95 +++++++++++ pypulseq/tests/test_rotation3D_vs_rotation.py | 161 ++++++++++++++++++ src/pypulseq/__init__.py | 1 + src/pypulseq/rotate.py | 13 +- 4 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 pypulseq/rotate3D.py create mode 100644 pypulseq/tests/test_rotation3D_vs_rotation.py diff --git a/pypulseq/rotate3D.py b/pypulseq/rotate3D.py new file mode 100644 index 00000000..9234a3d4 --- /dev/null +++ b/pypulseq/rotate3D.py @@ -0,0 +1,95 @@ +from types import SimpleNamespace +from typing import List, Union + +import numpy as np +from numpy import typing as npt + +from pypulseq.add_gradients import add_gradients +from pypulseq.opts import Opts +from pypulseq.scale_grad import scale_grad +from pypulseq.utils.tracing import trace, trace_enabled + + +def __get_grad_abs_mag(grad: SimpleNamespace) -> np.ndarray: + if grad.type == 'trap': + return abs(grad.amplitude) + return np.max(np.abs(grad.waveform)) + + +def rotate3D(*args: SimpleNamespace, rotation_matrix: np.ndarray[np.float64], system: Union[Opts, None] = None) -> List[SimpleNamespace]: + """ + Rotates the corresponding gradient(s) by the provided rotation matrix. Non-gradient(s) are not affected. + + See also `pypulseq.rotate.rotate()` and `pypulseq.Sequence.sequence.add_block()`. + + Parameters + ---------- + args : SimpleNamespace + Gradient(s). + rotation_matrix : np.ndarray[np.float64] + 3x3 rotation matrix by which the gradient(s) are rotated. + system : Opts, default=Opts() + System limits. + + Returns + ------- + rotated_grads : [SimpleNamespace] + Rotated gradient(s). + """ + if system is None: + system = Opts.default + + if rotation_matrix.shape != (3, 3): + raise ValueError('The rotation matrix must have shape (3, 3).') + + # First create indexes of the objects to be bypassed or rotated + axes = ['x', 'y', 'z'] + events_to_rotate_dict = {} + i_bypass = [] + + for i in range(len(args)): + event = args[i] + if (event.type != 'grad' and event.type != 'trap'): + i_bypass.append(i) + else: + if event.channel not in axes: + raise ValueError('Invalid event channel. Expected one of ' + str(axes)) + elif event.channel in events_to_rotate_dict: + raise ValueError('More than one gradient for the same channel provided, channel: ' + str(event.channel)) + else: + events_to_rotate_dict[event.channel] = event + + # Measure of relevant amplitude + max_mag = 0 + for axis in axes: + if axis in events_to_rotate_dict: + event = events_to_rotate_dict[axis] + max_mag = max(max_mag, __get_grad_abs_mag(event)) + fthresh = 1e-6 + thresh = fthresh * max_mag + + # Rotate the events (gradients) + rotated_gradients = [] + for j in range(3): + grad_out_curr = None + for i in range(3): + if axes[i] not in events_to_rotate_dict or abs(rotation_matrix[j, i]) < fthresh: + continue + scaled_gradient = scale_grad(grad=events_to_rotate_dict[axes[i]], scale=rotation_matrix[j, i]) + scaled_gradient.channel = axes[j] + if grad_out_curr is None: + grad_out_curr = scaled_gradient + else: + grad_out_curr = add_gradients((grad_out_curr, scaled_gradient), system=system) + if grad_out_curr is not None and __get_grad_abs_mag(grad_out_curr) >= thresh: + rotated_gradients.append(grad_out_curr) + + # Return + bypass = np.take(args, i_bypass) + return_grads = [*bypass, *rotated_gradients] + + if trace_enabled(): + for grad in return_grads: + grad.trace = trace() + + return return_grads diff --git a/pypulseq/tests/test_rotation3D_vs_rotation.py b/pypulseq/tests/test_rotation3D_vs_rotation.py new file mode 100644 index 00000000..fffe5aa1 --- /dev/null +++ b/pypulseq/tests/test_rotation3D_vs_rotation.py @@ -0,0 +1,161 @@ +import math +from types import SimpleNamespace + +import numpy as np + +import pypulseq +from pypulseq import rotate, rotate3D + + +def compare_gradient_sets(grad_set_A: list[SimpleNamespace], grad_set_B: list[SimpleNamespace], tolerance: float = 0) -> bool: + """ + Compare two sets of gradients for equality. Each set may contain up to three gradients. + The gradients must have channel 'x', 'y' or 'z'. In each set there must not be two gradients with the same channel. + Allow a tolerance for numeric values. + + Parameters + ---------- + grad_set_A : list[SimpleNamespace] + The first set of gradients to compare. Must have channel 'x', 'y' or 'z'. Must not have two gradients with the same channel. + grad_set_B : list[SimpleNamespace] + The second set of gradients to compare. Must have channel 'x', 'y' or 'z'. Must not have two gradients with the same channel. + tolerance : float = (default) 0 + The tolerance to allow for comparing numeric values. + + Returns + ------- + is_equal : bool + True if the two sets of gradients are equal with the tollerance. + """ + channel_list = ['x', 'y', 'z'] + + def check_gradient_set_and_get_channel_grad_dict(gradient_set): + channel_grad_dict = {} + assert len(gradient_set) <= 3, 'Each gradient set must not have more than three gradients.' + for grad in gradient_set: + assert hasattr(grad, 'channel'), 'Gradients must have attribute "channel".' + assert grad.channel in channel_list, 'Gradients must have channel "x", "y" or "z".' + assert grad.channel not in channel_grad_dict, 'There must not be two gradients with the same channel in each set.' + channel_grad_dict[grad.channel] = grad + return channel_grad_dict + + channel_grad_dict_A = check_gradient_set_and_get_channel_grad_dict(grad_set_A) + channel_grad_dict_B = check_gradient_set_and_get_channel_grad_dict(grad_set_B) + + def compare_gradients(grad_A, grad_B): + grad_A_dict = grad_A.__dict__ + grad_B_dict = grad_B.__dict__ + if len(grad_A_dict) != len(grad_B_dict): + return False + for key in grad_A_dict.keys(): + if not key in grad_B_dict.keys(): + return False + elif type(grad_A_dict[key]) == float and type(grad_B_dict[key]) == float: + if grad_A_dict[key] - grad_B_dict[key] > tolerance: + return False + elif type(grad_A_dict[key]) == np.float64 and type(grad_B_dict[key]) == np.float64: + if grad_A_dict[key] - grad_B_dict[key] > tolerance: + return False + elif type(grad_A_dict[key]) == np.ndarray and type(grad_B_dict[key]) == np.ndarray: + if grad_A_dict[key].dtype == np.float64 and grad_B_dict[key].dtype == np.float64: + if (grad_A_dict[key] - grad_B_dict[key] > tolerance).any(): + return False + else: + if (grad_A_dict[key] != grad_B_dict[key]).any(): + return False + elif grad_A_dict[key] != grad_B_dict[key]: + return False + return True + + for channel in channel_list: + if (channel in channel_grad_dict_A) != (channel in channel_grad_dict_B): + return False + if channel in channel_grad_dict_A: + grad_A = channel_grad_dict_A[channel] + grad_B = channel_grad_dict_B[channel] + if compare_gradients(grad_A, grad_B) == False: + return False + + return True + + + +def test_rotation3D_vs_rotation(): + """ + Create some trapezoids and extended trapezoids and compare the results of applying rotate and rotate3D. + """ + print("---- test_rotation3D_vs_rotation() ----") + + def get_rotation_matrix(channel, angle_radians): + cos_a = math.cos(angle_radians) + sin_a = math.sin(angle_radians) + if channel == 'x': + rotation_matrix = [[ 1, 0, 0 ], + [ 0, cos_a, -sin_a ], + [ 0, sin_a, cos_a ]] + elif channel == 'y': + rotation_matrix = [[ cos_a, 0, sin_a ], + [ 0, 1, 0 ], + [ -sin_a, 0, cos_a ]] + elif channel == 'z': + rotation_matrix = [[ cos_a, -sin_a, 0 ], + [ sin_a, cos_a, 0 ], + [ 0, 0, 1 ]] + else: + raise ValueError('Channel must be "x", "y" or "z".') + return np.array(rotation_matrix, dtype='float64') + + channel_list = ['x', 'y', 'z'] + + # prepare gradients + grad_list = [ + pypulseq.make_trapezoid(channel='x', amplitude=1, duration=13), + pypulseq.make_trapezoid(channel='y', amplitude=1, duration=13), + pypulseq.make_trapezoid(channel='z', amplitude=1, duration=13), + pypulseq.make_trapezoid(channel='x', amplitude=2, duration=5), + pypulseq.make_trapezoid(channel='y', amplitude=2, duration=5), + pypulseq.make_trapezoid(channel='z', amplitude=2, duration=5), + pypulseq.make_extended_trapezoid('x', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('y', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('z', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('x', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('y', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('z', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('x', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), + pypulseq.make_extended_trapezoid('y', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), + pypulseq.make_extended_trapezoid('z', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]) + ] + + for angle_degree in [0.0, 0.1, 1, 2, 3, 60, 90, 180, 360] + list(range(10, 450, 30)) + [-0.1, -1, -90, -180, -360, -400]: + print("angle_degree:", angle_degree) + angle_radians = angle_degree * math.pi / 180 + + for rotation_axis in channel_list: + rotation_matrix = get_rotation_matrix(rotation_axis, angle_radians) + + for grad in grad_list: + # apply rotations + grads_rotated = rotate(*[grad], angle=angle_radians, axis=rotation_axis) + grads_rotated3D = rotate3D(*[grad], rotation_matrix=rotation_matrix) + grads_rotated_double = rotate(*grads_rotated, angle=angle_radians, axis=rotation_axis) + grads_rotated3D_double = rotate3D(*grads_rotated3D, rotation_matrix=rotation_matrix) + grads_rotated3D_double_2 = rotate3D(*[grad], rotation_matrix=rotation_matrix @ rotation_matrix) + + # check results + # print("------") + # print("angle_degree:", angle_degree) + # print("rotation_axis:", rotation_axis) + # print("grads_rotated:", grads_rotated) + # print("grads_rotated3D:", grads_rotated3D) + # print("grads_rotated_double:", grads_rotated_double) + # print("grads_rotated3D_double:", grads_rotated3D_double) + # print("grads_rotated3D_double_2:", grads_rotated3D_double_2) + assert compare_gradient_sets(grads_rotated, grads_rotated3D, tolerance=1e-6), 'Result of rotate and rotate3D should be the same!' + assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double, tolerance=1e-6), 'Result of double rotate and rotate3D should be the same!' + assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double_2, tolerance=1e-6), 'Result of second double rotate and rotate3D should be the same!' + + print("Tests ok.") + + +if __name__ == '__main__': + test_rotation3D_vs_rotation() diff --git a/src/pypulseq/__init__.py b/src/pypulseq/__init__.py index 58587d08..d8fddda3 100644 --- a/src/pypulseq/__init__.py +++ b/src/pypulseq/__init__.py @@ -58,6 +58,7 @@ def round_half_up(n, decimals=0): from pypulseq.opts import Opts from pypulseq.points_to_waveform import points_to_waveform from pypulseq.rotate import rotate +from pypulseq.rotate3D import rotate3D from pypulseq.scale_grad import scale_grad from pypulseq.split_gradient import split_gradient from pypulseq.split_gradient_at import split_gradient_at diff --git a/src/pypulseq/rotate.py b/src/pypulseq/rotate.py index bedce55e..0f85bae3 100644 --- a/src/pypulseq/rotate.py +++ b/src/pypulseq/rotate.py @@ -1,5 +1,6 @@ from types import SimpleNamespace from typing import List, Union +from warnings import warn import numpy as np @@ -20,7 +21,10 @@ def rotate(*args: SimpleNamespace, angle: float, axis: str, system: Union[Opts, Rotates the corresponding gradient(s) about the given axis by the specified amount. Gradients parallel to the rotation axis and non-gradient(s) are not affected. Possible rotation axes are 'x', 'y' or 'z'. - See also `pypulseq.Sequence.sequence.add_block()`. + When using rotate() around the y-axis the rotation direction is reversed compared to previous versions to be consistent with rotate3D(). + There is no change in behaviour of rotate() for rotations around the x- or z-axis. + + See also `pypulseq.rotate3D.rotate3D()` and `pypulseq.Sequence.sequence.add_block()`. Parameters ---------- @@ -54,6 +58,13 @@ def rotate(*args: SimpleNamespace, angle: float, axis: str, system: Union[Opts, if len(axes_to_rotate) != 2: raise ValueError('Incorrect axes specification.') + if axis == 'y': + warning_message = 'When using rotate() around the y-axis the rotation direction is reversed ' + warning_message += 'compared to previous versions to be consistent with rotate3D().' + warning_message += 'There is no change in behaviour of rotate() for rotations around the x- or z-axis.' + warn(warning_message, stacklevel=2) + axes_to_rotate = [axes_to_rotate[1], axes_to_rotate[0]] # reverse the list to preserve the correct handiness of the rotation matrix + for i in range(len(args)): event = args[i] From 43bf433da3b1b1da6d7ffc9a4263299a0a7911c1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:46:14 +0000 Subject: [PATCH 2/9] [pre-commit] auto fixes from pre-commit hooks --- pypulseq/rotate3D.py | 7 +- pypulseq/tests/test_rotation3D_vs_rotation.py | 64 ++++++++++--------- src/pypulseq/rotate.py | 11 ++-- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/pypulseq/rotate3D.py b/pypulseq/rotate3D.py index 9234a3d4..b88dd961 100644 --- a/pypulseq/rotate3D.py +++ b/pypulseq/rotate3D.py @@ -2,7 +2,6 @@ from typing import List, Union import numpy as np -from numpy import typing as npt from pypulseq.add_gradients import add_gradients from pypulseq.opts import Opts @@ -16,7 +15,9 @@ def __get_grad_abs_mag(grad: SimpleNamespace) -> np.ndarray: return np.max(np.abs(grad.waveform)) -def rotate3D(*args: SimpleNamespace, rotation_matrix: np.ndarray[np.float64], system: Union[Opts, None] = None) -> List[SimpleNamespace]: +def rotate3D( + *args: SimpleNamespace, rotation_matrix: np.ndarray[np.float64], system: Union[Opts, None] = None +) -> List[SimpleNamespace]: """ Rotates the corresponding gradient(s) by the provided rotation matrix. Non-gradient(s) are not affected. @@ -49,7 +50,7 @@ def rotate3D(*args: SimpleNamespace, rotation_matrix: np.ndarray[np.float64], sy for i in range(len(args)): event = args[i] - if (event.type != 'grad' and event.type != 'trap'): + if event.type != 'grad' and event.type != 'trap': i_bypass.append(i) else: if event.channel not in axes: diff --git a/pypulseq/tests/test_rotation3D_vs_rotation.py b/pypulseq/tests/test_rotation3D_vs_rotation.py index fffe5aa1..59456991 100644 --- a/pypulseq/tests/test_rotation3D_vs_rotation.py +++ b/pypulseq/tests/test_rotation3D_vs_rotation.py @@ -7,9 +7,11 @@ from pypulseq import rotate, rotate3D -def compare_gradient_sets(grad_set_A: list[SimpleNamespace], grad_set_B: list[SimpleNamespace], tolerance: float = 0) -> bool: +def compare_gradient_sets( + grad_set_A: list[SimpleNamespace], grad_set_B: list[SimpleNamespace], tolerance: float = 0 +) -> bool: """ - Compare two sets of gradients for equality. Each set may contain up to three gradients. + Compare two sets of gradients for equality. Each set may contain up to three gradients. The gradients must have channel 'x', 'y' or 'z'. In each set there must not be two gradients with the same channel. Allow a tolerance for numeric values. @@ -25,17 +27,19 @@ def compare_gradient_sets(grad_set_A: list[SimpleNamespace], grad_set_B: list[Si Returns ------- is_equal : bool - True if the two sets of gradients are equal with the tollerance. + True if the two sets of gradients are equal with the tolerance. """ channel_list = ['x', 'y', 'z'] - + def check_gradient_set_and_get_channel_grad_dict(gradient_set): channel_grad_dict = {} assert len(gradient_set) <= 3, 'Each gradient set must not have more than three gradients.' for grad in gradient_set: assert hasattr(grad, 'channel'), 'Gradients must have attribute "channel".' assert grad.channel in channel_list, 'Gradients must have channel "x", "y" or "z".' - assert grad.channel not in channel_grad_dict, 'There must not be two gradients with the same channel in each set.' + assert ( + grad.channel not in channel_grad_dict + ), 'There must not be two gradients with the same channel in each set.' channel_grad_dict[grad.channel] = grad return channel_grad_dict @@ -50,10 +54,9 @@ def compare_gradients(grad_A, grad_B): for key in grad_A_dict.keys(): if not key in grad_B_dict.keys(): return False - elif type(grad_A_dict[key]) == float and type(grad_B_dict[key]) == float: - if grad_A_dict[key] - grad_B_dict[key] > tolerance: - return False - elif type(grad_A_dict[key]) == np.float64 and type(grad_B_dict[key]) == np.float64: + elif (type(grad_A_dict[key]) == float and type(grad_B_dict[key]) == float) or ( + type(grad_A_dict[key]) == np.float64 and type(grad_B_dict[key]) == np.float64 + ): if grad_A_dict[key] - grad_B_dict[key] > tolerance: return False elif type(grad_A_dict[key]) == np.ndarray and type(grad_B_dict[key]) == np.ndarray: @@ -75,32 +78,25 @@ def compare_gradients(grad_A, grad_B): grad_B = channel_grad_dict_B[channel] if compare_gradients(grad_A, grad_B) == False: return False - - return True + return True def test_rotation3D_vs_rotation(): - """ + """ Create some trapezoids and extended trapezoids and compare the results of applying rotate and rotate3D. """ - print("---- test_rotation3D_vs_rotation() ----") + print('---- test_rotation3D_vs_rotation() ----') def get_rotation_matrix(channel, angle_radians): cos_a = math.cos(angle_radians) sin_a = math.sin(angle_radians) if channel == 'x': - rotation_matrix = [[ 1, 0, 0 ], - [ 0, cos_a, -sin_a ], - [ 0, sin_a, cos_a ]] + rotation_matrix = [[1, 0, 0], [0, cos_a, -sin_a], [0, sin_a, cos_a]] elif channel == 'y': - rotation_matrix = [[ cos_a, 0, sin_a ], - [ 0, 1, 0 ], - [ -sin_a, 0, cos_a ]] + rotation_matrix = [[cos_a, 0, sin_a], [0, 1, 0], [-sin_a, 0, cos_a]] elif channel == 'z': - rotation_matrix = [[ cos_a, -sin_a, 0 ], - [ sin_a, cos_a, 0 ], - [ 0, 0, 1 ]] + rotation_matrix = [[cos_a, -sin_a, 0], [sin_a, cos_a, 0], [0, 0, 1]] else: raise ValueError('Channel must be "x", "y" or "z".') return np.array(rotation_matrix, dtype='float64') @@ -123,11 +119,13 @@ def get_rotation_matrix(channel, angle_radians): pypulseq.make_extended_trapezoid('z', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), pypulseq.make_extended_trapezoid('x', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), pypulseq.make_extended_trapezoid('y', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), - pypulseq.make_extended_trapezoid('z', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]) + pypulseq.make_extended_trapezoid('z', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), ] - for angle_degree in [0.0, 0.1, 1, 2, 3, 60, 90, 180, 360] + list(range(10, 450, 30)) + [-0.1, -1, -90, -180, -360, -400]: - print("angle_degree:", angle_degree) + for angle_degree in ( + [0.0, 0.1, 1, 2, 3, 60, 90, 180, 360] + list(range(10, 450, 30)) + [-0.1, -1, -90, -180, -360, -400] + ): + print('angle_degree:', angle_degree) angle_radians = angle_degree * math.pi / 180 for rotation_axis in channel_list: @@ -150,11 +148,17 @@ def get_rotation_matrix(channel, angle_radians): # print("grads_rotated_double:", grads_rotated_double) # print("grads_rotated3D_double:", grads_rotated3D_double) # print("grads_rotated3D_double_2:", grads_rotated3D_double_2) - assert compare_gradient_sets(grads_rotated, grads_rotated3D, tolerance=1e-6), 'Result of rotate and rotate3D should be the same!' - assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double, tolerance=1e-6), 'Result of double rotate and rotate3D should be the same!' - assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double_2, tolerance=1e-6), 'Result of second double rotate and rotate3D should be the same!' - - print("Tests ok.") + assert compare_gradient_sets( + grads_rotated, grads_rotated3D, tolerance=1e-6 + ), 'Result of rotate and rotate3D should be the same!' + assert compare_gradient_sets( + grads_rotated_double, grads_rotated3D_double, tolerance=1e-6 + ), 'Result of double rotate and rotate3D should be the same!' + assert compare_gradient_sets( + grads_rotated_double, grads_rotated3D_double_2, tolerance=1e-6 + ), 'Result of second double rotate and rotate3D should be the same!' + + print('Tests ok.') if __name__ == '__main__': diff --git a/src/pypulseq/rotate.py b/src/pypulseq/rotate.py index 0f85bae3..b1fb2592 100644 --- a/src/pypulseq/rotate.py +++ b/src/pypulseq/rotate.py @@ -21,8 +21,8 @@ def rotate(*args: SimpleNamespace, angle: float, axis: str, system: Union[Opts, Rotates the corresponding gradient(s) about the given axis by the specified amount. Gradients parallel to the rotation axis and non-gradient(s) are not affected. Possible rotation axes are 'x', 'y' or 'z'. - When using rotate() around the y-axis the rotation direction is reversed compared to previous versions to be consistent with rotate3D(). - There is no change in behaviour of rotate() for rotations around the x- or z-axis. + When using rotate() around the y-axis the rotation direction is reversed compared to previous versions to be consistent with rotate3D(). + There is no change in behavior of rotate() for rotations around the x- or z-axis. See also `pypulseq.rotate3D.rotate3D()` and `pypulseq.Sequence.sequence.add_block()`. @@ -61,9 +61,12 @@ def rotate(*args: SimpleNamespace, angle: float, axis: str, system: Union[Opts, if axis == 'y': warning_message = 'When using rotate() around the y-axis the rotation direction is reversed ' warning_message += 'compared to previous versions to be consistent with rotate3D().' - warning_message += 'There is no change in behaviour of rotate() for rotations around the x- or z-axis.' + warning_message += 'There is no change in behavior of rotate() for rotations around the x- or z-axis.' warn(warning_message, stacklevel=2) - axes_to_rotate = [axes_to_rotate[1], axes_to_rotate[0]] # reverse the list to preserve the correct handiness of the rotation matrix + axes_to_rotate = [ + axes_to_rotate[1], + axes_to_rotate[0], + ] # reverse the list to preserve the correct handiness of the rotation matrix for i in range(len(args)): event = args[i] From a6f72d106549c7c7fc69411dbc4c09a380cb1cd1 Mon Sep 17 00:00:00 2001 From: Ben Wilhelm Date: Mon, 3 Feb 2025 14:58:28 +0100 Subject: [PATCH 3/9] Fix more ruff-linter warnings. --- pypulseq/tests/test_rotation3D_vs_rotation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pypulseq/tests/test_rotation3D_vs_rotation.py b/pypulseq/tests/test_rotation3D_vs_rotation.py index 59456991..fe5d9f99 100644 --- a/pypulseq/tests/test_rotation3D_vs_rotation.py +++ b/pypulseq/tests/test_rotation3D_vs_rotation.py @@ -51,8 +51,8 @@ def compare_gradients(grad_A, grad_B): grad_B_dict = grad_B.__dict__ if len(grad_A_dict) != len(grad_B_dict): return False - for key in grad_A_dict.keys(): - if not key in grad_B_dict.keys(): + for key in grad_A_dict: + if not key in grad_B_dict: return False elif (type(grad_A_dict[key]) == float and type(grad_B_dict[key]) == float) or ( type(grad_A_dict[key]) == np.float64 and type(grad_B_dict[key]) == np.float64 @@ -123,7 +123,7 @@ def get_rotation_matrix(channel, angle_radians): ] for angle_degree in ( - [0.0, 0.1, 1, 2, 3, 60, 90, 180, 360] + list(range(10, 450, 30)) + [-0.1, -1, -90, -180, -360, -400] + [0.0, 0.1, 1, 2, 3, 60, 90, 180, 360, *list(range(10, 450, 30)), -0.1, -1, -90, -180, -360, -400] ): print('angle_degree:', angle_degree) angle_radians = angle_degree * math.pi / 180 From 864799c711f9306165c5facebad6442825afe97b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:05:52 +0000 Subject: [PATCH 4/9] [pre-commit] auto fixes from pre-commit hooks --- pypulseq/tests/test_rotation3D_vs_rotation.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/pypulseq/tests/test_rotation3D_vs_rotation.py b/pypulseq/tests/test_rotation3D_vs_rotation.py index fe5d9f99..d026c0e0 100644 --- a/pypulseq/tests/test_rotation3D_vs_rotation.py +++ b/pypulseq/tests/test_rotation3D_vs_rotation.py @@ -122,9 +122,24 @@ def get_rotation_matrix(channel, angle_radians): pypulseq.make_extended_trapezoid('z', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), ] - for angle_degree in ( - [0.0, 0.1, 1, 2, 3, 60, 90, 180, 360, *list(range(10, 450, 30)), -0.1, -1, -90, -180, -360, -400] - ): + for angle_degree in [ + 0.0, + 0.1, + 1, + 2, + 3, + 60, + 90, + 180, + 360, + *list(range(10, 450, 30)), + -0.1, + -1, + -90, + -180, + -360, + -400, + ]: print('angle_degree:', angle_degree) angle_radians = angle_degree * math.pi / 180 From b3c2f9212c0e51348e6a2132e0e62aa0ce264c94 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:15:38 +0000 Subject: [PATCH 5/9] [pre-commit] auto fixes from pre-commit hooks --- pypulseq/tests/test_rotation3D_vs_rotation.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pypulseq/tests/test_rotation3D_vs_rotation.py b/pypulseq/tests/test_rotation3D_vs_rotation.py index d026c0e0..9fdaf11f 100644 --- a/pypulseq/tests/test_rotation3D_vs_rotation.py +++ b/pypulseq/tests/test_rotation3D_vs_rotation.py @@ -37,9 +37,9 @@ def check_gradient_set_and_get_channel_grad_dict(gradient_set): for grad in gradient_set: assert hasattr(grad, 'channel'), 'Gradients must have attribute "channel".' assert grad.channel in channel_list, 'Gradients must have channel "x", "y" or "z".' - assert ( - grad.channel not in channel_grad_dict - ), 'There must not be two gradients with the same channel in each set.' + assert grad.channel not in channel_grad_dict, ( + 'There must not be two gradients with the same channel in each set.' + ) channel_grad_dict[grad.channel] = grad return channel_grad_dict @@ -163,15 +163,15 @@ def get_rotation_matrix(channel, angle_radians): # print("grads_rotated_double:", grads_rotated_double) # print("grads_rotated3D_double:", grads_rotated3D_double) # print("grads_rotated3D_double_2:", grads_rotated3D_double_2) - assert compare_gradient_sets( - grads_rotated, grads_rotated3D, tolerance=1e-6 - ), 'Result of rotate and rotate3D should be the same!' - assert compare_gradient_sets( - grads_rotated_double, grads_rotated3D_double, tolerance=1e-6 - ), 'Result of double rotate and rotate3D should be the same!' - assert compare_gradient_sets( - grads_rotated_double, grads_rotated3D_double_2, tolerance=1e-6 - ), 'Result of second double rotate and rotate3D should be the same!' + assert compare_gradient_sets(grads_rotated, grads_rotated3D, tolerance=1e-6), ( + 'Result of rotate and rotate3D should be the same!' + ) + assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double, tolerance=1e-6), ( + 'Result of double rotate and rotate3D should be the same!' + ) + assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double_2, tolerance=1e-6), ( + 'Result of second double rotate and rotate3D should be the same!' + ) print('Tests ok.') From ef9faac6e4fdc3d61b0d441bdc66344efd2b8d4f Mon Sep 17 00:00:00 2001 From: Ben Wilhelm Date: Thu, 13 Feb 2025 10:51:49 +0100 Subject: [PATCH 6/9] Moved rotate3D.py and test_rotation3D_vs_rotation.py to the correct folders. --- {pypulseq => src/pypulseq}/rotate3D.py | 0 {pypulseq/tests => tests}/test_rotation3D_vs_rotation.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {pypulseq => src/pypulseq}/rotate3D.py (100%) rename {pypulseq/tests => tests}/test_rotation3D_vs_rotation.py (100%) diff --git a/pypulseq/rotate3D.py b/src/pypulseq/rotate3D.py similarity index 100% rename from pypulseq/rotate3D.py rename to src/pypulseq/rotate3D.py diff --git a/pypulseq/tests/test_rotation3D_vs_rotation.py b/tests/test_rotation3D_vs_rotation.py similarity index 100% rename from pypulseq/tests/test_rotation3D_vs_rotation.py rename to tests/test_rotation3D_vs_rotation.py From 0fefe07e96af5b1bfda6abd610f7ae10160d5797 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:53:38 +0000 Subject: [PATCH 7/9] [pre-commit] auto fixes from pre-commit hooks --- tests/test_rotation3D_vs_rotation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_rotation3D_vs_rotation.py b/tests/test_rotation3D_vs_rotation.py index 9fdaf11f..4441f9a1 100644 --- a/tests/test_rotation3D_vs_rotation.py +++ b/tests/test_rotation3D_vs_rotation.py @@ -2,7 +2,6 @@ from types import SimpleNamespace import numpy as np - import pypulseq from pypulseq import rotate, rotate3D From a6ec920fd2f58530a801648d3178b224030f0b4f Mon Sep 17 00:00:00 2001 From: Patrick Schuenke Date: Mon, 2 Jun 2025 12:22:32 +0200 Subject: [PATCH 8/9] split tests and add missing abs() in tolerance check --- tests/test_rotation3D_vs_rotation.py | 216 +++++++++++++-------------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/tests/test_rotation3D_vs_rotation.py b/tests/test_rotation3D_vs_rotation.py index 4441f9a1..a6076bca 100644 --- a/tests/test_rotation3D_vs_rotation.py +++ b/tests/test_rotation3D_vs_rotation.py @@ -3,11 +3,28 @@ import numpy as np import pypulseq +import pytest from pypulseq import rotate, rotate3D +def get_rotation_matrix(channel, angle_rad): + cos_a = math.cos(angle_rad) + sin_a = math.sin(angle_rad) + if channel == 'x': + rotation_matrix = [[1, 0, 0], [0, cos_a, -sin_a], [0, sin_a, cos_a]] + elif channel == 'y': + rotation_matrix = [[cos_a, 0, sin_a], [0, 1, 0], [-sin_a, 0, cos_a]] + elif channel == 'z': + rotation_matrix = [[cos_a, -sin_a, 0], [sin_a, cos_a, 0], [0, 0, 1]] + else: + raise ValueError('Channel must be "x", "y" or "z".') + return np.array(rotation_matrix, dtype='float64') + + def compare_gradient_sets( - grad_set_A: list[SimpleNamespace], grad_set_B: list[SimpleNamespace], tolerance: float = 0 + grad_set_A: list[SimpleNamespace], + grad_set_B: list[SimpleNamespace], + tolerance: float = 0, ) -> bool: """ Compare two sets of gradients for equality. Each set may contain up to three gradients. @@ -48,24 +65,22 @@ def check_gradient_set_and_get_channel_grad_dict(gradient_set): def compare_gradients(grad_A, grad_B): grad_A_dict = grad_A.__dict__ grad_B_dict = grad_B.__dict__ - if len(grad_A_dict) != len(grad_B_dict): + if grad_A_dict.keys() != grad_B_dict.keys(): return False - for key in grad_A_dict: - if not key in grad_B_dict: - return False - elif (type(grad_A_dict[key]) == float and type(grad_B_dict[key]) == float) or ( - type(grad_A_dict[key]) == np.float64 and type(grad_B_dict[key]) == np.float64 - ): - if grad_A_dict[key] - grad_B_dict[key] > tolerance: + + for key, val_A in grad_A_dict.items(): + val_B = grad_B_dict[key] + + if isinstance(val_A, (float, np.float64)) and isinstance(val_B, (float, np.float64)): + if abs(val_A - val_B) > tolerance: return False - elif type(grad_A_dict[key]) == np.ndarray and type(grad_B_dict[key]) == np.ndarray: - if grad_A_dict[key].dtype == np.float64 and grad_B_dict[key].dtype == np.float64: - if (grad_A_dict[key] - grad_B_dict[key] > tolerance).any(): + elif isinstance(val_A, np.ndarray) and isinstance(val_B, np.ndarray): + if val_A.dtype in (np.float64, np.float32) and val_B.dtype in (np.float64, np.float32): # check if they are float arrays + if not np.allclose(val_A, val_B, atol=tolerance, rtol=0): # Using rtol=0 for pure absolute tolerance return False - else: - if (grad_A_dict[key] != grad_B_dict[key]).any(): + elif val_A.shape != val_B.shape or (val_A != val_B).any(): # For non-float arrays or if shapes differ return False - elif grad_A_dict[key] != grad_B_dict[key]: + elif val_A != val_B: return False return True @@ -80,100 +95,85 @@ def compare_gradients(grad_A, grad_B): return True +angle_deg_list = [0.0, 0.1, 1, 60, 90, 180, 360, 400.1, -0.1, -1, -90, -180, -360] + +grad_list = [ + pypulseq.make_trapezoid(channel='x', amplitude=1, duration=13), + pypulseq.make_trapezoid(channel='y', amplitude=1, duration=13), + pypulseq.make_trapezoid(channel='z', amplitude=1, duration=13), + pypulseq.make_trapezoid(channel='x', amplitude=2, duration=5), + pypulseq.make_trapezoid(channel='y', amplitude=2, duration=5), + pypulseq.make_trapezoid(channel='z', amplitude=2, duration=5), + pypulseq.make_extended_trapezoid('x', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('y', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('z', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('x', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('y', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('z', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), + pypulseq.make_extended_trapezoid('x', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), + pypulseq.make_extended_trapezoid('y', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), + pypulseq.make_extended_trapezoid('z', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), +] + +@pytest.mark.filterwarnings('ignore:When using rotate():UserWarning') +@pytest.mark.parametrize('angle_deg', angle_deg_list) +def test_rotation3D_vs_rotation(angle_deg): + """Compare results of rotate and rotate3D.""" -def test_rotation3D_vs_rotation(): - """ - Create some trapezoids and extended trapezoids and compare the results of applying rotate and rotate3D. - """ - print('---- test_rotation3D_vs_rotation() ----') - - def get_rotation_matrix(channel, angle_radians): - cos_a = math.cos(angle_radians) - sin_a = math.sin(angle_radians) - if channel == 'x': - rotation_matrix = [[1, 0, 0], [0, cos_a, -sin_a], [0, sin_a, cos_a]] - elif channel == 'y': - rotation_matrix = [[cos_a, 0, sin_a], [0, 1, 0], [-sin_a, 0, cos_a]] - elif channel == 'z': - rotation_matrix = [[cos_a, -sin_a, 0], [sin_a, cos_a, 0], [0, 0, 1]] - else: - raise ValueError('Channel must be "x", "y" or "z".') - return np.array(rotation_matrix, dtype='float64') + channel_list = ['x', 'y', 'z'] + angle_rad = angle_deg * math.pi / 180 + + for rotation_axis in channel_list: + rotation_matrix = get_rotation_matrix(rotation_axis, angle_rad) + + for grad in grad_list: + grads_rotated = rotate(*[grad], angle=angle_rad, axis=rotation_axis) + grads_rotated3D = rotate3D(*[grad], rotation_matrix=rotation_matrix) + + assert compare_gradient_sets(grads_rotated, grads_rotated3D, tolerance=1e-4), ( + f'Result of rotate and rotate3D should be the same! Angle: {angle_deg}, Axis: {rotation_axis}, Grad: {grad}' + ) + +@pytest.mark.filterwarnings('ignore:When using rotate():UserWarning') +@pytest.mark.parametrize('angle_deg', angle_deg_list) +def test_rotation3D_vs_rotation_double(angle_deg): + """Compare results of rotate and rotate3D.""" channel_list = ['x', 'y', 'z'] + angle_rad = angle_deg * math.pi / 180 - # prepare gradients - grad_list = [ - pypulseq.make_trapezoid(channel='x', amplitude=1, duration=13), - pypulseq.make_trapezoid(channel='y', amplitude=1, duration=13), - pypulseq.make_trapezoid(channel='z', amplitude=1, duration=13), - pypulseq.make_trapezoid(channel='x', amplitude=2, duration=5), - pypulseq.make_trapezoid(channel='y', amplitude=2, duration=5), - pypulseq.make_trapezoid(channel='z', amplitude=2, duration=5), - pypulseq.make_extended_trapezoid('x', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]), - pypulseq.make_extended_trapezoid('y', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]), - pypulseq.make_extended_trapezoid('z', [0, 5, 1, 3], convert_to_arbitrary=True, times=[1, 3, 4, 7]), - pypulseq.make_extended_trapezoid('x', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), - pypulseq.make_extended_trapezoid('y', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), - pypulseq.make_extended_trapezoid('z', [0, 5, 1, 3], convert_to_arbitrary=False, times=[1, 3, 4, 7]), - pypulseq.make_extended_trapezoid('x', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), - pypulseq.make_extended_trapezoid('y', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), - pypulseq.make_extended_trapezoid('z', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), - ] - - for angle_degree in [ - 0.0, - 0.1, - 1, - 2, - 3, - 60, - 90, - 180, - 360, - *list(range(10, 450, 30)), - -0.1, - -1, - -90, - -180, - -360, - -400, - ]: - print('angle_degree:', angle_degree) - angle_radians = angle_degree * math.pi / 180 - - for rotation_axis in channel_list: - rotation_matrix = get_rotation_matrix(rotation_axis, angle_radians) - - for grad in grad_list: - # apply rotations - grads_rotated = rotate(*[grad], angle=angle_radians, axis=rotation_axis) - grads_rotated3D = rotate3D(*[grad], rotation_matrix=rotation_matrix) - grads_rotated_double = rotate(*grads_rotated, angle=angle_radians, axis=rotation_axis) - grads_rotated3D_double = rotate3D(*grads_rotated3D, rotation_matrix=rotation_matrix) - grads_rotated3D_double_2 = rotate3D(*[grad], rotation_matrix=rotation_matrix @ rotation_matrix) - - # check results - # print("------") - # print("angle_degree:", angle_degree) - # print("rotation_axis:", rotation_axis) - # print("grads_rotated:", grads_rotated) - # print("grads_rotated3D:", grads_rotated3D) - # print("grads_rotated_double:", grads_rotated_double) - # print("grads_rotated3D_double:", grads_rotated3D_double) - # print("grads_rotated3D_double_2:", grads_rotated3D_double_2) - assert compare_gradient_sets(grads_rotated, grads_rotated3D, tolerance=1e-6), ( - 'Result of rotate and rotate3D should be the same!' - ) - assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double, tolerance=1e-6), ( - 'Result of double rotate and rotate3D should be the same!' - ) - assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double_2, tolerance=1e-6), ( - 'Result of second double rotate and rotate3D should be the same!' - ) - - print('Tests ok.') - - -if __name__ == '__main__': - test_rotation3D_vs_rotation() + for rotation_axis in channel_list: + rotation_matrix = get_rotation_matrix(rotation_axis, angle_rad) + + for grad in grad_list: + grads_rotated = rotate(*[grad], angle=angle_rad, axis=rotation_axis) + grads_rotated3D = rotate3D(*[grad], rotation_matrix=rotation_matrix) + + grads_rotated_double = rotate(*grads_rotated, angle=angle_rad, axis=rotation_axis) + grads_rotated3D_double = rotate3D(*grads_rotated3D, rotation_matrix=rotation_matrix) + + assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double, tolerance=1e-4), ( + f'Result of double rotate and rotate3D should be the same! Angle: {angle_deg}, Axis: {rotation_axis}, Grad: {grad}' + ) + +@pytest.mark.filterwarnings('ignore:When using rotate():UserWarning') +@pytest.mark.parametrize('angle_deg', angle_deg_list) +def test_rotation3D_vs_rotation_double_2(angle_deg): + """Compare results of rotate and rotate3D.""" + + channel_list = ['x', 'y', 'z'] + angle_rad = angle_deg * math.pi / 180 + + for rotation_axis in channel_list: + rotation_matrix = get_rotation_matrix(rotation_axis, angle_rad) + + for grad in grad_list: + grads_rotated = rotate(*[grad], angle=angle_rad, axis=rotation_axis) + + grads_rotated_double = rotate(*grads_rotated, angle=angle_rad, axis=rotation_axis) + grads_rotated3D_double_2 = rotate3D(*[grad], rotation_matrix=rotation_matrix @ rotation_matrix) + + + assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double_2, tolerance=1e-4), ( + f'Result of second double rotate and rotate3D should be the same! Angle: {angle_deg}, Axis: {rotation_axis}, Grad: {grad}' + ) From ab9c59282a4c8837e65e4f46d58df5ade2fe829a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 10:23:03 +0000 Subject: [PATCH 9/9] [pre-commit] auto fixes from pre-commit hooks --- tests/test_rotation3D_vs_rotation.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/test_rotation3D_vs_rotation.py b/tests/test_rotation3D_vs_rotation.py index a6076bca..7f5e131c 100644 --- a/tests/test_rotation3D_vs_rotation.py +++ b/tests/test_rotation3D_vs_rotation.py @@ -75,11 +75,16 @@ def compare_gradients(grad_A, grad_B): if abs(val_A - val_B) > tolerance: return False elif isinstance(val_A, np.ndarray) and isinstance(val_B, np.ndarray): - if val_A.dtype in (np.float64, np.float32) and val_B.dtype in (np.float64, np.float32): # check if they are float arrays - if not np.allclose(val_A, val_B, atol=tolerance, rtol=0): # Using rtol=0 for pure absolute tolerance - return False - elif val_A.shape != val_B.shape or (val_A != val_B).any(): # For non-float arrays or if shapes differ + if val_A.dtype in (np.float64, np.float32) and val_B.dtype in ( + np.float64, + np.float32, + ): # check if they are float arrays + if not np.allclose( + val_A, val_B, atol=tolerance, rtol=0 + ): # Using rtol=0 for pure absolute tolerance return False + elif val_A.shape != val_B.shape or (val_A != val_B).any(): # For non-float arrays or if shapes differ + return False elif val_A != val_B: return False return True @@ -95,6 +100,7 @@ def compare_gradients(grad_A, grad_B): return True + angle_deg_list = [0.0, 0.1, 1, 60, 90, 180, 360, 400.1, -0.1, -1, -90, -180, -360] grad_list = [ @@ -115,6 +121,7 @@ def compare_gradients(grad_A, grad_B): pypulseq.make_extended_trapezoid('z', [0, 3, 2, 3], convert_to_arbitrary=False, times=[1, 2, 3, 4]), ] + @pytest.mark.filterwarnings('ignore:When using rotate():UserWarning') @pytest.mark.parametrize('angle_deg', angle_deg_list) def test_rotation3D_vs_rotation(angle_deg): @@ -134,6 +141,7 @@ def test_rotation3D_vs_rotation(angle_deg): f'Result of rotate and rotate3D should be the same! Angle: {angle_deg}, Axis: {rotation_axis}, Grad: {grad}' ) + @pytest.mark.filterwarnings('ignore:When using rotate():UserWarning') @pytest.mark.parametrize('angle_deg', angle_deg_list) def test_rotation3D_vs_rotation_double(angle_deg): @@ -156,6 +164,7 @@ def test_rotation3D_vs_rotation_double(angle_deg): f'Result of double rotate and rotate3D should be the same! Angle: {angle_deg}, Axis: {rotation_axis}, Grad: {grad}' ) + @pytest.mark.filterwarnings('ignore:When using rotate():UserWarning') @pytest.mark.parametrize('angle_deg', angle_deg_list) def test_rotation3D_vs_rotation_double_2(angle_deg): @@ -173,7 +182,6 @@ def test_rotation3D_vs_rotation_double_2(angle_deg): grads_rotated_double = rotate(*grads_rotated, angle=angle_rad, axis=rotation_axis) grads_rotated3D_double_2 = rotate3D(*[grad], rotation_matrix=rotation_matrix @ rotation_matrix) - assert compare_gradient_sets(grads_rotated_double, grads_rotated3D_double_2, tolerance=1e-4), ( f'Result of second double rotate and rotate3D should be the same! Angle: {angle_deg}, Axis: {rotation_axis}, Grad: {grad}' )