Repository: PTB-MR/mrpro
Pull request #536: Add gradient and autodiff checks for linear operators
Author: login: ckolbPTB
Created: 2024-11-14T18:51:31Z | Merged: 2025-03-06T09:56:11Z

This supersedes #407 - it was too difficult to bring this up-to-date with main.

forward_mode_autodiff_of_linear_operator_test and gradient_of_linear_operator_test still need to be added to

- [x] ZeroOp
- [x] RearrangeOp
- [x] PCACompOp
- [x] GridSamplingOp

Do we want to run forward_mode_autodiff_of_linear_operator_test and gradient_of_linear_operator_test on all parametrize-options used for the adjointness tests?  


Diff:
diff --git a/tests/__init__.py b/tests/__init__.py
index 675fd5e26..c29efd34a 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,2 +1,10 @@
 from ._RandomGenerator import RandomGenerator
-from .helper import relative_image_difference, dotproduct_adjointness_test, operator_isometry_test, linear_operator_unitary_test, autodiff_test
+from .helper import (
+    relative_image_difference,
+    dotproduct_adjointness_test,
+    operator_isometry_test,
+    linear_operator_unitary_test,
+    autodiff_test,
+    gradient_of_linear_operator_test,
+    forward_mode_autodiff_of_linear_operator_test
+)
diff --git a/tests/conftest.py b/tests/conftest.py
index 012ef7397..2dc21c301 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -262,10 +262,10 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
 
 
 COMMON_MR_TRAJECTORIES = pytest.mark.parametrize(
-    ('im_shape', 'k_shape', 'nkx', 'nky', 'nkz', 'type_kx', 'type_ky', 'type_kz', 'type_k0', 'type_k1', 'type_k2'),
+    ('img_shape', 'k_shape', 'nkx', 'nky', 'nkz', 'type_kx', 'type_ky', 'type_kz', 'type_k0', 'type_k1', 'type_k2'),
     [
         (  # (0) 2d Cartesian single coil, no oversampling
-            (1, 1, 1, 96, 128),  # im_shape
+            (1, 1, 1, 96, 128),  # img_shape
             (1, 1, 1, 96, 128),  # k_shape
             (1, 1, 1, 1, 128),  # nkx
             (1, 1, 1, 96, 1),  # nky
@@ -278,7 +278,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'zero',  # type_k2
         ),
         (  # (1) 2d Cartesian single coil, with oversampling
-            (1, 1, 1, 96, 128),  # im_shape
+            (1, 1, 1, 96, 128),  # img_shape
             (1, 1, 1, 128, 192),  # k_shape
             (1, 1, 1, 1, 192),  # nkx
             (1, 1, 1, 128, 1),  # nky
@@ -291,7 +291,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'zero',  # type_k2
         ),
         (  # (2) 2d non-Cartesian mri with 2 coils
-            (1, 2, 1, 96, 128),  # im_shape
+            (1, 2, 1, 96, 128),  # img_shape
             (1, 2, 1, 16, 192),  # k_shape
             (1, 1, 1, 16, 192),  # nkx
             (1, 1, 1, 16, 192),  # nky
@@ -304,7 +304,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'zero',  # type_k2
         ),
         (  # (3) 2d Cartesian with irregular sampling
-            (1, 1, 1, 96, 128),  # im_shape
+            (1, 1, 1, 96, 128),  # img_shape
             (1, 1, 1, 1, 192),  # k_shape
             (1, 1, 1, 1, 192),  # nkx
             (1, 1, 1, 1, 192),  # nky
@@ -317,7 +317,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'zero',  # type_k2
         ),
         (  # (4) 2d single shot spiral
-            (1, 2, 1, 96, 128),  # im_shape
+            (1, 2, 1, 96, 128),  # img_shape
             (1, 2, 1, 1, 192),  # k_shape
             (1, 1, 1, 1, 192),  # nkx
             (1, 1, 1, 1, 192),  # nky
@@ -330,7 +330,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'zero',  # type_k2
         ),
         (  # (5) 3d single shot spiral, 4 coils, 2 other
-            (2, 4, 16, 32, 64),  # im_shape
+            (2, 4, 16, 32, 64),  # img_shape
             (2, 4, 1, 64, 1),  # k_shape
             (2, 1, 1, 64, 1),  # nkx
             (2, 1, 1, 64, 1),  # nky
@@ -343,7 +343,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'zero',  # type_k2
         ),
         (  # (6) 3d non-uniform, 3 coils, 2 other
-            (2, 3, 10, 12, 14),  # im_shape
+            (2, 3, 10, 12, 14),  # img_shape
             (2, 3, 6, 8, 10),  # k_shape
             (2, 1, 6, 8, 10),  # nkx
             (2, 1, 6, 8, 10),  # nky
@@ -356,7 +356,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'non-uniform',  # type_k2
         ),
         (  # (7) 2d non-uniform cine with 2 cardiac phases, 3 coils, same traj for each phase
-            (2, 3, 1, 64, 64),  # im_shape
+            (2, 3, 1, 64, 64),  # img_shape
             (2, 3, 1, 18, 128),  # k_shape
             (1, 1, 1, 18, 128),  # nkx
             (1, 1, 1, 18, 128),  # nky
@@ -369,7 +369,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'zero',  # type_k2
         ),
         (  # (8) 2d non-uniform cine with 2 cardiac phases, 3 coils
-            (2, 3, 1, 64, 64),  # im_shape
+            (2, 3, 1, 64, 64),  # img_shape
             (2, 3, 1, 18, 128),  # k_shape
             (2, 1, 1, 18, 128),  # nkx
             (2, 1, 1, 18, 128),  # nky
@@ -382,7 +382,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'zero',  # type_k2
         ),
         (  # (9) 2d cartesian cine with 2 cardiac phases, 3 coils
-            (2, 3, 1, 96, 128),  # im_shape
+            (2, 3, 1, 96, 128),  # img_shape
             (2, 3, 1, 128, 192),  # k_shape
             (2, 1, 1, 1, 192),  # nkx
             (2, 1, 1, 128, 1),  # nky
@@ -395,7 +395,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'zero',  # type_k2
         ),
         (  # (10) radial phase encoding (RPE), 3 coils, with oversampling in both FFT and non-uniform directions
-            (2, 3, 6, 8, 10),  # im_shape
+            (2, 3, 6, 8, 10),  # img_shape
             (2, 3, 8, 10, 12),  # k_shape
             (2, 1, 1, 1, 12),  # nkx
             (2, 1, 8, 10, 1),  # nky
@@ -408,7 +408,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'non-uniform',  # type_k2
         ),
         (  # (11) radial phase encoding (RPE), 2 coils with non-Cartesian sampling along readout
-            (2, 2, 6, 8, 10),  # im_shape
+            (2, 2, 6, 8, 10),  # img_shape
             (2, 2, 8, 10, 12),  # k_shape
             (2, 1, 1, 1, 12),  # nkx
             (2, 1, 8, 10, 1),  # nky
@@ -421,7 +421,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'non-uniform',  # type_k2
         ),
         (  # (12) stack of stars, 3 other, 2 coil, oversampling in both FFT and non-uniform directions
-            (3, 2, 10, 8, 8),  # im_shape
+            (3, 2, 10, 8, 8),  # img_shape
             (3, 2, 12, 8, 10),  # k_shape
             (3, 1, 1, 8, 10),  # nkx
             (3, 1, 1, 8, 10),  # nky
@@ -434,7 +434,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'uniform',  # type_k2
         ),
         (  # (13) stack of stars, (2,2,4) other, 3 coil, trajectory is different along second other dimension
-            (2, 2, 4, 3, 8, 6, 6),  # im_shape
+            (2, 2, 4, 3, 8, 6, 6),  # img_shape
             (2, 2, 4, 3, 12, 6, 10),  # k_shape
             (2, 1, 1, 1, 6, 10),  # nkx
             (2, 1, 1, 1, 6, 10),  # nky
@@ -447,7 +447,7 @@ def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory):
             'uniform',  # type_k2
         ),
         (  # (14) 2d non-uniform not aligned in kzyx and k210, similar to 3d single shot spiral but with singleton dim
-            (8, 5, 64, 1, 64),  # im_shape
+            (8, 5, 64, 1, 64),  # img_shape
             (8, 5, 1, 18, 128),  # k_shape
             (8, 1, 1, 18, 128),  # nkx
             (8, 1, 1, 1, 1),  # nky
diff --git a/tests/data/test_trajectory.py b/tests/data/test_trajectory.py
index d127be807..f256dd453 100644
--- a/tests/data/test_trajectory.py
+++ b/tests/data/test_trajectory.py
@@ -151,7 +151,7 @@ def test_trajectory_cpu(cartesian_grid) -> None:
 
 @COMMON_MR_TRAJECTORIES
 def test_ktype_along_kzyx(
-    im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
+    img_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
 ) -> None:
     """Test identification of traj types."""
 
@@ -184,7 +184,7 @@ def test_ktype_along_kzyx(
 
 @COMMON_MR_TRAJECTORIES
 def test_ktype_along_k210(
-    im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
+    img_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
 ) -> None:
     """Test identification of traj types."""
 
diff --git a/tests/helper.py b/tests/helper.py
index db4eeff34..fee220d9f 100644
--- a/tests/helper.py
+++ b/tests/helper.py
@@ -31,8 +31,8 @@ def dotproduct_adjointness_test(
     u: torch.Tensor,
     v: torch.Tensor,
     relative_tolerance: float = 1e-3,
-    absolute_tolerance=1e-5,
-):
+    absolute_tolerance: float = 1e-5,
+) -> None:
     """Test the adjointness of linear operator and operator.H.
 
     Test if
@@ -43,7 +43,6 @@ def dotproduct_adjointness_test(
     Note: This property should hold for all u and v.
     Commonly, this function is called with two random vectors u and v.
 
-
     Parameters
     ----------
     operator
@@ -82,8 +81,8 @@ def operator_isometry_test(
     operator: Operator[torch.Tensor, tuple[torch.Tensor]],
     u: torch.Tensor,
     relative_tolerance: float = 1e-3,
-    absolute_tolerance=1e-5,
-):
+    absolute_tolerance: float = 1e-5,
+) -> None:
     """Test the isometry of a operator.
 
     Test if
@@ -113,7 +112,7 @@ def operator_isometry_test(
 
 def linear_operator_unitary_test(
     operator: LinearOperator, u: torch.Tensor, relative_tolerance: float = 1e-3, absolute_tolerance=1e-5
-):
+) -> None:
     """Test if a linear operator is unitary.
 
     Test if
@@ -145,7 +144,7 @@ def linear_operator_unitary_test(
 def autodiff_test(
     operator: Operator[Unpack[Tin], tuple[torch.Tensor, ...]],
     *u: Unpack[Tin],
-):
+) -> None:
     """Test if autodiff of an operator is working.
     This test does not check that the gradient is correct but simply that it can be calculated using both torch.func.jvp
     and torch.func.vjp.
@@ -174,3 +173,88 @@ def autodiff_test(
         vjp = vjpfunc(v_range)
     assert all(x.isfinite().all() for x in output), 'result is not finite'
     assert all(x.isfinite().all() for x in vjp), 'VJP is not finite'
+
+
+def gradient_of_linear_operator_test(
+    operator: LinearOperator,
+    u: torch.Tensor,
+    v: torch.Tensor,
+    relative_tolerance: float = 1e-3,
+    absolute_tolerance: float = 1e-5,
+) -> None:
+    """Test the gradient of a linear operator is the adjoint.
+    Note: This property should hold for all u and v.
+    Commonly, this function is called with two random vectors u and v.
+
+    Parameters
+    ----------
+    operator
+        linear operator
+    u
+        element of the domain of the operator
+    v
+        element of the range of the operator
+    relative_tolerance
+        default is pytorch's default for float16
+    absolute_tolerance
+        default is pytorch's default for float16
+
+    Raises
+    ------
+    AssertionError
+        if the gradient is not the adjoint
+    """
+    # Gradient of the forward via vjp
+    (_, vjpfunc) = torch.func.vjp(operator.forward, u)
+    assert torch.allclose(vjpfunc((v,))[0], operator.adjoint(v)[0], rtol=relative_tolerance, atol=absolute_tolerance)
+
+    # Gradient of the adjoint via vjp
+    (_, vjpfunc) = torch.func.vjp(operator.adjoint, v)
+    assert torch.allclose(vjpfunc((u,))[0], operator.forward(u)[0], rtol=relative_tolerance, atol=absolute_tolerance)
+
+
+def forward_mode_autodiff_of_linear_operator_test(
+    operator: LinearOperator,
+    u: torch.Tensor,
+    v: torch.Tensor,
+    relative_tolerance: float = 1e-3,
+    absolute_tolerance: float = 1e-5,
+) -> None:
+    """Test the forward-mode autodiff calculation.
+    Verifies that the Jacobian-vector product (jvp) is equivalent to applying the operator.
+    Note: This property should hold for all u and v.
+    Commonly, this function is called with two random vectors u and v.
+
+    Parameters
+    ----------
+    operator
+        linear operator
+    u
+        element of the domain of the operator
+    v
+        element of the range of the operator
+    relative_tolerance
+        default is pytorch's default for float16
+    absolute_tolerance
+        default is pytorch's default for float16
+
+    Raises
+    ------
+    AssertionError
+        if the jvp yields different results than applying the operator
+    """
+    # jvp of the forward
+    assert torch.allclose(
+        torch.func.jvp(operator.forward, (u,), (u,))[0][0],
+        operator.forward(u)[0],
+        rtol=relative_tolerance,
+        atol=absolute_tolerance,
+    )
+
+    # jvp of the adjoint
+    assert torch.allclose(
+        torch.func.jvp(operator.adjoint, (v,), (v,))[0][0],
+        operator.adjoint(v)[0],
+        rtol=relative_tolerance,
+        atol=absolute_tolerance,
+    )
diff --git a/tests/operators/test_cartesian_sampling_op.py b/tests/operators/test_cartesian_sampling_op.py
index a886b21bd..ec757e4e5 100644
--- a/tests/operators/test_cartesian_sampling_op.py
+++ b/tests/operators/test_cartesian_sampling_op.py
@@ -1,5 +1,7 @@
 """Tests for the Cartesian sampling operator."""
 
+from typing import TypeAlias
+
 import pytest
 import torch
 from einops import rearrange
@@ -7,11 +9,18 @@
 from mrpro.operators import CartesianSamplingOp
 from typing_extensions import Unpack
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
 from tests.conftest import create_traj
 
+AtLeast3Ints: TypeAlias = tuple[Unpack[tuple[int, ...]], int, int, int]
+
 
-def test_cart_sampling_op_data_match():
+def test_cart_sampling_op_data_match() -> None:
     # Create 3D uniform trajectory
     k_shape = (1, 5, 20, 40, 60)
     nkx = (1, 1, 1, 1, 60)
@@ -90,7 +99,28 @@ def subsample_traj(
     return trajectory
 
 
-@pytest.mark.parametrize(
+def create_cart_sampling_op_and_range_domain(
+    sampling: str,
+    k_shape: AtLeast3Ints = (2, 5, 20, 40, 60),
+    nkx: AtLeast3Ints = (2, 1, 1, 1, 60),
+    nky: AtLeast3Ints = (2, 1, 1, 40, 1),
+    nkz: AtLeast3Ints = (2, 1, 20, 1, 1),
+) -> tuple[CartesianSamplingOp, torch.Tensor, torch.Tensor]:
+    type_kx = 'uniform'
+    type_ky = 'non-uniform' if sampling == 'cartesian_and_non_cartesian' else 'uniform'
+    type_kz = 'non-uniform' if sampling == 'cartesian_and_non_cartesian' else 'uniform'
+    trajectory = create_traj(nkx, nky, nkz, type_kx, type_ky, type_kz)
+    trajectory = subsample_traj(trajectory, sampling, k_shape)
+
+    encoding_matrix = SpatialDimension(k_shape[-3], k_shape[-2], k_shape[-1])
+    sampling_op = CartesianSamplingOp(encoding_matrix=encoding_matrix, traj=trajectory)
+    random_generator = RandomGenerator(seed=0)
+    u = random_generator.complex64_tensor(size=k_shape)
+    v = random_generator.complex64_tensor(size=(*k_shape[:-3], *trajectory.broadcasted_shape[-3:]))
+    return sampling_op, u, v
+
+
+SAMPLING_PARAMETERS = pytest.mark.parametrize(
     'sampling',
     [
         'random',
@@ -104,28 +134,24 @@ def subsample_traj(
         'kx_ky_along_k0_undersampling',
     ],
 )
-def test_cart_sampling_op_fwd_adj(sampling):
-    """Test adjoint property of Cartesian sampling operator."""
 
-    # Create 3D uniform trajectory
-    k_shape = (2, 5, 20, 40, 60)
-    nkx = (2, 1, 1, 1, 60)
-    nky = (2, 1, 1, 40, 1)
-    nkz = (2, 1, 20, 1, 1)
-    type_kx = 'uniform'
-    type_ky = 'non-uniform' if sampling == 'cartesian_and_non_cartesian' else 'uniform'
-    type_kz = 'non-uniform' if sampling == 'cartesian_and_non_cartesian' else 'uniform'
-    trajectory = create_traj(nkx, nky, nkz, type_kx, type_ky, type_kz)
-    trajectory = subsample_traj(trajectory, sampling, k_shape)
 
-    encoding_matrix = SpatialDimension(k_shape[-3], k_shape[-2], k_shape[-1])
-    sampling_op = CartesianSamplingOp(encoding_matrix=encoding_matrix, traj=trajectory)
+@SAMPLING_PARAMETERS
+def test_cart_sampling_op_fwd_adj(sampling: str) -> None:
+    """Test adjoint property of the Cartesian sampling operator."""
+    dotproduct_adjointness_test(*create_cart_sampling_op_and_range_domain(sampling))
+
+
+@SAMPLING_PARAMETERS
+def test_cart_sampling_op_grad(sampling: str) -> None:
+    """Test the gradient of the Cartesian sampling operator."""
+    gradient_of_linear_operator_test(*create_cart_sampling_op_and_range_domain(sampling))
 
-    # Test adjoint property; i.e. <Fu,v> == <u, F^Hv> for all u,v
-    random_generator = RandomGenerator(seed=0)
-    u = random_generator.complex64_tensor(size=k_shape)
-    v = random_generator.complex64_tensor(size=k_shape[:-3] + trajectory.broadcasted_shape[-3:])
-    dotproduct_adjointness_test(sampling_op, u, v)
+
+@SAMPLING_PARAMETERS
+def test_cart_sampling_op_forward_mode_autodiff(sampling: str) -> None:
+    """Test forward-mode autodiff of the Cartesian sampling operator."""
+    forward_mode_autodiff_of_linear_operator_test(*create_cart_sampling_op_and_range_domain(sampling))
 
 
 @pytest.mark.parametrize(
@@ -142,23 +168,9 @@ def test_cart_sampling_op_fwd_adj(sampling):
         'kx_ky_along_k0_undersampling',
     ],
 )
-def test_cart_sampling_op_gram(sampling):
+def test_cart_sampling_op_gram(sampling: str) -> None:
     """Test adjoint gram of Cartesian sampling operator."""
-
-    # Create 3D uniform trajectory
-    k_shape = (2, 5, 20, 40, 60)
-    nkx = (2, 1, 1, 1, 60)
-    nky = (2, 1, 1, 40, 1)
-    nkz = (2, 1, 20, 1, 1)
-    type_kx = 'uniform'
-    type_ky = 'non-uniform' if sampling == 'cartesian_and_non_cartesian' else 'uniform'
-    type_kz = 'non-uniform' if sampling == 'cartesian_and_non_cartesian' else 'uniform'
-    trajectory = create_traj(nkx, nky, nkz, type_kx, type_ky, type_kz)
-    trajectory = subsample_traj(trajectory, sampling, k_shape)
-
-    encoding_matrix = SpatialDimension(k_shape[-3], k_shape[-2], k_shape[-1])
-    sampling_op = CartesianSamplingOp(encoding_matrix=encoding_matrix, traj=trajectory)
-    u = RandomGenerator(seed=0).complex64_tensor(size=k_shape)
+    sampling_op, u, _ = create_cart_sampling_op_and_range_domain(sampling)
     (expected,) = (sampling_op.H @ sampling_op)(u)
     (actual,) = sampling_op.gram(u)
     torch.testing.assert_close(actual, expected, rtol=1e-3, atol=1e-3)
@@ -166,7 +178,7 @@ def test_cart_sampling_op_gram(sampling):
 
 @pytest.mark.parametrize(('k2_min', 'k2_max'), [(-1, 21), (-21, 1)])
 @pytest.mark.parametrize(('k0_min', 'k0_max'), [(-6, 13), (-13, 6)])
-def test_cart_sampling_op_oversampling(k0_min, k0_max, k2_min, k2_max):
+def test_cart_sampling_op_oversampling(k0_min: int, k0_max: int, k2_min: int, k2_max: int) -> None:
     """Test trajectory points outside of encoding_matrix."""
     encoding_matrix = SpatialDimension(40, 1, 20)
 
diff --git a/tests/operators/test_density_compensation_op.py b/tests/operators/test_density_compensation_op.py
index a979f815e..1a22791cc 100644
--- a/tests/operators/test_density_compensation_op.py
+++ b/tests/operators/test_density_compensation_op.py
@@ -4,24 +4,44 @@
 from mrpro.data import DcfData
 from mrpro.operators import DensityCompensationOp
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
 
 
-def test_density_compensation_op_adjointness() -> None:
-    """Test density operator adjoint property."""
+def create_density_compensation_op_and_range_domain() -> tuple[DensityCompensationOp, torch.Tensor, torch.Tensor]:
+    """Create a density compensation operator and an element from domain and range."""
     random_generator = RandomGenerator(seed=0)
 
     n_zyx = (2, 3, 4)
     n_other = (5, 6, 7)
     n_coils = 8
-
+    # Generate random dcf and operator
     random_tensor = random_generator.complex64_tensor(size=(*n_other, 1, *n_zyx))
     random_dcf = DcfData(data=random_tensor)
     dcf_op = DensityCompensationOp(random_dcf)
 
     u = random_generator.complex64_tensor(size=(*n_other, n_coils, *n_zyx))
     v = random_generator.complex64_tensor(size=(*n_other, n_coils, *n_zyx))
-    dotproduct_adjointness_test(dcf_op, u, v)
+    return dcf_op, u, v
+
+
+def test_density_compensation_op_adjointness() -> None:
+    """Test density operator adjoint property."""
+    dotproduct_adjointness_test(*create_density_compensation_op_and_range_domain())
+
+
+def test_density_compensation_op_grad() -> None:
+    """Test the gradient of the density compensation operator."""
+    gradient_of_linear_operator_test(*create_density_compensation_op_and_range_domain())
+
+
+def test_density_compensation_op_forward_mode_autodiff() -> None:
+    """Test forward-mode autodiff of the density compensation operator."""
+    forward_mode_autodiff_of_linear_operator_test(*create_density_compensation_op_and_range_domain())
 
 
 def test_density_compensation_op_dcfdata_tensor() -> None:
diff --git a/tests/operators/test_einsum_op.py b/tests/operators/test_einsum_op.py
index 16f12fcc1..79a4104a7 100644
--- a/tests/operators/test_einsum_op.py
+++ b/tests/operators/test_einsum_op.py
@@ -1,14 +1,33 @@
 """Tests for Einsum Operator."""
 
+from collections.abc import Sequence
+
 import pytest
 import torch
 from mrpro.operators.EinsumOp import EinsumOp
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
 
 
-@pytest.mark.parametrize('dtype', ['float32', 'complex128'])
-@pytest.mark.parametrize(
+def create_einsum_op_and_range_domain(
+    tensor_shape: Sequence[int], input_shape: Sequence[int], rule: str, output_shape: Sequence[int], dtype: str
+) -> tuple[EinsumOp, torch.Tensor, torch.Tensor]:
+    """Create an Einsum operator and an element from range and domain."""
+    generator = RandomGenerator(seed=0)
+    generate_tensor = getattr(generator, f'{dtype}_tensor')
+    tensor = generate_tensor(size=tensor_shape)
+    u = generate_tensor(size=input_shape)
+    v = generate_tensor(size=output_shape)
+    einsum_op = EinsumOp(tensor, rule)
+    return einsum_op, u, v
+
+
+EINSUM_PARAMETERS = pytest.mark.parametrize(
     ('tensor_shape', 'input_shape', 'rule', 'output_shape'),
     [
         ((3, 3), (3), 'i j,j->i', (3)),  # matrix vector product
@@ -18,15 +37,39 @@
         ((3, 5, 4, 2), (3, 2, 5), 'l ... i j, l j k -> k i l', (5, 4, 3)),  # general tensor contraction
     ],
 )
-def test_einsum_op(tensor_shape, input_shape, rule, output_shape, dtype) -> None:
+
+
+@pytest.mark.parametrize('dtype', ['float32', 'complex128'])
+@EINSUM_PARAMETERS
+def test_einsum_op(
+    tensor_shape: Sequence[int], input_shape: Sequence[int], rule: str, output_shape: Sequence[int], dtype: str
+) -> None:
     """Test adjointness and shape."""
-    generator = RandomGenerator(seed=0)
-    generate_tensor = getattr(generator, f'{dtype}_tensor')
-    tensor = generate_tensor(size=tensor_shape)
-    u = generate_tensor(size=input_shape)
-    v = generate_tensor(size=output_shape)
-    operator = EinsumOp(tensor, rule)
-    dotproduct_adjointness_test(operator, u, v)
+    dotproduct_adjointness_test(
+        *create_einsum_op_and_range_domain(tensor_shape, input_shape, rule, output_shape, dtype)
+    )
+
+
+@pytest.mark.parametrize('dtype', ['float32', 'complex128'])
+@EINSUM_PARAMETERS
+def test_einsum_op_grad(
+    tensor_shape: Sequence[int], input_shape: Sequence[int], rule: str, output_shape: Sequence[int], dtype: str
+) -> None:
+    """Test the gradient of the einsum operator."""
+    gradient_of_linear_operator_test(
+        *create_einsum_op_and_range_domain(tensor_shape, input_shape, rule, output_shape, dtype)
+    )
+
+
+@pytest.mark.parametrize('dtype', ['float32', 'complex128'])
+@EINSUM_PARAMETERS
+def test_einsum_op_forward_mode_autodiff(
+    tensor_shape: Sequence[int], input_shape: Sequence[int], rule: str, output_shape: Sequence[int], dtype: str
+) -> None:
+    """Test forward-mode autodiff of the einsum operator."""
+    forward_mode_autodiff_of_linear_operator_test(
+        *create_einsum_op_and_range_domain(tensor_shape, input_shape, rule, output_shape, dtype)
+    )
 
 
 @pytest.mark.parametrize(
@@ -39,7 +82,7 @@ def test_einsum_op(tensor_shape, input_shape, rule, output_shape, dtype) -> None
         '',  # empty string
     ],
 )
-def test_einsum_op_invalid(rule) -> None:
+def test_einsum_op_invalid(rule: str) -> None:
     """Test with different invalid rules."""
     with pytest.raises(ValueError, match='pattern should match'):
         EinsumOp(torch.tensor([]), rule)
diff --git a/tests/operators/test_fast_fourier_op.py b/tests/operators/test_fast_fourier_op.py
index 18e74ab2c..16a362bf7 100644
--- a/tests/operators/test_fast_fourier_op.py
+++ b/tests/operators/test_fast_fourier_op.py
@@ -1,12 +1,33 @@
 """Tests for Fast Fourier Operator class."""
 
+from collections.abc import Sequence
+
 import numpy as np
 import pytest
 import torch
 from mrpro.data import SpatialDimension
 from mrpro.operators import FastFourierOp
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
+
+
+def create_fast_fourier_op_and_range_domain(
+    recon_matrix: Sequence[int], encoding_matrix: Sequence[int]
+) -> tuple[FastFourierOp, torch.Tensor, torch.Tensor]:
+    """Create a fast Fourier operator and an element from domain and range."""
+    # Create test data
+    generator = RandomGenerator(seed=0)
+    u = generator.complex64_tensor(recon_matrix)
+    v = generator.complex64_tensor(encoding_matrix)
+
+    # Create operator and apply
+    ff_op = FastFourierOp(recon_matrix=recon_matrix, encoding_matrix=encoding_matrix)
+    return ff_op, u, v
 
 
 @pytest.mark.parametrize(('npoints', 'a'), [(100, 20), (300, 20)])
@@ -34,7 +55,7 @@ def test_fast_fourier_op_forward(npoints: int, a: int) -> None:
     torch.testing.assert_close(igauss_fwd, kgauss)
 
 
-@pytest.mark.parametrize(
+MATRIX_PARAMETERS = pytest.mark.parametrize(
     ('encoding_matrix', 'recon_matrix'),
     [
         ((101, 201, 50), (13, 221, 64)),
@@ -43,17 +64,28 @@ def test_fast_fourier_op_forward(npoints: int, a: int) -> None:
         ((100, 200, 50), (13, 221, 64)),
     ],
 )
-def test_fast_fourier_op_adjoint(encoding_matrix, recon_matrix) -> None:
+
+
+@MATRIX_PARAMETERS
+def test_fast_fourier_op_adjoint(encoding_matrix: Sequence[int], recon_matrix: Sequence[int]) -> None:
     """Test adjointness of Fast Fourier Op."""
+    dotproduct_adjointness_test(*create_fast_fourier_op_and_range_domain(recon_matrix, encoding_matrix))
 
-    # Create test data
-    generator = RandomGenerator(seed=0)
-    u = generator.complex64_tensor(recon_matrix)
-    v = generator.complex64_tensor(encoding_matrix)
 
-    # Create operator and apply
-    ff_op = FastFourierOp(recon_matrix=recon_matrix, encoding_matrix=encoding_matrix)
-    dotproduct_adjointness_test(ff_op, u, v)
+@MATRIX_PARAMETERS
+def test_density_compensation_op_grad(encoding_matrix: Sequence[int], recon_matrix: Sequence[int]) -> None:
+    """Test the gradient of the fast Fourier operator."""
+    gradient_of_linear_operator_test(*create_fast_fourier_op_and_range_domain(recon_matrix, encoding_matrix))
+
+
+@MATRIX_PARAMETERS
+def test_density_compensation_op_forward_mode_autodiff(
+    encoding_matrix: Sequence[int], recon_matrix: Sequence[int]
+) -> None:
+    """Test forward-mode autodiff of the fast Fourier operator."""
+    forward_mode_autodiff_of_linear_operator_test(
+        *create_fast_fourier_op_and_range_domain(recon_matrix, encoding_matrix)
+    )
 
 
 def test_fast_fourier_op_spatial_dim() -> None:
diff --git a/tests/operators/test_finite_difference_op.py b/tests/operators/test_finite_difference_op.py
index ea21ae919..b625526ab 100644
--- a/tests/operators/test_finite_difference_op.py
+++ b/tests/operators/test_finite_difference_op.py
@@ -1,15 +1,38 @@
 """Tests for finite difference operator."""
 
+from collections.abc import Sequence
+from typing import Literal
+
 import pytest
 import torch
 from einops import repeat
 from mrpro.operators import FiniteDifferenceOp
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
+
+
+def create_finite_difference_op_and_range_domain(
+    dim: Sequence[int], mode: Literal['central', 'forward', 'backward'], pad_mode: Literal['zeros', 'circular']
+) -> tuple[FiniteDifferenceOp, torch.Tensor, torch.Tensor]:
+    """Create a finite difference operator and an element from domain and range."""
+    random_generator = RandomGenerator(seed=0)
+    img_shape = (5, 6, 4, 10, 20, 16)
+
+    # Generate finite difference operator
+    finite_difference_op = FiniteDifferenceOp(dim, mode, pad_mode)
+
+    u = random_generator.complex64_tensor(size=img_shape)
+    v = random_generator.complex64_tensor(size=(len(dim), *img_shape))
+    return finite_difference_op, u, v
 
 
 @pytest.mark.parametrize('mode', ['central', 'forward', 'backward'])
-def test_finite_difference_op_forward(mode):
+def test_finite_difference_op_forward(mode: Literal['central', 'forward', 'backward']) -> None:
     """Test correct finite difference of simple object."""
     # Test object with positive linear gradient in real and negative linear gradient imaginary part
     linear_gradient_object = (
@@ -29,16 +52,28 @@ def test_finite_difference_op_forward(mode):
 @pytest.mark.parametrize('pad_mode', ['zeros', 'circular'])
 @pytest.mark.parametrize('mode', ['central', 'forward', 'backward'])
 @pytest.mark.parametrize('dim', [(-1,), (-2, -1), (-3, -2, -1), (-4,), (1, 3)])
-def test_finite_difference_op_adjointness(dim, mode, pad_mode):
+def test_finite_difference_op_adjointness(
+    dim: Sequence[int], mode: Literal['central', 'forward', 'backward'], pad_mode: Literal['zeros', 'circular']
+) -> None:
     """Test finite difference operator adjoint property."""
+    dotproduct_adjointness_test(*create_finite_difference_op_and_range_domain(dim, mode, pad_mode))
 
-    random_generator = RandomGenerator(seed=0)
-    im_shape = (5, 6, 4, 10, 20, 16)
 
-    # Generate finite difference operator
-    finite_difference_op = FiniteDifferenceOp(dim, mode, pad_mode)
+@pytest.mark.parametrize('pad_mode', ['zeros', 'circular'])
+@pytest.mark.parametrize('mode', ['central', 'forward', 'backward'])
+@pytest.mark.parametrize('dim', [(-1,), (-2, -1), (-3, -2, -1), (-4,), (1, 3)])
+def test_finite_difference_op_grad(
+    dim: Sequence[int], mode: Literal['central', 'forward', 'backward'], pad_mode: Literal['zeros', 'circular']
+) -> None:
+    """Test the gradient of finite difference operator."""
+    gradient_of_linear_operator_test(*create_finite_difference_op_and_range_domain(dim, mode, pad_mode))
 
-    # Check adjoint property
-    u = random_generator.complex64_tensor(size=im_shape)
-    v = random_generator.complex64_tensor(size=(len(dim), *im_shape))
-    dotproduct_adjointness_test(finite_difference_op, u, v)
+
+@pytest.mark.parametrize('pad_mode', ['zeros', 'circular'])
+@pytest.mark.parametrize('mode', ['central', 'forward', 'backward'])
+@pytest.mark.parametrize('dim', [(-1,), (-2, -1), (-3, -2, -1), (-4,), (1, 3)])
+def test_finite_difference_op_forward_mode_autodiff(
+    dim: Sequence[int], mode: Literal['central', 'forward', 'backward'], pad_mode: Literal['zeros', 'circular']
+) -> None:
+    """Test the forward-mode autodiff of the finite difference operator."""
+    forward_mode_autodiff_of_linear_operator_test(*create_finite_difference_op_and_range_domain(dim, mode, pad_mode))
diff --git a/tests/operators/test_fourier_op.py b/tests/operators/test_fourier_op.py
index 6227fdad3..576960860 100644
--- a/tests/operators/test_fourier_op.py
+++ b/tests/operators/test_fourier_op.py
@@ -1,6 +1,6 @@
 """Tests for Fourier operator."""
 
-from typing import cast
+from typing import TypeAlias, cast
 
 import pytest
 import torch
@@ -8,19 +8,64 @@
 from mrpro.data.enums import TrajType
 from mrpro.data.traj_calculators import KTrajectoryCartesian
 from mrpro.operators import FourierOp
+from typing_extensions import Unpack
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
 from tests.conftest import COMMON_MR_TRAJECTORIES, create_traj
 
+AtLeast3Ints: TypeAlias = tuple[int, int, int, Unpack[tuple[int, ...]]]
+
 
-def create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz) -> tuple[torch.Tensor, KTrajectory]:
+def create_data(
+    img_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+) -> tuple[torch.Tensor, KTrajectory]:
     """Create k-space trajectory and random image."""
     random_generator = RandomGenerator(seed=0)
-    img = random_generator.complex64_tensor(size=im_shape)
+    img = random_generator.complex64_tensor(size=img_shape)
     trajectory = create_traj(nkx, nky, nkz, type_kx, type_ky, type_kz)
     return img, trajectory
 
 
+def create_fourier_op_and_range_domain(
+    img_shape: AtLeast3Ints,
+    k_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+) -> tuple[FourierOp, torch.Tensor, torch.Tensor]:
+    """Create a fourier operator and an element from domain and range."""
+    # generate random images and k-space trajectories
+    _, trajectory = create_data(img_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
+
+    # create operator
+    recon_matrix = SpatialDimension(img_shape[-3], img_shape[-2], img_shape[-1])
+    encoding_matrix = SpatialDimension(
+        int(trajectory.kz.max() - trajectory.kz.min() + 1),
+        int(trajectory.ky.max() - trajectory.ky.min() + 1),
+        int(trajectory.kx.max() - trajectory.kx.min() + 1),
+    )
+    fourier_op = FourierOp(recon_matrix=recon_matrix, encoding_matrix=encoding_matrix, traj=trajectory)
+
+    random_generator = RandomGenerator(seed=0)
+    u = random_generator.complex64_tensor(size=img_shape)
+    v = random_generator.complex64_tensor(size=k_shape)
+    return fourier_op, u, v
+
+
 class NufftTrajektory(KTrajectory):
     """Always returns non-grid trajectory type."""
 
@@ -35,47 +80,84 @@ def _traj_types(
 
 @COMMON_MR_TRAJECTORIES
 def test_fourier_op_fwd_adj_property(
-    im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
+    img_shape: AtLeast3Ints,
+    k_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+    type_k0: str,
+    type_k1: str,
+    type_k2: str,
 ) -> None:
     """Test adjoint property of Fourier operator."""
+    dotproduct_adjointness_test(
+        *create_fourier_op_and_range_domain(img_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
+    )
 
-    # generate random images and k-space trajectories
-    _, trajectory = create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
 
-    # create operator
-    recon_matrix = SpatialDimension(im_shape[-3], im_shape[-2], im_shape[-1])
-    encoding_matrix = SpatialDimension(
-        int(trajectory.kz.max() - trajectory.kz.min() + 1),
-        int(trajectory.ky.max() - trajectory.ky.min() + 1),
-        int(trajectory.kx.max() - trajectory.kx.min() + 1),
+@COMMON_MR_TRAJECTORIES
+def test_fourier_op_grad(
+    img_shape: AtLeast3Ints,
+    k_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+    type_k0: str,
+    type_k1: str,
+    type_k2: str,
+) -> None:
+    """Test gradient of Fourier operator."""
+    gradient_of_linear_operator_test(
+        *create_fourier_op_and_range_domain(img_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
     )
-    fourier_op = FourierOp(recon_matrix=recon_matrix, encoding_matrix=encoding_matrix, traj=trajectory)
 
-    # test adjoint property; i.e. <Fu,v> == <u, F^Hv> for all u,v
-    random_generator = RandomGenerator(seed=0)
-    u = random_generator.complex64_tensor(size=im_shape)
-    v = random_generator.complex64_tensor(size=k_shape)
-    dotproduct_adjointness_test(fourier_op, u, v)
+
+@COMMON_MR_TRAJECTORIES
+def test_fourier_op_forward_mode_autodiff(
+    img_shape: AtLeast3Ints,
+    k_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+    type_k0: str,
+    type_k1: str,
+    type_k2: str,
+) -> None:
+    """Test forward-mode autodiff of Fourier operator."""
+    forward_mode_autodiff_of_linear_operator_test(
+        *create_fourier_op_and_range_domain(img_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
+    )
 
 
 @COMMON_MR_TRAJECTORIES
 def test_fourier_op_gram(
-    im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
+    img_shape: AtLeast3Ints,
+    k_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+    type_k0: str,
+    type_k1: str,
+    type_k2: str,
 ) -> None:
     """Test gram of Fourier operator."""
-    img, trajectory = create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
-
-    recon_matrix = SpatialDimension(im_shape[-3], im_shape[-2], im_shape[-1])
-    encoding_matrix = SpatialDimension(
-        int(trajectory.kz.max() - trajectory.kz.min() + 1),
-        int(trajectory.ky.max() - trajectory.ky.min() + 1),
-        int(trajectory.kx.max() - trajectory.kx.min() + 1),
+    fourier_op, img, _ = create_fourier_op_and_range_domain(
+        img_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz
     )
-    fourier_op = FourierOp(recon_matrix=recon_matrix, encoding_matrix=encoding_matrix, traj=trajectory)
-
     (expected,) = (fourier_op.H @ fourier_op)(img)
     (actual,) = fourier_op.gram(img)
-
     torch.testing.assert_close(actual, expected, rtol=1e-3, atol=1e-3)
 
 
@@ -99,10 +181,10 @@ def test_fourier_op_cartesian_sorting(ismrmrd_cart) -> None:
 
 
 @pytest.mark.parametrize(
-    ('im_shape', 'k_shape', 'nkx', 'nky', 'nkz', 'type_kx', 'type_ky', 'type_kz'),  # parameter names
+    ('img_shape', 'k_shape', 'nkx', 'nky', 'nkz', 'type_kx', 'type_ky', 'type_kz'),  # parameter names
     [
         (  # 3d single shot stack of spiral but cartesian FFT dimension in ky and k2
-            (1, 2, 96, 4, 128),  # im_shape
+            (1, 2, 96, 4, 128),  # img_shape
             (1, 2, 4, 1, 192),  # k_shape
             (1, 1, 1, 1, 192),  # nkx
             (1, 1, 4, 1, 1),  # nky
@@ -112,7 +194,7 @@ def test_fourier_op_cartesian_sorting(ismrmrd_cart) -> None:
             'non-uniform',  # type_kz
         ),
         (  # radial phase encoding, cartesian FFT dimension not aligned with corresponding k2, k1, k0 dimensions
-            (2, 3, 48, 16, 32),  # im_shape
+            (2, 3, 48, 16, 32),  # img_shape
             (2, 3, 96, 18, 64),  # k_shape
             (2, 1, 1, 18, 64),  # nkx
             (2, 1, 96, 1, 1),  # nky - Cartesian ky dimension defined along k2 rather than k1
@@ -124,14 +206,23 @@ def test_fourier_op_cartesian_sorting(ismrmrd_cart) -> None:
     ],
     ids=['3d_single_shot_stack_of_spirals_ky_in_k2', 'cartesian_fft_dims_not_aligned_with_k2_k1_k0_dims'],
 )
-def test_fourier_op_not_supported_traj(im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz) -> None:
+def test_fourier_op_not_supported_traj(
+    img_shape: AtLeast3Ints,
+    k_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+) -> None:
     """Test trajectory not supported by Fourier operator."""
 
     # generate random images and k-space trajectories
-    img, trajectory = create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
+    img, trajectory = create_data(img_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
 
     # create operator
-    recon_matrix = SpatialDimension(im_shape[-3], im_shape[-2], im_shape[-1])
+    recon_matrix = SpatialDimension(img_shape[-3], img_shape[-2], img_shape[-1])
     encoding_matrix = SpatialDimension(
         int(trajectory.kz.max() - trajectory.kz.min() + 1),
         int(trajectory.ky.max() - trajectory.ky.min() + 1),
@@ -143,15 +234,25 @@ def test_fourier_op_not_supported_traj(im_shape, k_shape, nkx, nky, nkz, type_kx
 
 @COMMON_MR_TRAJECTORIES
 def test_fourier_op_fft_nufft_forward(
-    im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
+    img_shape: AtLeast3Ints,
+    k_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+    type_k0: str,
+    type_k1: str,
+    type_k2: str,
 ) -> None:
     """Test Nufft vs FFT for Fourier operator."""
     if not any(t == 'uniform' for t in [type_kx, type_ky, type_kz]):
         return  # only test for uniform trajectories
 
-    img, trajectory = create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
+    img, trajectory = create_data(img_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
 
-    recon_matrix = SpatialDimension(im_shape[-3], im_shape[-2], im_shape[-1])
+    recon_matrix = SpatialDimension(img_shape[-3], img_shape[-2], img_shape[-1])
     encoding_matrix = SpatialDimension(
         int(trajectory.kz.max() - trajectory.kz.min() + 1),
         int(trajectory.ky.max() - trajectory.ky.min() + 1),
@@ -172,13 +273,23 @@ def test_fourier_op_fft_nufft_forward(
 
 @COMMON_MR_TRAJECTORIES
 def test_fourier_op_fft_nufft_adjoint(
-    im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
+    img_shape: AtLeast3Ints,
+    k_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+    type_k0: str,
+    type_k1: str,
+    type_k2: str,
 ) -> None:
     """Test AdjointNufft vs IFFT for Fourier operator."""
     if not any(t == 'uniform' for t in [type_kx, type_ky, type_kz]):
         return  # only test for uniform trajectories
-    img, trajectory = create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
-    recon_matrix = SpatialDimension(im_shape[-3], im_shape[-2], im_shape[-1])
+    img, trajectory = create_data(img_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
+    recon_matrix = SpatialDimension(img_shape[-3], img_shape[-2], img_shape[-1])
     encoding_matrix = SpatialDimension(
         int(trajectory.kz.max() - trajectory.kz.min() + 1),
         int(trajectory.ky.max() - trajectory.ky.min() + 1),
@@ -200,13 +311,23 @@ def test_fourier_op_fft_nufft_adjoint(
 
 @COMMON_MR_TRAJECTORIES
 def test_fourier_op_fft_nufft_gram(
-    im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
-):
+    img_shape: AtLeast3Ints,
+    k_shape: AtLeast3Ints,
+    nkx: AtLeast3Ints,
+    nky: AtLeast3Ints,
+    nkz: AtLeast3Ints,
+    type_kx: str,
+    type_ky: str,
+    type_kz: str,
+    type_k0: str,
+    type_k1: str,
+    type_k2: str,
+) -> None:
     """Test Nufft gram vs FFt gram for Fourier operator."""
     if not any(t == 'uniform' for t in [type_kx, type_ky, type_kz]):
         return  # only test for uniform trajectories
-    img, trajectory = create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
-    recon_matrix = SpatialDimension(im_shape[-3], im_shape[-2], im_shape[-1])
+    img, trajectory = create_data(img_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
+    recon_matrix = SpatialDimension(img_shape[-3], img_shape[-2], img_shape[-1])
     encoding_matrix = SpatialDimension(
         int(trajectory.kz.max() - trajectory.kz.min() + 1),
         int(trajectory.ky.max() - trajectory.ky.min() + 1),
diff --git a/tests/operators/test_non_uniform_fast_fourier_op.py b/tests/operators/test_non_uniform_fast_fourier_op.py
index e69578646..5910c7a46 100644
--- a/tests/operators/test_non_uniform_fast_fourier_op.py
+++ b/tests/operators/test_non_uniform_fast_fourier_op.py
@@ -12,25 +12,25 @@
 from tests.helper import dotproduct_adjointness_test, relative_image_difference
 
 
-def create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz) -> tuple[torch.Tensor, KTrajectory]:
+def create_data(img_shape, nkx, nky, nkz, type_kx, type_ky, type_kz) -> tuple[torch.Tensor, KTrajectory]:
     """Create k-space trajectory and random image."""
     random_generator = RandomGenerator(seed=0)
-    img = random_generator.complex64_tensor(size=im_shape)
+    img = random_generator.complex64_tensor(size=img_shape)
     trajectory = create_traj(nkx, nky, nkz, type_kx, type_ky, type_kz)
     return img, trajectory
 
 
 @COMMON_MR_TRAJECTORIES
 def test_non_uniform_fast_fourier_op_fwd_adj_property(
-    im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
+    img_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
 ) -> None:
     """Test adjoint property of non-uniform Fast Fourier operator."""
 
     # generate random images and k-space trajectories
-    _, trajectory = create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
+    _, trajectory = create_data(img_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
 
     # create operator
-    recon_matrix = SpatialDimension(im_shape[-3], im_shape[-2], im_shape[-1])
+    recon_matrix = SpatialDimension(img_shape[-3], img_shape[-2], img_shape[-1])
     encoding_matrix = SpatialDimension(
         int(trajectory.kz.max() - trajectory.kz.min() + 1),
         int(trajectory.ky.max() - trajectory.ky.min() + 1),
@@ -46,19 +46,19 @@ def test_non_uniform_fast_fourier_op_fwd_adj_property(
 
     # test adjoint property; i.e. <Fu,v> == <u, F^Hv> for all u,v
     random_generator = RandomGenerator(seed=0)
-    u = random_generator.complex64_tensor(size=im_shape)
+    u = random_generator.complex64_tensor(size=img_shape)
     v = random_generator.complex64_tensor(size=k_shape)
     dotproduct_adjointness_test(nufft_op, u, v)
 
 
 @COMMON_MR_TRAJECTORIES
 def test_non_uniform_fast_fourier_op_gram(
-    im_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
+    img_shape, k_shape, nkx, nky, nkz, type_kx, type_ky, type_kz, type_k0, type_k1, type_k2
 ) -> None:
     """Test gram of of non-uniform Fast Fourier operator."""
-    img, trajectory = create_data(im_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
+    img, trajectory = create_data(img_shape, nkx, nky, nkz, type_kx, type_ky, type_kz)
 
-    recon_matrix = SpatialDimension(im_shape[-3], im_shape[-2], im_shape[-1])
+    recon_matrix = SpatialDimension(img_shape[-3], img_shape[-2], img_shape[-1])
     encoding_matrix = SpatialDimension(
         int(trajectory.kz.max() - trajectory.kz.min() + 1),
         int(trajectory.ky.max() - trajectory.ky.min() + 1),
diff --git a/tests/operators/test_pca_compression_op.py b/tests/operators/test_pca_compression_op.py
index e73bf3951..a224c2bd9 100644
--- a/tests/operators/test_pca_compression_op.py
+++ b/tests/operators/test_pca_compression_op.py
@@ -1,12 +1,36 @@
 """Tests for PCA Compression Operator."""
 
+from collections.abc import Sequence
+
 import pytest
+import torch
 from mrpro.operators import PCACompressionOp
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
+
+
+def create_pca_compression_op_and_domain_range(
+    init_data_shape: Sequence[int], input_shape: Sequence[int], n_components: int
+) -> tuple[PCACompressionOp, torch.Tensor, torch.Tensor]:
+    """Create a pca compression operator and an element from domain and range."""
+    # Create test data
+    generator = RandomGenerator(seed=0)
+    data_to_calculate_compression_matrix_from = generator.complex64_tensor(init_data_shape)
+    u = generator.complex64_tensor(input_shape)
+    output_shape = (*input_shape[:-1], n_components)
+    v = generator.complex64_tensor(output_shape)
+
+    # Create operator and apply
+    pca_comp_op = PCACompressionOp(data=data_to_calculate_compression_matrix_from, n_components=n_components)
+    return pca_comp_op, u, v
 
 
-@pytest.mark.parametrize(
+SHAPE_PARAMETERS = pytest.mark.parametrize(
     ('init_data_shape', 'input_shape', 'n_components'),
     [
         ((40, 10), (100, 10), 6),
@@ -15,22 +39,35 @@
         ((3, 4, 40, 10), (7, 3, 4, 100, 10), 3),
     ],
 )
-def test_pca_compression_op_adjoint(init_data_shape, input_shape, n_components):
+
+
+@SHAPE_PARAMETERS
+def test_pca_compression_op_adjoint(
+    init_data_shape: Sequence[int], input_shape: Sequence[int], n_components: int
+) -> None:
     """Test adjointness of PCA Compression Op."""
+    dotproduct_adjointness_test(*create_pca_compression_op_and_domain_range(init_data_shape, input_shape, n_components))
 
-    # Create test data
-    generator = RandomGenerator(seed=0)
-    data_to_calculate_compression_matrix_from = generator.complex64_tensor(init_data_shape)
-    u = generator.complex64_tensor(input_shape)
-    output_shape = (*input_shape[:-1], n_components)
-    v = generator.complex64_tensor(output_shape)
 
-    # Create operator and apply
-    pca_comp_op = PCACompressionOp(data=data_to_calculate_compression_matrix_from, n_components=n_components)
-    dotproduct_adjointness_test(pca_comp_op, u, v)
+@SHAPE_PARAMETERS
+def test_pca_compression_op_grad(init_data_shape: Sequence[int], input_shape: Sequence[int], n_components: int) -> None:
+    """Test gradient of PCA Compression Op."""
+    gradient_of_linear_operator_test(
+        *create_pca_compression_op_and_domain_range(init_data_shape, input_shape, n_components)
+    )
+
+
+@SHAPE_PARAMETERS
+def test_pca_compression_op_forward_mode_autodiff(
+    init_data_shape: Sequence[int], input_shape: Sequence[int], n_components: int
+) -> None:
+    """Test forward-mode autodiff of PCA Compression Op."""
+    forward_mode_autodiff_of_linear_operator_test(
+        *create_pca_compression_op_and_domain_range(init_data_shape, input_shape, n_components)
+    )
 
 
-def test_pca_compression_op_wrong_shapes():
+def test_pca_compression_op_wrong_shapes() -> None:
     """Test if Operator raises error if shape mismatch."""
     init_data_shape = (10, 6)
     input_shape = (100, 3)
diff --git a/tests/operators/test_rearrangeop.py b/tests/operators/test_rearrangeop.py
index ecacafb42..605f11082 100644
--- a/tests/operators/test_rearrangeop.py
+++ b/tests/operators/test_rearrangeop.py
@@ -1,13 +1,18 @@
 """Tests for Rearrange Operator."""
 
+from collections.abc import Sequence
+
 import pytest
 from mrpro.operators.RearrangeOp import RearrangeOp
 
-from tests import RandomGenerator, dotproduct_adjointness_test
-
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
 
-@pytest.mark.parametrize('dtype', ['float32', 'complex128'])
-@pytest.mark.parametrize(
+SHAPE_PARAMETERS = pytest.mark.parametrize(
     ('input_shape', 'rule', 'output_shape', 'additional_info'),
     [
         ((1, 2, 3), 'a b c-> b a c', (2, 1, 3), None),  # swap axes
@@ -16,8 +21,14 @@
     ],
     ids=['swap_axes', 'flatten', 'unflatten'],
 )
-def test_einsum_op(input_shape, rule, output_shape, additional_info, dtype):
-    """Test adjointness and shape."""
+
+
+@pytest.mark.parametrize('dtype', ['float32', 'complex128'])
+@SHAPE_PARAMETERS
+def test_einsum_op_adjointness(
+    input_shape: Sequence[int], rule: str, output_shape: Sequence[int], additional_info: dict, dtype: str
+) -> None:
+    """Test adjointness and shape of Einsum Op."""
     generator = RandomGenerator(seed=0)
     generate_tensor = getattr(generator, f'{dtype}_tensor')
     u = generate_tensor(size=input_shape)
@@ -26,7 +37,35 @@ def test_einsum_op(input_shape, rule, output_shape, additional_info, dtype):
     dotproduct_adjointness_test(operator, u, v)
 
 
-def test_einsum_op_invalid():
+@pytest.mark.parametrize('dtype', ['float32', 'complex128'])
+@SHAPE_PARAMETERS
+def test_einsum_op_grad(
+    input_shape: Sequence[int], rule: str, output_shape: Sequence[int], additional_info: dict, dtype: str
+) -> None:
+    """Test gradient of Einsum Op."""
+    generator = RandomGenerator(seed=0)
+    generate_tensor = getattr(generator, f'{dtype}_tensor')
+    u = generate_tensor(size=input_shape)
+    v = generate_tensor(size=output_shape)
+    operator = RearrangeOp(rule, additional_info)
+    gradient_of_linear_operator_test(operator, u, v)
+
+
+@pytest.mark.parametrize('dtype', ['float32', 'complex128'])
+@SHAPE_PARAMETERS
+def test_einsum_op_forward_mode_autodiff(
+    input_shape: Sequence[int], rule: str, output_shape: Sequence[int], additional_info: dict, dtype: str
+) -> None:
+    """Test forward-mode autodiff of Einsum Op."""
+    generator = RandomGenerator(seed=0)
+    generate_tensor = getattr(generator, f'{dtype}_tensor')
+    u = generate_tensor(size=input_shape)
+    v = generate_tensor(size=output_shape)
+    operator = RearrangeOp(rule, additional_info)
+    forward_mode_autodiff_of_linear_operator_test(operator, u, v)
+
+
+def test_einsum_op_invalid() -> None:
     """Test with invalid rule."""
     with pytest.raises(ValueError, match='pattern should match'):
         RearrangeOp('missing arrow')
diff --git a/tests/operators/test_sensitivity_op.py b/tests/operators/test_sensitivity_op.py
index 31816882b..08e574f98 100644
--- a/tests/operators/test_sensitivity_op.py
+++ b/tests/operators/test_sensitivity_op.py
@@ -7,27 +7,44 @@
 from mrpro.data import CsmData, QHeader, SpatialDimension
 from mrpro.operators import SensitivityOp
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
 
 
-def test_sensitivity_op_adjointness() -> None:
-    """Test Sensitivity operator adjoint property."""
-
+def create_sensitivity_op_and_domain_range() -> tuple[SensitivityOp, torch.Tensor, torch.Tensor]:
+    """Create a sensitivity operator and an element from domain and range."""
     random_generator = RandomGenerator(seed=0)
 
     n_zyx = (2, 3, 4)
     n_other = (5, 6, 7)
     n_coils = 4
-
     # Generate sensitivity operator
     random_tensor = random_generator.complex64_tensor(size=(*n_other, n_coils, *n_zyx))
     random_csmdata = CsmData(data=random_tensor, header=QHeader(resolution=SpatialDimension(1.0, 1.0, 1.0)))
     sensitivity_op = SensitivityOp(random_csmdata)
 
-    # Check adjoint property
     u = random_generator.complex64_tensor(size=(*n_other, 1, *n_zyx))
     v = random_generator.complex64_tensor(size=(*n_other, n_coils, *n_zyx))
-    dotproduct_adjointness_test(sensitivity_op, u, v)
+    return sensitivity_op, u, v
+
+
+def test_sensitivity_op_adjointness() -> None:
+    """Test Sensitivity operator adjoint property."""
+    dotproduct_adjointness_test(*create_sensitivity_op_and_domain_range())
+
+
+def test_sensitivity_op_grad() -> None:
+    """Test gradient of sensitivity operator."""
+    gradient_of_linear_operator_test(*create_sensitivity_op_and_domain_range())
+
+
+def test_sensitivity_op_forward_mode_autodiff() -> None:
+    """Test forward-mode autodiff of sensitivity operator."""
+    forward_mode_autodiff_of_linear_operator_test(*create_sensitivity_op_and_domain_range())
 
 
 def test_sensitivity_op_csmdata_tensor() -> None:
diff --git a/tests/operators/test_slice_projection_op.py b/tests/operators/test_slice_projection_op.py
index 53209e98d..104c48ce1 100644
--- a/tests/operators/test_slice_projection_op.py
+++ b/tests/operators/test_slice_projection_op.py
@@ -1,6 +1,7 @@
 """Tests for projection operator."""
 
 import math
+from typing import Literal
 
 import numpy as np
 import pytest
@@ -9,10 +10,38 @@
 from mrpro.operators import SliceProjectionOp
 from mrpro.utils.slice_profiles import SliceGaussian, SliceInterpolate, SliceSmoothedRectangular
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
 
 
-def test_slice_projection_op_cube_basic():
+def create_slice_projection_op_and_domain_range(
+    optimize_for: Literal['forward', 'adjoint', 'both'], dtype: str
+) -> tuple[SliceProjectionOp, torch.Tensor, torch.Tensor]:
+    """Create a slice projection operator and an element from domain and range."""
+    rng = getattr(RandomGenerator(314), f'{dtype}_tensor')
+    operator_dtype = getattr(torch, dtype).to_real()
+    input_shape = SpatialDimension(10, 20, 30)
+    slice_rotation = None
+    slice_shift = 0.0
+    slice_profile = 1.0
+    operator = SliceProjectionOp(
+        input_shape=input_shape,
+        slice_rotation=slice_rotation,
+        slice_shift=slice_shift,
+        slice_profile=slice_profile,
+        optimize_for=optimize_for,
+    )
+    operator = operator.to(operator_dtype)
+    u = rng((1, *input_shape.zyx))
+    v = rng((1, 1, 1, max(input_shape.zyx), max(input_shape.zyx)))
+    return operator, u, v
+
+
+def test_slice_projection_op_cube_basic() -> None:
     input_shape = SpatialDimension(10, 20, 30)
     slice_rotation = None
     slice_shift = 0.0
@@ -29,7 +58,7 @@ def test_slice_projection_op_cube_basic():
 
 
 @pytest.mark.parametrize('axis', ['x', 'y', 'z'])
-def test_slice_projection_op_cube_rotation(axis):
+def test_slice_projection_op_cube_rotation(axis: Literal['x', 'y', 'z']) -> None:
     input_shape = SpatialDimension(201, 201, 201)
     slice_rotation = Rotation.from_euler(axis, 45, degrees=True)
     slice_shift = 0.0
@@ -60,7 +89,7 @@ def test_slice_projection_op_cube_rotation(axis):
 
 @pytest.mark.parametrize('axis', ['x', 'y', 'z'])
 @pytest.mark.parametrize('match_shift', [True, False])
-def test_slice_projection_op_cube_shift(axis, match_shift):
+def test_slice_projection_op_cube_shift(axis: Literal['x', 'y', 'z'], match_shift: bool) -> None:
     input_shape = SpatialDimension(21, 21, 21)
     slice_profile = 1.0
     slice_rotation = Rotation.from_euler(axis, 90, degrees=True)
@@ -88,7 +117,7 @@ def test_slice_projection_op_cube_shift(axis, match_shift):
     assert (slice2d > 0.01).sum() == match_shift
 
 
-def test_slice_projection_width_error():
+def test_slice_projection_width_error() -> None:
     input_shape = SpatialDimension(1, 1, 1)
     slice_profile = 0.99
     with pytest.raises(ValueError, match='width'):
@@ -97,27 +126,11 @@ def test_slice_projection_width_error():
 
 @pytest.mark.parametrize('dtype', ['complex64', 'float64', 'float32'])
 @pytest.mark.parametrize('optimize_for', ['forward', 'adjoint', 'both'])
-def test_slice_projection_op_basic_adjointness(optimize_for, dtype):
-    rng = getattr(RandomGenerator(314), f'{dtype}_tensor')
-    operator_dtype = getattr(torch, dtype).to_real()
-    input_shape = SpatialDimension(10, 20, 30)
-    slice_rotation = None
-    slice_shift = 0.0
-    slice_profile = 1.0
-    operator = SliceProjectionOp(
-        input_shape=input_shape,
-        slice_rotation=slice_rotation,
-        slice_shift=slice_shift,
-        slice_profile=slice_profile,
-        optimize_for=optimize_for,
-    )
-    operator = operator.to(operator_dtype)
-    u = rng((1, *input_shape.zyx))
-    v = rng((1, 1, 1, max(input_shape.zyx), max(input_shape.zyx)))
-    dotproduct_adjointness_test(operator, u, v)
+def test_slice_projection_op_basic_adjointness(optimize_for: Literal['forward', 'adjoint', 'both'], dtype: str) -> None:
+    dotproduct_adjointness_test(*create_slice_projection_op_and_domain_range(optimize_for, dtype))
 
 
-def test_slice_projection_op_slice_batching():
+def test_slice_projection_op_slice_batching() -> None:
     rng = RandomGenerator(314).float32_tensor
     input_shape = SpatialDimension(10, 20, 30)
     slice_rotation = Rotation.random((5, 1), 0)
@@ -137,7 +150,7 @@ def test_slice_projection_op_slice_batching():
     dotproduct_adjointness_test(operator, u, v)
 
 
-def test_slice_projection_op_volume_batching():
+def test_slice_projection_op_volume_batching() -> None:
     rng = RandomGenerator(314).float32_tensor
     input_shape = SpatialDimension(10, 20, 30)
     slice_rotation = None
@@ -157,7 +170,9 @@ def test_slice_projection_op_volume_batching():
 @pytest.mark.parametrize('direction', ['forward', 'adjoint'])
 @pytest.mark.parametrize('dtype', ['complex64', 'float64', 'float32'])
 @pytest.mark.parametrize('optimize_for', ['forward', 'adjoint', 'both'])
-def test_slice_projection_op_backward_is_adjoint(optimize_for, dtype, direction):
+def test_slice_projection_op_backward_is_adjoint(
+    optimize_for: Literal['forward', 'adjoint', 'both'], dtype: str, direction: str
+) -> None:
     rng = getattr(RandomGenerator(314), f'{dtype}_tensor')
     operator_dtype = getattr(torch, dtype).to_real()
     input_shape = SpatialDimension(10, 20, 30)
@@ -189,3 +204,22 @@ def test_slice_projection_op_backward_is_adjoint(optimize_for, dtype, direction)
     dotproduct_range = torch.vdot(forward_u.flatten(), v.flatten())
     dotproduct_domain = torch.vdot(u.flatten().flatten(), adjoint_v.flatten())
     torch.testing.assert_close(dotproduct_range, dotproduct_domain)
+
+
+# Sparse tensors do currently not work with torch.func. See https://github.com/pytorch/pytorch/issues/136357
+@pytest.mark.parametrize('dtype', ['complex64', 'float64', 'float32'])
+@pytest.mark.parametrize('optimize_for', ['forward', 'adjoint', 'both'])
+def test_slice_projection_op_grad(optimize_for: Literal['forward', 'adjoint', 'both'], dtype: str) -> None:
+    """Test gradient of slice projection operator."""
+    with pytest.raises(RuntimeError, match='Sparse CSR tensors'):
+        gradient_of_linear_operator_test(*create_slice_projection_op_and_domain_range(optimize_for, dtype))
+
+
+@pytest.mark.parametrize('dtype', ['complex64', 'float64', 'float32'])
+@pytest.mark.parametrize('optimize_for', ['forward', 'adjoint', 'both'])
+def test_slice_projection_op_forward_mode_autodiff(
+    optimize_for: Literal['forward', 'adjoint', 'both'], dtype: str
+) -> None:
+    """Test forward-mode autodiff of slice projection operator."""
+    with pytest.raises(RuntimeError, match='Sparse CSR tensors'):
+        forward_mode_autodiff_of_linear_operator_test(*create_slice_projection_op_and_domain_range(optimize_for, dtype))
diff --git a/tests/operators/test_wavelet_op.py b/tests/operators/test_wavelet_op.py
index 92de01286..ec3dede2e 100644
--- a/tests/operators/test_wavelet_op.py
+++ b/tests/operators/test_wavelet_op.py
@@ -1,28 +1,65 @@
 """Tests for Wavelet Operator."""
 
+from collections.abc import Sequence
+
 import numpy as np
 import pytest
 import torch
 from mrpro.operators import WaveletOp
+from mrpro.operators.WaveletOp import WaveletType
 from ptwt.conv_transform import wavedec
 from ptwt.conv_transform_2 import wavedec2
 from ptwt.conv_transform_3 import wavedec3
 
-from tests import RandomGenerator, dotproduct_adjointness_test, linear_operator_unitary_test, operator_isometry_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+    linear_operator_unitary_test,
+    operator_isometry_test,
+)
+
+
+def create_wavelet_op_and_domain_range(
+    img_shape: Sequence[int],
+    domain_shape: Sequence[int],
+    dim: tuple[int] | tuple[int, int] | tuple[int, int, int],
+    wavelet_name: WaveletType,
+) -> tuple[WaveletOp, torch.Tensor, torch.Tensor]:
+    """Create a wavelet operator and an element from domain and range."""
+    random_generator = RandomGenerator(seed=0)
+
+    wavelet_op = WaveletOp(domain_shape=domain_shape, dim=dim, wavelet_name=wavelet_name)
+
+    # calculate 1D length of wavelet coefficients
+    wavelet_stack_length = torch.sum(torch.as_tensor([(np.prod(shape)) for shape in wavelet_op.coefficients_shape]))
+
+    # sorted and normed dimensions needed to correctly calculate range
+    dim_sorted = sorted([d % len(img_shape) for d in dim], reverse=True)
+    range_shape = list(img_shape)
+    range_shape[dim_sorted[-1]] = int(wavelet_stack_length)
+    [range_shape.pop(d) for d in dim_sorted[:-1]]
+
+    u = random_generator.complex64_tensor(size=img_shape)
+    v = random_generator.complex64_tensor(size=range_shape)
+    return wavelet_op, u, v
 
 
 @pytest.mark.parametrize(
-    ('im_shape', 'domain_shape', 'dim'),
+    ('img_shape', 'domain_shape', 'dim'),
     [
         ((5, 16, 16, 16), (16,), (-1,)),
         ((5, 16, 16, 16), (16, 16), (-2, -1)),
         ((5, 16, 16, 16), (16, 16, 16), (-3, -2, -1)),
     ],
 )
-def test_wavelet_op_coefficient_transform(im_shape, domain_shape, dim):
+def test_wavelet_op_coefficient_transform(
+    img_shape: Sequence[int], domain_shape: Sequence[int], dim: tuple[int] | tuple[int, int] | tuple[int, int, int]
+) -> None:
     """Test transform between ptwt and mrpro coefficient format."""
     random_generator = RandomGenerator(seed=0)
-    img = random_generator.float32_tensor(size=im_shape)
+    img = random_generator.float32_tensor(size=img_shape)
     wavelet_op = WaveletOp(domain_shape=domain_shape, dim=dim)
     if len(dim) == 1:
         coeff_ptwt = wavedec(img, 'haar', level=2, mode='reflect')
@@ -56,31 +93,31 @@ def test_wavelet_op_coefficient_transform(im_shape, domain_shape, dim):
             assert all(torch.allclose(coeff_ptwt[i][key], coeff_ptwt_transformed_3d[i][key]) for key in coeff_ptwt[i])
 
 
-def test_wavelet_op_wrong_dim():
+def test_wavelet_op_wrong_dim() -> None:
     """Wavelet only works for 1D, 2D and 3D data."""
     with pytest.raises(ValueError, match='Only 1D, 2D and 3D wavelet'):
         WaveletOp(dim=(0, 1, 2, 3))  # type: ignore[arg-type]
 
 
-def test_wavelet_op_mismatch_dim_domain_shape():
+def test_wavelet_op_mismatch_dim_domain_shape() -> None:
     """Dimensions and shapes need to be of same length."""
     with pytest.raises(ValueError, match='Number of dimensions along which'):
         WaveletOp(domain_shape=(10, 20), dim=(-2,))
 
 
-def test_wavelet_op_error_for_odd_domain_shape():
+def test_wavelet_op_error_for_odd_domain_shape() -> None:
     with pytest.raises(NotImplementedError, match='ptwt only supports wavelet transforms for tensors with even'):
         WaveletOp(domain_shape=(11, 20), dim=(-2, -1))
 
 
-def test_wavelet_op_complex_real_shape():
+def test_wavelet_op_complex_real_shape() -> None:
     """Test that the shape of the output tensors is the same for complex/real data."""
-    im_shape = (6, 10, 20, 30)
+    img_shape = (6, 10, 20, 30)
     domain_shape = (20, 30, 6)
     dim = (-2, -1, -4)
     random_generator = RandomGenerator(seed=0)
-    img_complex = random_generator.complex64_tensor(size=im_shape)
-    img_real = random_generator.float32_tensor(size=im_shape)
+    img_complex = random_generator.complex64_tensor(size=img_shape)
+    img_real = random_generator.float32_tensor(size=img_shape)
     wavelet_op = WaveletOp(domain_shape=domain_shape, dim=dim, wavelet_name='db4', level=None)
     (coeff_complex,) = wavelet_op(img_complex)
     (coeff_real,) = wavelet_op(img_real)
@@ -88,9 +125,8 @@ def test_wavelet_op_complex_real_shape():
     assert wavelet_op.adjoint(coeff_complex)[0].shape == wavelet_op.adjoint(coeff_real)[0].shape
 
 
-@pytest.mark.parametrize('wavelet_name', ['haar', 'db4'])
-@pytest.mark.parametrize(
-    ('im_shape', 'domain_shape', 'dim'),
+SHAPE_PARAMETERS = pytest.mark.parametrize(
+    ('img_shape', 'domain_shape', 'dim'),
     [
         ((1, 5, 20, 30), (30,), (-1,)),
         ((5, 1, 10, 20, 30), (10,), (-3,)),
@@ -101,70 +137,73 @@ def test_wavelet_op_complex_real_shape():
         ((6, 10, 20, 30), (6, 20, 30), (-4, -2, -1)),
         ((6, 10, 20, 30), (20, 30, 6), (-2, -1, -4)),
         ((6, 10, 20, 30), (20, 30, 6), (2, 3, 0)),
-        ((5, 10, 20, 30), None, (-3, -2, -1)),
     ],
 )
-def test_wavelet_op_isometry(im_shape, domain_shape, dim, wavelet_name):
+
+
+@pytest.mark.parametrize('wavelet_name', ['haar', 'db4'])
+@SHAPE_PARAMETERS
+def test_wavelet_op_isometry(
+    img_shape: Sequence[int],
+    domain_shape: Sequence[int],
+    dim: tuple[int] | tuple[int, int] | tuple[int, int, int],
+    wavelet_name: WaveletType,
+) -> None:
     """Test that the wavelet operator is a linear isometry."""
     random_generator = RandomGenerator(seed=0)
-    img = random_generator.complex64_tensor(size=im_shape)
+    img = random_generator.complex64_tensor(size=img_shape)
     wavelet_op = WaveletOp(domain_shape=domain_shape, dim=dim, wavelet_name=wavelet_name, level=None)
     operator_isometry_test(wavelet_op, img)
 
 
 @pytest.mark.parametrize('wavelet_name', ['haar', 'db4'])
-@pytest.mark.parametrize(
-    ('im_shape', 'domain_shape', 'dim'),
-    [
-        ((1, 5, 20, 30), (30,), (-1,)),
-        ((5, 1, 10, 20, 30), (10,), (-3,)),
-        ((1, 5, 20, 30), (20, 30), (-2, -1)),
-        ((4, 10, 20, 30), (20, 30), (-2, -1)),
-        ((4, 10, 20, 30), (10, 30), (-3, -1)),
-        ((5, 10, 20, 30), (10, 20, 30), (-3, -2, -1)),
-        ((6, 10, 20, 30), (6, 20, 30), (-4, -2, -1)),
-        ((6, 10, 20, 30), (20, 30, 6), (-2, -1, -4)),
-        ((6, 10, 20, 30), (20, 30, 6), (2, 3, 0)),
-    ],
-)
-def test_wavelet_op_adjointness(im_shape, domain_shape, dim, wavelet_name):
+@SHAPE_PARAMETERS
+def test_wavelet_op_adjointness(
+    img_shape: Sequence[int],
+    domain_shape: Sequence[int],
+    dim: tuple[int] | tuple[int, int] | tuple[int, int, int],
+    wavelet_name: WaveletType,
+) -> None:
     """Test adjoint property; i.e. <Fu,v> == <u, F^Hv> for all u,v."""
-    random_generator = RandomGenerator(seed=0)
-
-    wavelet_op = WaveletOp(domain_shape=domain_shape, dim=dim, wavelet_name=wavelet_name)
-
-    # calculate 1D length of wavelet coefficients
-    wavelet_stack_length = torch.sum(torch.as_tensor([(np.prod(shape)) for shape in wavelet_op.coefficients_shape]))
-
-    # sorted and normed dimensions needed to correctly calculate range
-    dim_sorted = sorted([d % len(im_shape) for d in dim], reverse=True)
-    range_shape = list(im_shape)
-    range_shape[dim_sorted[-1]] = int(wavelet_stack_length)
-    [range_shape.pop(d) for d in dim_sorted[:-1]]
-
-    u = random_generator.complex64_tensor(size=im_shape)
-    v = random_generator.complex64_tensor(size=range_shape)
-    dotproduct_adjointness_test(wavelet_op, u, v)
+    dotproduct_adjointness_test(*create_wavelet_op_and_domain_range(img_shape, domain_shape, dim, wavelet_name))
 
 
 @pytest.mark.parametrize('wavelet_name', ['haar', 'db4'])
-@pytest.mark.parametrize(
-    ('im_shape', 'domain_shape', 'dim'),
-    [
-        ((1, 5, 20, 30), (30,), (-1,)),
-        ((5, 1, 10, 20, 30), (10,), (-3,)),
-        ((1, 5, 20, 30), (20, 30), (-2, -1)),
-        ((4, 10, 20, 30), (20, 30), (-2, -1)),
-        ((4, 10, 20, 30), (10, 30), (-3, -1)),
-        ((5, 10, 20, 30), (10, 20, 30), (-3, -2, -1)),
-        ((6, 10, 20, 30), (6, 20, 30), (-4, -2, -1)),
-        ((6, 10, 20, 30), (20, 30, 6), (-2, -1, -4)),
-        ((6, 10, 20, 30), (20, 30, 6), (2, 3, 0)),
-    ],
-)
-def test_wavelet_op_unitary(im_shape, domain_shape, dim, wavelet_name):
+@SHAPE_PARAMETERS
+def test_wavelet_op_unitary(
+    img_shape: Sequence[int],
+    domain_shape: Sequence[int],
+    dim: tuple[int] | tuple[int, int] | tuple[int, int, int],
+    wavelet_name: WaveletType,
+) -> None:
     """Test if wavelet operator is unitary."""
     random_generator = RandomGenerator(seed=0)
-    img = random_generator.complex64_tensor(size=im_shape)
+    img = random_generator.complex64_tensor(size=img_shape)
     wavelet_op = WaveletOp(domain_shape=domain_shape, dim=dim, wavelet_name=wavelet_name)
     linear_operator_unitary_test(wavelet_op, img)
+
+
+@pytest.mark.parametrize('wavelet_name', ['haar', 'db4'])
+@SHAPE_PARAMETERS
+def test_wavelet_op_grad(
+    img_shape: Sequence[int],
+    domain_shape: Sequence[int],
+    dim: tuple[int] | tuple[int, int] | tuple[int, int, int],
+    wavelet_name: WaveletType,
+) -> None:
+    """Test gradient of wavelet operator."""
+    gradient_of_linear_operator_test(*create_wavelet_op_and_domain_range(img_shape, domain_shape, dim, wavelet_name))
+
+
+@pytest.mark.parametrize('wavelet_name', ['haar', 'db4'])
+@SHAPE_PARAMETERS
+def test_wavelet_op_forward_mode_autodiff(
+    img_shape: Sequence[int],
+    domain_shape: Sequence[int],
+    dim: tuple[int] | tuple[int, int] | tuple[int, int, int],
+    wavelet_name: WaveletType,
+) -> None:
+    """Test forward-mode autodiff of wavelet operator."""
+    forward_mode_autodiff_of_linear_operator_test(
+        *create_wavelet_op_and_domain_range(img_shape, domain_shape, dim, wavelet_name)
+    )
diff --git a/tests/operators/test_zero_op.py b/tests/operators/test_zero_op.py
index 1e7f47017..d5b8b2195 100644
--- a/tests/operators/test_zero_op.py
+++ b/tests/operators/test_zero_op.py
@@ -3,10 +3,15 @@
 from mrpro.operators.LinearOperator import LinearOperatorSum
 from typing_extensions import assert_type
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
 
 
-def test_zero_op_keepshape():
+def test_zero_op_keepshape() -> None:
     """Test that the zero operator returns zeros."""
     generator = RandomGenerator(seed=0)
     x = generator.complex64_tensor(2, 3, 4)
@@ -16,7 +21,7 @@ def test_zero_op_keepshape():
     torch.testing.assert_close(actual, expected)
 
 
-def test_zero_op_scalar():
+def test_zero_op_scalar() -> None:
     """Test that the zero operator returns single zero."""
     generator = RandomGenerator(seed=0)
     x = generator.complex64_tensor(2, 3, 4)
@@ -26,7 +31,7 @@ def test_zero_op_scalar():
     torch.testing.assert_close(actual, expected)
 
 
-def test_zero_op_neutral_linop():
+def test_zero_op_neutral_linop() -> None:
     """Test that the zero operator is neutral for addition."""
     op = IdentityOp()
     zero = ZeroOp()
@@ -44,7 +49,7 @@ def test_zero_op_neutral_linop():
     assert zero * 1 is zero
 
 
-def test_zero_op_neutral_op():
+def test_zero_op_neutral_op() -> None:
     """Test that the zero operator is neutral for addition."""
     op = MagnitudeOp() @ IdentityOp()
     zero = ZeroOp()
@@ -56,7 +61,7 @@ def test_zero_op_neutral_op():
     assert_type(lsum, Operator[torch.Tensor, tuple[torch.Tensor]])
 
 
-def test_zero_op_adjoint_keepshape():
+def test_zero_op_adjoint_keepshape() -> None:
     """Test that the adjoint of the zero operator is the zero operator."""
     generator = RandomGenerator(seed=0)
     u = generator.complex64_tensor(2, 3, 4)
@@ -65,7 +70,25 @@ def test_zero_op_adjoint_keepshape():
     dotproduct_adjointness_test(operator, u, v)
 
 
-def test_zero_op_adjoint_scalar():
+def test_zero_op_grad_keepshape() -> None:
+    """Test gradient of zero operator."""
+    generator = RandomGenerator(seed=0)
+    u = generator.complex64_tensor(2, 3, 4)
+    v = generator.complex64_tensor(2, 3, 4)
+    operator = ZeroOp(keep_shape=True)
+    gradient_of_linear_operator_test(operator, u, v)
+
+
+def test_zero_op_forward_mode_autodiff_keepshape() -> None:
+    """Test forward-mode autodiff of zero operator."""
+    generator = RandomGenerator(seed=0)
+    u = generator.complex64_tensor(2, 3, 4)
+    v = generator.complex64_tensor(2, 3, 4)
+    operator = ZeroOp(keep_shape=True)
+    forward_mode_autodiff_of_linear_operator_test(operator, u, v)
+
+
+def test_zero_op_adjoint_scalar() -> None:
     """Test that the adjoint of the zero operator is the zero operator."""
     generator = RandomGenerator(seed=0)
     u = generator.complex64_tensor(2, 3, 4)
@@ -77,3 +100,21 @@ def test_zero_op_adjoint_scalar():
     # as ZeroOp is the neutral element of the addition.
     operator = LinearOperatorSum(ZeroOp(keep_shape=False), IdentityOp())
     dotproduct_adjointness_test(operator, u, v)
+
+
+def test_zero_op_grad_scalar() -> None:
+    """Test gradient of zero operator."""
+    generator = RandomGenerator(seed=0)
+    u = generator.complex64_tensor(2, 3, 4)
+    v = generator.complex64_tensor(2, 3, 4)
+    operator = LinearOperatorSum(ZeroOp(keep_shape=False), IdentityOp())
+    gradient_of_linear_operator_test(operator, u, v)
+
+
+def test_zero_op_forward_mode_autodiff_scalar() -> None:
+    """Test forward-mode autodiff of zero operator."""
+    generator = RandomGenerator(seed=0)
+    u = generator.complex64_tensor(2, 3, 4)
+    v = generator.complex64_tensor(2, 3, 4)
+    operator = LinearOperatorSum(ZeroOp(keep_shape=False), IdentityOp())
+    forward_mode_autodiff_of_linear_operator_test(operator, u, v)
diff --git a/tests/operators/test_zero_pad_op.py b/tests/operators/test_zero_pad_op.py
index 5d1f02135..fa600fe03 100644
--- a/tests/operators/test_zero_pad_op.py
+++ b/tests/operators/test_zero_pad_op.py
@@ -3,11 +3,28 @@
 import pytest
 import torch
 from mrpro.operators import ZeroPadOp
+from typing_extensions import Unpack
 
-from tests import RandomGenerator, dotproduct_adjointness_test
+from tests import (
+    RandomGenerator,
+    dotproduct_adjointness_test,
+    forward_mode_autodiff_of_linear_operator_test,
+    gradient_of_linear_operator_test,
+)
 
 
-def test_zero_pad_op_content():
+def create_zero_pad_op_and_domain_range(
+    u_shape: tuple[int, int, int, Unpack[tuple[int, ...]]], v_shape: tuple[int, int, int, Unpack[tuple[int, ...]]]
+) -> tuple[ZeroPadOp, torch.Tensor, torch.Tensor]:
+    """Create a zero padding operator and an element from domain and range."""
+    generator = RandomGenerator(seed=0)
+    u = generator.complex64_tensor(u_shape)
+    v = generator.complex64_tensor(v_shape)
+    zero_padding_op = ZeroPadOp(dim=(-3, -2, -1), original_shape=u_shape, padded_shape=v_shape)
+    return zero_padding_op, u, v
+
+
+def test_zero_pad_op_content() -> None:
     """Test correct padding and cropping (i.e. negative padding size)."""
     original_shape = (2, 100, 3, 200, 50, 2)
     padded_shape = (2, 80, 3, 100, 240, 2)
@@ -25,7 +42,7 @@ def test_zero_pad_op_content():
     torch.testing.assert_close(original_data[:, 10:90, :, 50:150, :, :], padded_data[:, :, :, :, 95:145, :])
 
 
-@pytest.mark.parametrize(
+SHAPE_PARAMETERS = pytest.mark.parametrize(
     ('u_shape', 'v_shape'),
     [
         ((101, 201, 50), (13, 221, 64)),
@@ -34,10 +51,27 @@ def test_zero_pad_op_content():
         ((100, 200, 50), (13, 221, 64)),
     ],
 )
-def test_zero_pad_op_adjoint(u_shape, v_shape):
+
+
+@SHAPE_PARAMETERS
+def test_zero_pad_op_adjoint(
+    u_shape: tuple[int, int, int, Unpack[tuple[int, ...]]], v_shape: tuple[int, int, int, Unpack[tuple[int, ...]]]
+) -> None:
     """Test adjointness of pad operator."""
-    generator = RandomGenerator(seed=0)
-    u = generator.complex64_tensor(u_shape)
-    v = generator.complex64_tensor(v_shape)
-    zero_padding_op = ZeroPadOp(dim=(-3, -2, -1), original_shape=u_shape, padded_shape=v_shape)
-    dotproduct_adjointness_test(zero_padding_op, u, v)
+    dotproduct_adjointness_test(*create_zero_pad_op_and_domain_range(u_shape, v_shape))
+
+
+@SHAPE_PARAMETERS
+def test_zero_pad_op_grad(
+    u_shape: tuple[int, int, int, Unpack[tuple[int, ...]]], v_shape: tuple[int, int, int, Unpack[tuple[int, ...]]]
+) -> None:
+    """Test gradient of zero padding operator."""
+    gradient_of_linear_operator_test(*create_zero_pad_op_and_domain_range(u_shape, v_shape))
+
+
+@SHAPE_PARAMETERS
+def test_zero_pad_op_forward_mode_autodiff(
+    u_shape: tuple[int, int, int, Unpack[tuple[int, ...]]], v_shape: tuple[int, int, int, Unpack[tuple[int, ...]]]
+) -> None:
+    """Test forward-mode autodiff of zero padding operator."""
+    forward_mode_autodiff_of_linear_operator_test(*create_zero_pad_op_and_domain_range(u_shape, v_shape))


Reviews:
user: login: fzimmermann89
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:26:05Z
user: login: fzimmermann89
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:30:15Z
user: login: fzimmermann89
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:30:38Z
user: login: fzimmermann89
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:31:29Z
user: login: fzimmermann89
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:31:47Z
user: login: fzimmermann89
body: Ignore my previous question about fixture.
I prefer your current style.

For new functions and tests, please add type hints
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-05T09:33:01Z
user: login: ckolbPTB
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-06T08:39:25Z
user: login: fzimmermann89
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-06T09:52:57Z

Review comments:
diff_hunk: @@ -170,3 +170,84 @@ def autodiff_test(
     with torch.autograd.detect_anomaly():
         (_, vjpfunc) = torch.func.vjp(operator.forward, *u)
         vjpfunc(v_range)
+
+
+def gradient_of_linear_operator_test(
+    operator: LinearOperator,
+    u: torch.Tensor,
+    v: torch.Tensor,
+    relative_tolerance: float = 1e-3,
+    absolute_tolerance=1e-5,
+):
+    """Test the gradient of a linear operator is the adjoint.
+    Note: This property should hold for all u and v.
+    Commonly, this function is called with two random vectors u and v.
+    Parameters
path: tests/helper.py
user: login: fzimmermann89
body: i think here should be whitespace lines
created_at: 2025-03-05T09:26:04Z
author_association: MEMBER
diff_hunk: @@ -90,23 +95,7 @@ def subsample_traj(
     return trajectory
 
 
-@pytest.mark.parametrize(
-    'sampling',
-    [
-        'random',
-        'partial_echo',
-        'partial_fourier',
-        'regular_undersampling',
-        'random_undersampling',
-        'different_random_undersampling',
-        'cartesian_and_non_cartesian',
-        'kx_ky_along_k0',
-        'kx_ky_along_k0_undersampling',
-    ],
-)
-def test_cart_sampling_op_fwd_adj(sampling):
-    """Test adjoint property of Cartesian sampling operator."""
-
+def create_cart_sampling_op_and_range_domain(sampling):
path: tests/operators/test_cartesian_sampling_op.py
user: login: fzimmermann89
body: ```suggestion
def create_cart_sampling_op_and_range_domain(sampling:str, kshape:Sequence[int]=(2, 5, 10, 40, 60), nkx:Sequence[int]:(2, 1, 1, 60)->tuple[CartesianSamplingOp, torch.Tensor, torch.Tensor]
```
created_at: 2025-03-05T09:30:15Z
author_association: MEMBER
diff_hunk: @@ -125,7 +114,41 @@ def test_cart_sampling_op_fwd_adj(sampling):
     random_generator = RandomGenerator(seed=0)
     u = random_generator.complex64_tensor(size=k_shape)
     v = random_generator.complex64_tensor(size=k_shape[:2] + trajectory.as_tensor().shape[2:])
-    dotproduct_adjointness_test(sampling_op, u, v)
+    return sampling_op, u, v
+
+
+SAMPLING_PARAMETERS = pytest.mark.parametrize(
+    'sampling',
+    [
+        'random',
+        'partial_echo',
+        'partial_fourier',
+        'regular_undersampling',
+        'random_undersampling',
+        'different_random_undersampling',
+        'cartesian_and_non_cartesian',
+        'kx_ky_along_k0',
+        'kx_ky_along_k0_undersampling',
+    ],
+)
+
+
+@SAMPLING_PARAMETERS
+def test_cart_sampling_op_fwd_adj(sampling):
path: tests/operators/test_cartesian_sampling_op.py
user: login: fzimmermann89
body: ```suggestion
def test_cart_sampling_op_fwd_adj(sampling:str)->None:
```
created_at: 2025-03-05T09:30:38Z
author_association: MEMBER
diff_hunk: @@ -170,3 +170,84 @@ def autodiff_test(
     with torch.autograd.detect_anomaly():
         (_, vjpfunc) = torch.func.vjp(operator.forward, *u)
         vjpfunc(v_range)
+
+
+def gradient_of_linear_operator_test(
+    operator: LinearOperator,
+    u: torch.Tensor,
+    v: torch.Tensor,
+    relative_tolerance: float = 1e-3,
+    absolute_tolerance=1e-5,
+):
+    """Test the gradient of a linear operator is the adjoint.
+    Note: This property should hold for all u and v.
+    Commonly, this function is called with two random vectors u and v.
+    Parameters
+    ----------
+    operator
+        linear operator
+    u
+        element of the domain of the operator
+    v
+        element of the range of the operator
+    relative_tolerance
+        default is pytorch's default for float16
+    absolute_tolerance
+        default is pytorch's default for float16
+    Raises
+    ------
+    AssertionError
+        if the gradient is not the adjoint
+    """
+    # Gradient of the forward via vjp
+    (_, vjpfunc) = torch.func.vjp(operator.forward, u)
+    assert torch.allclose(vjpfunc((v,))[0], operator.adjoint(v)[0], rtol=relative_tolerance, atol=absolute_tolerance)
+
+    # Gradient of the adjoint via vjp
+    (_, vjpfunc) = torch.func.vjp(operator.adjoint, v)
+    assert torch.allclose(vjpfunc((u,))[0], operator.forward(u)[0], rtol=relative_tolerance, atol=absolute_tolerance)
+
+
+def forward_mode_autodiff_of_linear_operator_test(
+    operator: LinearOperator,
+    u: torch.Tensor,
+    v: torch.Tensor,
+    relative_tolerance: float = 1e-3,
+    absolute_tolerance=1e-5,
path: tests/helper.py
user: login: fzimmermann89
body: ```suggestion
    absolute_tolerance:float=1e-5,
```
created_at: 2025-03-05T09:31:29Z
author_association: MEMBER
diff_hunk: @@ -170,3 +170,84 @@ def autodiff_test(
     with torch.autograd.detect_anomaly():
         (_, vjpfunc) = torch.func.vjp(operator.forward, *u)
         vjpfunc(v_range)
+
+
+def gradient_of_linear_operator_test(
+    operator: LinearOperator,
+    u: torch.Tensor,
+    v: torch.Tensor,
+    relative_tolerance: float = 1e-3,
+    absolute_tolerance=1e-5,
+):
+    """Test the gradient of a linear operator is the adjoint.
+    Note: This property should hold for all u and v.
+    Commonly, this function is called with two random vectors u and v.
+    Parameters
+    ----------
+    operator
+        linear operator
+    u
+        element of the domain of the operator
+    v
+        element of the range of the operator
+    relative_tolerance
+        default is pytorch's default for float16
+    absolute_tolerance
+        default is pytorch's default for float16
+    Raises
+    ------
+    AssertionError
+        if the gradient is not the adjoint
+    """
+    # Gradient of the forward via vjp
+    (_, vjpfunc) = torch.func.vjp(operator.forward, u)
+    assert torch.allclose(vjpfunc((v,))[0], operator.adjoint(v)[0], rtol=relative_tolerance, atol=absolute_tolerance)
+
+    # Gradient of the adjoint via vjp
+    (_, vjpfunc) = torch.func.vjp(operator.adjoint, v)
+    assert torch.allclose(vjpfunc((u,))[0], operator.forward(u)[0], rtol=relative_tolerance, atol=absolute_tolerance)
+
+
+def forward_mode_autodiff_of_linear_operator_test(
+    operator: LinearOperator,
+    u: torch.Tensor,
+    v: torch.Tensor,
+    relative_tolerance: float = 1e-3,
+    absolute_tolerance=1e-5,
+):
path: tests/helper.py
user: login: fzimmermann89
body: ```suggestion
):->None
```
created_at: 2025-03-05T09:31:46Z
author_association: MEMBER
diff_hunk: @@ -174,3 +173,88 @@ def autodiff_test(
         vjp = vjpfunc(v_range)
     assert all(x.isfinite().all() for x in output), 'result is not finite'
     assert all(x.isfinite().all() for x in vjp), 'VJP is not finite'
+
+
+def gradient_of_linear_operator_test(
+    operator: LinearOperator,
+    u: torch.Tensor,
+    v: torch.Tensor,
+    relative_tolerance: float = 1e-3,
+    absolute_tolerance: float = 1e-5,
+):
path: tests/helper.py
user: login: ckolbPTB
body: ```suggestion
) -> None:
```
created_at: 2025-03-06T08:39:25Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2024-11-14T18:58:59Z
body: <!-- Pytest Coverage Comment: test -->
<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/README.md"><img alt="Coverage" src="https://img.shields.io/badge/Coverage-93%25-brightgreen.svg" /></a><details><summary>Coverage Report </summary><table><tr><th>File</th><th>Stmts</th><th>Miss</th><th>Cover</th><th>Missing</th></tr><tbody><tr><td colspan="5"><b>src/mrpro/algorithms/csm</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/csm/inati.py">inati.py</a></td><td>24</td><td>1</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/csm/inati.py#L44">44</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/csm/walsh.py">walsh.py</a></td><td>16</td><td>1</td><td>94%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/csm/walsh.py#L53">53</a></td></tr><tr><td colspan="5"><b>src/mrpro/algorithms/dcf</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/dcf/dcf_voronoi.py">dcf_voronoi.py</a></td><td>53</td><td>4</td><td>92%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/dcf/dcf_voronoi.py#L15">15</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/dcf/dcf_voronoi.py#L55-L56">55&ndash;56</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/dcf/dcf_voronoi.py#L89">89</a></td></tr><tr><td colspan="5"><b>src/mrpro/algorithms/optimizers</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/optimizers/adam.py">adam.py</a></td><td>20</td><td>1</td><td>95%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/optimizers/adam.py#L101">101</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/optimizers/pdhg.py">pdhg.py</a></td><td>79</td><td>3</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/optimizers/pdhg.py#L177-L178">177&ndash;178</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/optimizers/pdhg.py#L184">184</a></td></tr><tr><td colspan="5"><b>src/mrpro/algorithms/reconstruction</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/DirectReconstruction.py">DirectReconstruction.py</a></td><td>28</td><td>16</td><td>43%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/DirectReconstruction.py#L59-L79">59&ndash;79</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/DirectReconstruction.py#L93">93</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/IterativeSENSEReconstruction.py">IterativeSENSEReconstruction.py</a></td><td>13</td><td>1</td><td>92%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/IterativeSENSEReconstruction.py#L79">79</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/Reconstruction.py">Reconstruction.py</a></td><td>50</td><td>22</td><td>56%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/Reconstruction.py#L42">42</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/Reconstruction.py#L54-L56">54&ndash;56</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/Reconstruction.py#L80-L87">80&ndash;87</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/Reconstruction.py#L108-L117">108&ndash;117</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/RegularizedIterativeSENSEReconstruction.py">RegularizedIterativeSENSEReconstruction.py</a></td><td>41</td><td>17</td><td>59%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/RegularizedIterativeSENSEReconstruction.py#L97-L101">97&ndash;101</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/algorithms/reconstruction/RegularizedIterativeSENSEReconstruction.py#L115-L140">115&ndash;140</a></td></tr><tr><td colspan="5"><b>src/mrpro/data</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/AcqInfo.py">AcqInfo.py</a></td><td>128</td><td>3</td><td>98%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/AcqInfo.py#L26">26</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/AcqInfo.py#L169">169</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/AcqInfo.py#L207">207</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/CsmData.py">CsmData.py</a></td><td>29</td><td>3</td><td>90%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/CsmData.py#L15">15</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/CsmData.py#L84-L86">84&ndash;86</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/DcfData.py">DcfData.py</a></td><td>45</td><td>8</td><td>82%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/DcfData.py#L18">18</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/DcfData.py#L66">66</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/DcfData.py#L78-L83">78&ndash;83</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/IData.py">IData.py</a></td><td>67</td><td>9</td><td>87%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/IData.py#L120">120</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/IData.py#L126">126</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/IData.py#L130">130</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/IData.py#L160-L168">160&ndash;168</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/IHeader.py">IHeader.py</a></td><td>75</td><td>7</td><td>91%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/IHeader.py#L75">75</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/IHeader.py#L109">109</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/IHeader.py#L127-L131">127&ndash;131</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py">KData.py</a></td><td>214</td><td>25</td><td>88%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L111-L112">111&ndash;112</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L127">127</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L134">134</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L144">144</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L152">152</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L206-L207">206&ndash;207</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L245">245</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L250-L251">250&ndash;251</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L270-L281">270&ndash;281</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L440">440</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L442">442</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L507">507</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L522">522</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L559">559</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L590">590</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KData.py#L599">599</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py">KHeader.py</a></td><td>153</td><td>17</td><td>89%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py#L25">25</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py#L119-L123">119&ndash;123</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py#L150">150</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py#L199">199</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py#L210">210</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py#L217-L218">217&ndash;218</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py#L221">221</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py#L228">228</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KHeader.py#L260-L271">260&ndash;271</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KNoise.py">KNoise.py</a></td><td>31</td><td>15</td><td>52%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KNoise.py#L39-L52">39&ndash;52</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KNoise.py#L56-L61">56&ndash;61</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KTrajectory.py">KTrajectory.py</a></td><td>81</td><td>12</td><td>85%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KTrajectory.py#L112-L117">112&ndash;117</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KTrajectory.py#L120-L122">120&ndash;122</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/KTrajectory.py#L207-L211">207&ndash;211</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py">MoveDataMixin.py</a></td><td>140</td><td>18</td><td>87%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L28">28</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L126">126</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L142">142</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L156-L158">156&ndash;158</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L220">220</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L336-L338">336&ndash;338</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L351">351</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L430">430</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L450-L451">450&ndash;451</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L453">453</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L468-L469">468&ndash;469</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/MoveDataMixin.py#L471">471</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/QData.py">QData.py</a></td><td>39</td><td>7</td><td>82%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/QData.py#L42">42</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/QData.py#L65-L73">65&ndash;73</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py">Rotation.py</a></td><td>674</td><td>35</td><td>95%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L100">100</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L198">198</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L335">335</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L433">433</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L477">477</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L495">495</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L582">582</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L584">584</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L593">593</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L627">627</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L629">629</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L692">692</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L769">769</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L774">774</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L777">777</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L792">792</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L809">809</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L814">814</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L890">890</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1078">1078</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1083">1083</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1086">1086</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1110">1110</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1114">1114</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1242">1242</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1244">1244</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1252-L1253">1252&ndash;1253</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1317">1317</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1399">1399</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1692">1692</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1844">1844</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1879">1879</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1883">1883</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/Rotation.py#L1994">1994</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py">SpatialDimension.py</a></td><td>232</td><td>21</td><td>91%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L34">34</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L104">104</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L141">141</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L148">148</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L154">154</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L274-L276">274&ndash;276</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L289-L291">289&ndash;291</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L325">325</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L343">343</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L356">356</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L369">369</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L382">382</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L395">395</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L404-L405">404&ndash;405</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L420">420</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/SpatialDimension.py#L429">429</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/acq_filters.py">acq_filters.py</a></td><td>12</td><td>1</td><td>92%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/acq_filters.py#L47">47</a></td></tr><tr><td colspan="5"><b>src/mrpro/data/traj_calculators</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/traj_calculators/KTrajectoryCalculator.py">KTrajectoryCalculator.py</a></td><td>25</td><td>2</td><td>92%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/traj_calculators/KTrajectoryCalculator.py#L23">23</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/traj_calculators/KTrajectoryCalculator.py#L45">45</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/traj_calculators/KTrajectoryIsmrmrd.py">KTrajectoryIsmrmrd.py</a></td><td>13</td><td>2</td><td>85%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/traj_calculators/KTrajectoryIsmrmrd.py#L41">41</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/traj_calculators/KTrajectoryIsmrmrd.py#L50">50</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/traj_calculators/KTrajectoryPulseq.py">KTrajectoryPulseq.py</a></td><td>23</td><td>1</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/data/traj_calculators/KTrajectoryPulseq.py#L55">55</a></td></tr><tr><td colspan="5"><b>src/mrpro/operators</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/CartesianSamplingOp.py">CartesianSamplingOp.py</a></td><td>89</td><td>3</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/CartesianSamplingOp.py#L118">118</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/CartesianSamplingOp.py#L157">157</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/CartesianSamplingOp.py#L280">280</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/ConstraintsOp.py">ConstraintsOp.py</a></td><td>60</td><td>2</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/ConstraintsOp.py#L46">46</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/ConstraintsOp.py#L48">48</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/EndomorphOperator.py">EndomorphOperator.py</a></td><td>65</td><td>2</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/EndomorphOperator.py#L228">228</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/EndomorphOperator.py#L234">234</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/FiniteDifferenceOp.py">FiniteDifferenceOp.py</a></td><td>27</td><td>2</td><td>93%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/FiniteDifferenceOp.py#L40">40</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/FiniteDifferenceOp.py#L105">105</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/FourierOp.py">FourierOp.py</a></td><td>158</td><td>3</td><td>98%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/FourierOp.py#L274">274</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/FourierOp.py#L392">392</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/FourierOp.py#L397">397</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/Functional.py">Functional.py</a></td><td>71</td><td>5</td><td>93%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/Functional.py#L20-L22">20&ndash;22</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/Functional.py#L117">117</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/Functional.py#L119">119</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/GridSamplingOp.py">GridSamplingOp.py</a></td><td>136</td><td>9</td><td>93%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/GridSamplingOp.py#L72-L73">72&ndash;73</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/GridSamplingOp.py#L82-L83">82&ndash;83</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/GridSamplingOp.py#L90-L91">90&ndash;91</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/GridSamplingOp.py#L94">94</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/GridSamplingOp.py#L96">96</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/GridSamplingOp.py#L98">98</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperator.py">LinearOperator.py</a></td><td>168</td><td>8</td><td>95%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperator.py#L114">114</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperator.py#L224">224</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperator.py#L254">254</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperator.py#L295">295</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperator.py#L304">304</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperator.py#L312">312</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperator.py#L329">329</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperator.py#L364">364</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py">LinearOperatorMatrix.py</a></td><td>158</td><td>16</td><td>90%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L82">82</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L119">119</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L152">152</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L161">161</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L166">166</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L175-L178">175&ndash;178</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L191-L194">191&ndash;194</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L203">203</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L215">215</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L304">304</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L331">331</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/LinearOperatorMatrix.py#L359">359</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/MultiIdentityOp.py">MultiIdentityOp.py</a></td><td>13</td><td>2</td><td>85%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/MultiIdentityOp.py#L43">43</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/MultiIdentityOp.py#L48">48</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/Operator.py">Operator.py</a></td><td>78</td><td>2</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/Operator.py#L31">31</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/Operator.py#L87">87</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/ProximableFunctionalSeparableSum.py">ProximableFunctionalSeparableSum.py</a></td><td>39</td><td>3</td><td>92%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/ProximableFunctionalSeparableSum.py#L50">50</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/ProximableFunctionalSeparableSum.py#L103">103</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/ProximableFunctionalSeparableSum.py#L110">110</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/SliceProjectionOp.py">SliceProjectionOp.py</a></td><td>173</td><td>8</td><td>95%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/SliceProjectionOp.py#L44">44</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/SliceProjectionOp.py#L61">61</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/SliceProjectionOp.py#L63">63</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/SliceProjectionOp.py#L69">69</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/SliceProjectionOp.py#L207">207</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/SliceProjectionOp.py#L228">228</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/SliceProjectionOp.py#L261">261</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/SliceProjectionOp.py#L301">301</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/WaveletOp.py">WaveletOp.py</a></td><td>119</td><td>5</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/WaveletOp.py#L151">151</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/WaveletOp.py#L169">169</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/WaveletOp.py#L204">204</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/WaveletOp.py#L209">209</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/WaveletOp.py#L232">232</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/ZeroPadOp.py">ZeroPadOp.py</a></td><td>16</td><td>1</td><td>94%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/operators/ZeroPadOp.py#L30">30</a></td></tr><tr><td colspan="5"><b>src/mrpro/utils</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/filters.py">filters.py</a></td><td>62</td><td>2</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/filters.py#L44">44</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/filters.py#L49">49</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/reshape.py">reshape.py</a></td><td>60</td><td>1</td><td>98%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/reshape.py#L191">191</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/slice_profiles.py">slice_profiles.py</a></td><td>46</td><td>6</td><td>87%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/slice_profiles.py#L20">20</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/slice_profiles.py#L36">36</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/slice_profiles.py#L113-L116">113&ndash;116</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/slice_profiles.py#L149">149</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/sliding_window.py">sliding_window.py</a></td><td>34</td><td>1</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/sliding_window.py#L34">34</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/split_idx.py">split_idx.py</a></td><td>10</td><td>2</td><td>80%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/split_idx.py#L43">43</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/split_idx.py#L47">47</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/summarize_tensorvalues.py">summarize_tensorvalues.py</a></td><td>11</td><td>9</td><td>18%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/summarize_tensorvalues.py#L20-L29">20&ndash;29</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/typing.py">typing.py</a></td><td>21</td><td>11</td><td>48%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/typing.py#L8-L23">8&ndash;23</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/zero_pad_or_crop.py">zero_pad_or_crop.py</a></td><td>31</td><td>6</td><td>81%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/zero_pad_or_crop.py#L26">26</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/zero_pad_or_crop.py#L30">30</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/zero_pad_or_crop.py#L55">55</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/zero_pad_or_crop.py#L58">58</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/zero_pad_or_crop.py#L61">61</a>, <a href="https://github.com/PTB-MR/mrpro/blob/652209854402e941840574a3d2d9a9870ab28fd9/src/mrpro/utils/zero_pad_or_crop.py#L64">64</a></td></tr><tr><td><b>TOTAL</b></td><td><b>4973</b></td><td><b>361</b></td><td><b>93%</b></td><td>&nbsp;</td></tr></tbody></table></details>

| Tests | Skipped | Failures | Errors | Time |
| ----- | ------- | -------- | -------- | ------------------ |
| 2516 | 0 :zzz: | 0 :x: | 0 :fire: | 1m 24s :stopwatch: |

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2024-11-14T19:00:18Z
body: <!-- documentation build 536 -->
### :books: Documentation
:file_folder: [Download as zip](https://github.com/PTB-MR/mrpro/actions/runs/13695858483/artifacts/2702518847)
:mag: [View online](https://zimf.de/zipserve/https://github.com/PTB-MR/mrpro/actions/runs/13695858483/artifacts/2702518847/)
author_association: CONTRIBUTOR
user: login: ckolbPTB
created_at: 2024-11-14T19:06:24Z
body: Tests which currently do not pass:
FAILED tests/operators/test_slice_projection_op.py::test_slice_projection_op_grad - RuntimeError: Sparse CSR tensors do not have strides
FAILED tests/operators/test_slice_projection_op.py::test_slice_projection_op_forward_mode_autodiff - RuntimeError: Sparse CSR tensors do not have strides
author_association: COLLABORATOR
user: login: fzimmermann89
created_at: 2024-11-14T21:06:14Z
body: That is a known issue #409  🙈
https://github.com/pytorch/pytorch/issues/97286 https://github.com/pytorch/pytorch/issues/136357 

There is imho no feasibe way tfor us to support the slice projection operator in torch.func.

Fixing it in pytorch would also be infeasible for us, as the the functorch c backend seems to cause the issue, as sparse tensors or completely missing in its implementation,


We could replace the sparse-matrix/dense vector multiplication by manual indexing and dotproducts. This will make it quite a bit slower and way more complicated.

For now, I would ether remove the tests or mark them as failing.

This mainly means that we cant solve a superresolutionproblem  with fista or pdgh if we keep using torch.func for the gradients.

author_association: MEMBER
user: login: ckolbPTB
created_at: 2024-11-27T11:24:17Z
body: @fzimmermann89 do you think you could add the missing tests for GridSamplingOp? Everthing else should now be covered...
author_association: COLLABORATOR
user: login: fzimmermann89
created_at: 2025-01-13T19:13:19Z
body: I moved #604 out of this PR, to unblock #293 
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-03-02T19:50:02Z
body: <!-- Pytest Coverage Comment: coverage_report_pr -->
<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/README.md"><img alt="Coverage" src="https://img.shields.io/badge/Coverage-92%25-brightgreen.svg" /></a><details><summary>Coverage Report </summary><table><tr><th>File</th><th>Stmts</th><th>Miss</th><th>Cover</th><th>Missing</th></tr><tbody><tr><td colspan="5"><b>src/mrpro</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/_version.py">_version.py</a></td><td>6</td><td>2</td><td>67%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/_version.py#L7-L8">7&ndash;8</a></td></tr><tr><td colspan="5"><b>src/mrpro/algorithms/csm</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/csm/inati.py">inati.py</a></td><td>24</td><td>1</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/csm/inati.py#L44">44</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/csm/walsh.py">walsh.py</a></td><td>16</td><td>1</td><td>94%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/csm/walsh.py#L53">53</a></td></tr><tr><td colspan="5"><b>src/mrpro/algorithms/dcf</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/dcf/dcf_voronoi.py">dcf_voronoi.py</a></td><td>55</td><td>4</td><td>93%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/dcf/dcf_voronoi.py#L15">15</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/dcf/dcf_voronoi.py#L55-L56">55&ndash;56</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/dcf/dcf_voronoi.py#L89">89</a></td></tr><tr><td colspan="5"><b>src/mrpro/algorithms/optimizers</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/optimizers/adam.py">adam.py</a></td><td>20</td><td>1</td><td>95%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/optimizers/adam.py#L101">101</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/optimizers/pdhg.py">pdhg.py</a></td><td>79</td><td>3</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/optimizers/pdhg.py#L177-L178">177&ndash;178</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/optimizers/pdhg.py#L184">184</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/optimizers/pgd.py">pgd.py</a></td><td>51</td><td>4</td><td>92%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/optimizers/pgd.py#L106">106</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/optimizers/pgd.py#L151-L154">151&ndash;154</a></td></tr><tr><td colspan="5"><b>src/mrpro/algorithms/reconstruction</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/DirectReconstruction.py">DirectReconstruction.py</a></td><td>28</td><td>16</td><td>43%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/DirectReconstruction.py#L59-L79">59&ndash;79</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/DirectReconstruction.py#L93">93</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/IterativeSENSEReconstruction.py">IterativeSENSEReconstruction.py</a></td><td>13</td><td>1</td><td>92%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/IterativeSENSEReconstruction.py#L79">79</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/Reconstruction.py">Reconstruction.py</a></td><td>50</td><td>22</td><td>56%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/Reconstruction.py#L42">42</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/Reconstruction.py#L54-L56">54&ndash;56</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/Reconstruction.py#L80-L87">80&ndash;87</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/Reconstruction.py#L108-L117">108&ndash;117</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/RegularizedIterativeSENSEReconstruction.py">RegularizedIterativeSENSEReconstruction.py</a></td><td>41</td><td>17</td><td>59%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/RegularizedIterativeSENSEReconstruction.py#L97-L101">97&ndash;101</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/algorithms/reconstruction/RegularizedIterativeSENSEReconstruction.py#L115-L140">115&ndash;140</a></td></tr><tr><td colspan="5"><b>src/mrpro/data</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/AcqInfo.py">AcqInfo.py</a></td><td>145</td><td>5</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/AcqInfo.py#L42">42</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/AcqInfo.py#L121-L122">121&ndash;122</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/AcqInfo.py#L124">124</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/AcqInfo.py#L233">233</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/CsmData.py">CsmData.py</a></td><td>29</td><td>3</td><td>90%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/CsmData.py#L15">15</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/CsmData.py#L84-L86">84&ndash;86</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/DcfData.py">DcfData.py</a></td><td>45</td><td>8</td><td>82%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/DcfData.py#L18">18</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/DcfData.py#L66">66</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/DcfData.py#L78-L83">78&ndash;83</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/EncodingLimits.py">EncodingLimits.py</a></td><td>73</td><td>3</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/EncodingLimits.py#L33">33</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/EncodingLimits.py#L123">123</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/EncodingLimits.py#L126">126</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IData.py">IData.py</a></td><td>59</td><td>8</td><td>86%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IData.py#L118">118</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IData.py#L132">132</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IData.py#L159-L167">159&ndash;167</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IHeader.py">IHeader.py</a></td><td>136</td><td>12</td><td>91%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IHeader.py#L69-L72">69&ndash;72</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IHeader.py#L253">253</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IHeader.py#L257">257</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IHeader.py#L261">261</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IHeader.py#L265">265</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/IHeader.py#L299-L303">299&ndash;303</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py">KData.py</a></td><td>201</td><td>29</td><td>86%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L113-L114">113&ndash;114</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L129">129</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L136">136</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L147-L157">147&ndash;157</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L166">166</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L174">174</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L183">183</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L217">217</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L239-L241">239&ndash;241</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L277-L278">277&ndash;278</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L333-L344">333&ndash;344</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L473">473</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L475">475</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KData.py#L545">545</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py">KHeader.py</a></td><td>141</td><td>21</td><td>85%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py#L24">24</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py#L109-L115">109&ndash;115</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py#L142">142</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py#L190">190</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py#L197-L198">197&ndash;198</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py#L201">201</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py#L208">208</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py#L225-L232">225&ndash;232</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KHeader.py#L240-L251">240&ndash;251</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KNoise.py">KNoise.py</a></td><td>31</td><td>15</td><td>52%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KNoise.py#L39-L52">39&ndash;52</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KNoise.py#L56-L61">56&ndash;61</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KTrajectory.py">KTrajectory.py</a></td><td>108</td><td>3</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KTrajectory.py#L166">166</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KTrajectory.py#L168">168</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/KTrajectory.py#L188">188</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py">MoveDataMixin.py</a></td><td>140</td><td>18</td><td>87%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L28">28</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L126">126</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L142">142</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L156-L158">156&ndash;158</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L220">220</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L336-L338">336&ndash;338</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L351">351</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L430">430</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L450-L451">450&ndash;451</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L453">453</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L468-L469">468&ndash;469</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/MoveDataMixin.py#L471">471</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/QData.py">QData.py</a></td><td>39</td><td>7</td><td>82%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/QData.py#L42">42</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/QData.py#L65-L73">65&ndash;73</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py">Rotation.py</a></td><td>719</td><td>35</td><td>95%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L101">101</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L199">199</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L336">336</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L434">434</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L478">478</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L496">496</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L583">583</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L585">585</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L594">594</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L628">628</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L630">630</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L693">693</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L770">770</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L775">775</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L778">778</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L793">793</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L810">810</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L815">815</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L891">891</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1079">1079</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1084">1084</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1087">1087</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1111">1111</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1115">1115</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1243">1243</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1245">1245</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1253-L1254">1253&ndash;1254</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1318">1318</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1400">1400</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1703">1703</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1855">1855</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1890">1890</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L1894">1894</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/Rotation.py#L2084">2084</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py">SpatialDimension.py</a></td><td>232</td><td>20</td><td>91%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L34">34</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L104">104</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L148">148</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L154">154</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L274-L276">274&ndash;276</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L289-L291">289&ndash;291</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L325">325</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L343">343</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L356">356</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L369">369</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L382">382</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L395">395</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L404-L405">404&ndash;405</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L420">420</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/SpatialDimension.py#L429">429</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/acq_filters.py">acq_filters.py</a></td><td>12</td><td>1</td><td>92%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/acq_filters.py#L47">47</a></td></tr><tr><td colspan="5"><b>src/mrpro/data/traj_calculators</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/traj_calculators/KTrajectoryCalculator.py">KTrajectoryCalculator.py</a></td><td>27</td><td>1</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/traj_calculators/KTrajectoryCalculator.py#L84">84</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/traj_calculators/KTrajectoryIsmrmrd.py">KTrajectoryIsmrmrd.py</a></td><td>19</td><td>1</td><td>95%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/data/traj_calculators/KTrajectoryIsmrmrd.py#L57">57</a></td></tr><tr><td colspan="5"><b>src/mrpro/operators</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/CartesianSamplingOp.py">CartesianSamplingOp.py</a></td><td>96</td><td>4</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/CartesianSamplingOp.py#L124">124</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/CartesianSamplingOp.py#L163">163</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/CartesianSamplingOp.py#L238">238</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/CartesianSamplingOp.py#L305">305</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/ConstraintsOp.py">ConstraintsOp.py</a></td><td>60</td><td>2</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/ConstraintsOp.py#L46">46</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/ConstraintsOp.py#L48">48</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/EndomorphOperator.py">EndomorphOperator.py</a></td><td>32</td><td>2</td><td>94%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/EndomorphOperator.py#L52">52</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/EndomorphOperator.py#L58">58</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/FiniteDifferenceOp.py">FiniteDifferenceOp.py</a></td><td>27</td><td>2</td><td>93%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/FiniteDifferenceOp.py#L40">40</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/FiniteDifferenceOp.py#L105">105</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/FourierOp.py">FourierOp.py</a></td><td>90</td><td>3</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/FourierOp.py#L185">185</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/FourierOp.py#L257">257</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/FourierOp.py#L262">262</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/Functional.py">Functional.py</a></td><td>77</td><td>9</td><td>88%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/Functional.py#L20-L22">20&ndash;22</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/Functional.py#L117">117</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/Functional.py#L119">119</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/Functional.py#L226-L228">226&ndash;228</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/Functional.py#L242">242</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/GridSamplingOp.py">GridSamplingOp.py</a></td><td>136</td><td>9</td><td>93%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/GridSamplingOp.py#L72-L73">72&ndash;73</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/GridSamplingOp.py#L82-L83">82&ndash;83</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/GridSamplingOp.py#L90-L91">90&ndash;91</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/GridSamplingOp.py#L94">94</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/GridSamplingOp.py#L96">96</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/GridSamplingOp.py#L98">98</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperator.py">LinearOperator.py</a></td><td>201</td><td>8</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperator.py#L107">107</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperator.py#L217">217</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperator.py#L244">244</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperator.py#L251">251</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperator.py#L292">292</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperator.py#L301">301</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperator.py#L309">309</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperator.py#L326">326</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py">LinearOperatorMatrix.py</a></td><td>162</td><td>19</td><td>88%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L82">82</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L119">119</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L152">152</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L161">161</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L166">166</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L175-L178">175&ndash;178</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L191-L194">191&ndash;194</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L202-L203">202&ndash;203</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L208-L209">208&ndash;209</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L221">221</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L310">310</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L337">337</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/LinearOperatorMatrix.py#L364">364</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/MultiIdentityOp.py">MultiIdentityOp.py</a></td><td>13</td><td>2</td><td>85%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/MultiIdentityOp.py#L43">43</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/MultiIdentityOp.py#L48">48</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py">NonUniformFastFourierOp.py</a></td><td>188</td><td>10</td><td>95%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L69">69</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L96">96</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L206">206</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L208">208</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L241">241</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L243">243</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L319">319</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L373">373</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L423">423</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/NonUniformFastFourierOp.py#L428">428</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/Operator.py">Operator.py</a></td><td>79</td><td>2</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/Operator.py#L32">32</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/Operator.py#L88">88</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/ProximableFunctionalSeparableSum.py">ProximableFunctionalSeparableSum.py</a></td><td>39</td><td>3</td><td>92%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/ProximableFunctionalSeparableSum.py#L50">50</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/ProximableFunctionalSeparableSum.py#L103">103</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/ProximableFunctionalSeparableSum.py#L110">110</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/SliceProjectionOp.py">SliceProjectionOp.py</a></td><td>174</td><td>8</td><td>95%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/SliceProjectionOp.py#L45">45</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/SliceProjectionOp.py#L62">62</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/SliceProjectionOp.py#L64">64</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/SliceProjectionOp.py#L70">70</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/SliceProjectionOp.py#L206">206</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/SliceProjectionOp.py#L227">227</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/SliceProjectionOp.py#L260">260</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/SliceProjectionOp.py#L300">300</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/WaveletOp.py">WaveletOp.py</a></td><td>119</td><td>5</td><td>96%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/WaveletOp.py#L151">151</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/WaveletOp.py#L169">169</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/WaveletOp.py#L204">204</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/WaveletOp.py#L209">209</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/WaveletOp.py#L232">232</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/ZeroPadOp.py">ZeroPadOp.py</a></td><td>16</td><td>1</td><td>94%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/operators/ZeroPadOp.py#L30">30</a></td></tr><tr><td colspan="5"><b>src/mrpro/phantoms</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py">brainweb.py</a></td><td>275</td><td>26</td><td>91%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L325-L335">325&ndash;335</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L371">371</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L427-L430">427&ndash;430</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L452-L453">452&ndash;453</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L458-L459">458&ndash;459</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L474">474</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L520">520</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L586-L587">586&ndash;587</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L606-L609">606&ndash;609</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L620">620</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L622">622</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L657-L658">657&ndash;658</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/phantoms/brainweb.py#L671">671</a></td></tr><tr><td colspan="5"><b>src/mrpro/utils</b></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/filters.py">filters.py</a></td><td>62</td><td>2</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/filters.py#L44">44</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/filters.py#L49">49</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/reshape.py">reshape.py</a></td><td>95</td><td>2</td><td>98%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/reshape.py#L110">110</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/reshape.py#L301">301</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/slice_profiles.py">slice_profiles.py</a></td><td>47</td><td>6</td><td>87%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/slice_profiles.py#L21">21</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/slice_profiles.py#L37">37</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/slice_profiles.py#L116-L119">116&ndash;119</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/slice_profiles.py#L152">152</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/sliding_window.py">sliding_window.py</a></td><td>34</td><td>1</td><td>97%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/sliding_window.py#L34">34</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/split_idx.py">split_idx.py</a></td><td>10</td><td>2</td><td>80%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/split_idx.py#L43">43</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/split_idx.py#L47">47</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/summarize_tensorvalues.py">summarize_tensorvalues.py</a></td><td>12</td><td>3</td><td>75%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/summarize_tensorvalues.py#L23">23</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/summarize_tensorvalues.py#L25">25</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/summarize_tensorvalues.py#L27">27</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/typing.py">typing.py</a></td><td>69</td><td>55</td><td>20%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/typing.py#L9-L235">9&ndash;235</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py">unit_conversion.py</a></td><td>60</td><td>14</td><td>77%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L32">32</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L40">40</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L42">42</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L49">49</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L51">51</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L58">58</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L60">60</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L69">69</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L80">80</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L82">82</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L101">101</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L103">103</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L124">124</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/unit_conversion.py#L126">126</a></td></tr><tr><td>&nbsp; &nbsp;<a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/zero_pad_or_crop.py">zero_pad_or_crop.py</a></td><td>31</td><td>6</td><td>81%</td><td><a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/zero_pad_or_crop.py#L26">26</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/zero_pad_or_crop.py#L30">30</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/zero_pad_or_crop.py#L55">55</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/zero_pad_or_crop.py#L58">58</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/zero_pad_or_crop.py#L61">61</a>, <a href="https://github.com/PTB-MR/mrpro/blob/1524beb4fd3cd38dad1c6772f48dc73bf39474f5/src/mrpro/utils/zero_pad_or_crop.py#L64">64</a></td></tr><tr><td><b>TOTAL</b></td><td><b>5695</b></td><td><b>468</b></td><td><b>92%</b></td><td>&nbsp;</td></tr></tbody></table></details>

| Tests | Skipped | Failures | Errors | Time |
| ----- | ------- | -------- | -------- | ------------------ |
| 2687 | 0 :zzz: | 0 :x: | 0 :fire: | 2m 7s :stopwatch: |

author_association: CONTRIBUTOR
user: login: fzimmermann89
created_at: 2025-03-05T21:12:45Z
body: I hope I did the merge correctly...
author_association: MEMBER