diff --git a/tests/algorithms/dcf/test_dcf_voronoi.py b/tests/algorithms/dcf/test_dcf_voronoi.py index d88e7d2c0..208c94278 100644 --- a/tests/algorithms/dcf/test_dcf_voronoi.py +++ b/tests/algorithms/dcf/test_dcf_voronoi.py @@ -25,11 +25,11 @@ def example_traj_rad_2d(n_kr, n_ka, phi0=0.0, broadcast=True): @pytest.mark.parametrize( ('n_kr', 'n_ka', 'phi0', 'broadcast'), [ - (100, 20, 0, True), - (100, 1, 0, True), - (100, 20, torch.pi / 4, True), - (100, 1, torch.pi / 4, True), - (100, 1, 0, False), + (20, 20, 0, True), + (20, 1, 0, True), + (20, 20, torch.pi / 4, True), + (20, 1, torch.pi / 4, True), + (20, 1, 0, False), ], ) def test_dcf_rad_traj_voronoi(n_kr, n_ka, phi0, broadcast): @@ -56,7 +56,7 @@ def test_dcf_rad_traj_voronoi(n_kr, n_ka, phi0, broadcast): assert dcf.shape == traj.broadcasted_shape, 'DCF shape should match broadcasted trajectory shape' -@pytest.mark.parametrize(('n_k2', 'n_k1', 'n_k0'), [(40, 16, 20), (1, 2, 2)]) +@pytest.mark.parametrize(('n_k2', 'n_k1', 'n_k0'), [(4, 6, 8), (1, 2, 2)]) def test_dcf_3d_cart_traj_broadcast_voronoi(n_k2, n_k1, n_k0): """Compare voronoi-based dcf calculation for broadcasted 3D regular Cartesian trajectory to analytical solution which is 1 for each k-space @@ -76,7 +76,7 @@ def test_dcf_3d_cart_traj_broadcast_voronoi(n_k2, n_k1, n_k0): torch.testing.assert_close(dcf[:, 1:-1, 1:-1, 1:-1], dcf_analytical[:, 1:-1, 1:-1, 1:-1]) -@pytest.mark.parametrize(('n_k2', 'n_k1', 'n_k0'), [(40, 16, 20), (1, 2, 2)]) +@pytest.mark.parametrize(('n_k2', 'n_k1', 'n_k0'), [(4, 6, 8), (1, 2, 2)]) def test_dcf_3d_cart_full_traj_voronoi(n_k2, n_k1, n_k0): """Compare voronoi-based dcf calculation for full 3D regular Cartesian trajectory to analytical solution which is 1 for each k-space point.""" @@ -103,7 +103,7 @@ def test_dcf_3d_cart_full_traj_voronoi(n_k2, n_k1, n_k0): @pytest.mark.parametrize( ('n_k2', 'n_k1', 'n_k0', 'k2_steps', 'k1_steps', 'k0_steps'), - [(30, 20, 10, (1.0, 0.5, 0.25), (1.0, 0.5), (1.0,))], + [(4, 6, 8, (1.0, 0.5, 0.25), (1.0, 0.5), (1.0,))], ) def test_dcf_3d_cart_nonuniform_traj_voronoi(n_k2, n_k1, n_k0, k2_steps, k1_steps, k0_steps): """Compare voronoi-based dcf calculation for 3D nonuniform Cartesian diff --git a/tests/algorithms/test_optimizers.py b/tests/algorithms/test_optimizers.py index bda6820e1..ddc355841 100644 --- a/tests/algorithms/test_optimizers.py +++ b/tests/algorithms/test_optimizers.py @@ -9,7 +9,8 @@ @pytest.mark.parametrize('enforce_bounds_on_x1', [True, False]) @pytest.mark.parametrize( - ('optimizer', 'optimizer_kwargs'), [(adam, {'lr': 0.02, 'max_iter': 10000}), (lbfgs, {'lr': 1.0})] + ('optimizer', 'optimizer_kwargs'), + [(adam, {'lr': 0.02, 'max_iter': 2000, 'betas': (0.8, 0.999)}), (lbfgs, {'lr': 1.0, 'max_iter': 20})], ) def test_optimizers_rosenbrock(optimizer, enforce_bounds_on_x1, optimizer_kwargs): # use Rosenbrock function as test case with 2D test data @@ -17,8 +18,8 @@ def test_optimizers_rosenbrock(optimizer, enforce_bounds_on_x1, optimizer_kwargs rosen_brock = Rosenbrock(a, b) # initial point of optimization - x1 = torch.tensor([a / 3.14]) - x2 = torch.tensor([3.14]) + x1 = torch.tensor([a / 1.23]) + x2 = torch.tensor([1.23]) x1.grad = torch.tensor([2.78]) x2.grad = torch.tensor([-1.0]) params_init = [x1, x2] @@ -45,7 +46,7 @@ def test_optimizers_rosenbrock(optimizer, enforce_bounds_on_x1, optimizer_kwargs params_result = constrain_op(*params_result) # obtained solution should match analytical - torch.testing.assert_close(torch.tensor(params_result), analytical_solution) + torch.testing.assert_close(torch.tensor(params_result), analytical_solution, rtol=1e-4, atol=0) for p, before, grad_before in zip(params_init, params_init_before, params_init_grad_before, strict=True): assert p == before, 'the initial parameter should not have changed during optimization' diff --git a/tests/conftest.py b/tests/conftest.py index 30ae9c229..477b93be4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -192,7 +192,7 @@ def random_acq_info(random_acquisition): return acq_info -@pytest.fixture(params=({'seed': 0, 'n_other': 10, 'n_k2': 40, 'n_k1': 20},)) +@pytest.fixture(params=({'seed': 0, 'n_other': 4, 'n_k2': 12, 'n_k1': 14},)) def random_kheader_shape(request, random_acquisition, random_full_ismrmrd_header): """Random (not necessarily valid) KHeader with defined shape.""" # Get dimensions @@ -263,6 +263,20 @@ def ismrmrd_cart(ellipse_phantom, tmp_path_factory): return ismrmrd_kdata +@pytest.fixture(scope='session') +def ismrmrd_cart_high_res(ellipse_phantom, tmp_path_factory): + """Fully sampled cartesian data set.""" + ismrmrd_filename = tmp_path_factory.mktemp('mrpro') / 'ismrmrd_cart_high_res.h5' + ismrmrd_kdata = IsmrmrdRawTestData( + filename=ismrmrd_filename, + matrix_size=256, + noise_level=0.0, + repetitions=3, + phantom=ellipse_phantom.phantom, + ) + return ismrmrd_kdata + + 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'), [ @@ -331,12 +345,12 @@ def ismrmrd_cart(ellipse_phantom, tmp_path_factory): 'zero', # type_k1 'zero', # type_k2 ), - ( # (5) 3d non-uniform, 4 coils, 2 other - (2, 4, 16, 32, 64), # im_shape - (2, 4, 16, 32, 64), # k_shape - (2, 16, 32, 64), # nkx - (2, 16, 32, 64), # nky - (2, 16, 32, 64), # nkz + ( # (5) 3d non-uniform, 3 coils, 2 other + (2, 3, 10, 12, 14), # im_shape + (2, 3, 6, 8, 10), # k_shape + (2, 6, 8, 10), # nkx + (2, 6, 8, 10), # nky + (2, 6, 8, 10), # nkz 'non-uniform', # type_kx 'non-uniform', # type_ky 'non-uniform', # type_kz @@ -344,12 +358,12 @@ def ismrmrd_cart(ellipse_phantom, tmp_path_factory): 'non-uniform', # type_k1 'non-uniform', # type_k2 ), - ( # (6) 2d non-uniform cine with 8 cardiac phases, 5 coils - (8, 5, 1, 64, 64), # im_shape - (8, 5, 1, 18, 128), # k_shape - (8, 1, 18, 128), # nkx - (8, 1, 18, 128), # nky - (8, 1, 1, 1), # nkz + ( # (6) 2d non-uniform cine with 3 cardiac phases, 2 coils + (3, 2, 1, 64, 64), # im_shape + (3, 2, 1, 18, 128), # k_shape + (3, 1, 18, 128), # nkx + (3, 1, 18, 128), # nky + (3, 1, 1, 1), # nkz 'non-uniform', # type_kx 'non-uniform', # type_ky 'zero', # type_kz @@ -357,12 +371,12 @@ def ismrmrd_cart(ellipse_phantom, tmp_path_factory): 'non-uniform', # type_k1 'zero', # type_k2 ), - ( # (7) 2d cartesian cine with 9 cardiac phases, 6 coils - (9, 6, 1, 96, 128), # im_shape - (9, 6, 1, 128, 192), # k_shape - (9, 1, 1, 192), # nkx - (9, 1, 128, 1), # nky - (9, 1, 1, 1), # nkz + ( # (7) 2d cartesian cine with 2 cardiac phases, 3 coils + (2, 3, 1, 96, 128), # im_shape + (2, 3, 1, 128, 192), # k_shape + (2, 1, 1, 192), # nkx + (2, 1, 128, 1), # nky + (2, 1, 1, 1), # nkz 'uniform', # type_kx 'uniform', # type_ky 'zero', # type_kz @@ -370,12 +384,12 @@ def ismrmrd_cart(ellipse_phantom, tmp_path_factory): 'uniform', # type_k1 'zero', # type_k2 ), - ( # (8) radial phase encoding (RPE), 8 coils, with oversampling in both FFT and non-uniform directions - (2, 8, 64, 32, 48), # im_shape - (2, 8, 8, 64, 96), # k_shape - (2, 1, 1, 96), # nkx - (2, 8, 64, 1), # nky - (2, 8, 64, 1), # nkz + ( # (8) radial phase encoding (RPE), 3 coils, with oversampling in both FFT and non-uniform directions + (2, 3, 12, 14, 16), # im_shape + (2, 3, 8, 10, 12), # k_shape + (2, 1, 1, 12), # nkx + (2, 8, 10, 1), # nky + (2, 8, 10, 1), # nkz 'uniform', # type_kx 'non-uniform', # type_ky 'non-uniform', # type_kz @@ -383,12 +397,12 @@ def ismrmrd_cart(ellipse_phantom, tmp_path_factory): 'non-uniform', # type_k1 'non-uniform', # type_k2 ), - ( # (9) radial phase encoding (RPE), 8 coils with non-Cartesian sampling along readout - (2, 8, 64, 32, 48), # im_shape - (2, 8, 8, 64, 96), # k_shape - (2, 1, 1, 96), # nkx - (2, 8, 64, 1), # nky - (2, 8, 64, 1), # nkz + ( # (9) radial phase encoding (RPE), 2 coils with non-Cartesian sampling along readout + (2, 2, 12, 14, 16), # im_shape + (2, 2, 8, 10, 12), # k_shape + (2, 2, 1, 12), # nkx + (2, 2, 10, 1), # nky + (2, 2, 10, 1), # nkz 'non-uniform', # type_kx 'non-uniform', # type_ky 'non-uniform', # type_kz @@ -396,12 +410,12 @@ def ismrmrd_cart(ellipse_phantom, tmp_path_factory): 'non-uniform', # type_k1 'non-uniform', # type_k2 ), - ( # (10) stack of stars, 5 other, 3 coil, oversampling in both FFT and non-uniform directions - (5, 3, 48, 16, 32), # im_shape - (5, 3, 96, 18, 64), # k_shape - (5, 1, 18, 64), # nkx - (5, 1, 18, 64), # nky - (5, 96, 1, 1), # nkz + ( # (10) stack of stars, 3 other, 2 coil, oversampling in both FFT and non-uniform directions + (3, 2, 24, 16, 32), # im_shape + (3, 2, 48, 12, 14), # k_shape + (3, 1, 12, 14), # nkx + (3, 1, 12, 14), # nky + (3, 48, 1, 1), # nkz 'non-uniform', # type_kx 'non-uniform', # type_ky 'uniform', # type_kz @@ -416,11 +430,11 @@ def ismrmrd_cart(ellipse_phantom, tmp_path_factory): '2d_non_cartesian_mri_2_coils', '2d_cartesian_irregular_sampling', '2d_single_shot_spiral', - '3d_nonuniform_4_coils_2_other', - '2d_nnonuniform_cine_mri_8_cardiac_phases_5_coils', - '2d_cartesian_cine_9_cardiac_phases_6_coils', - 'radial_phase_encoding_8_coils_with_oversampling', - 'radial_phase_encoding_8_coils_non_cartesian_sampling', - 'stack_of_stars_5_other_3_coil_with_oversampling', + '3d_nonuniform_3_coils_2_other', + '2d_nonuniform_cine_mri_3_cardiac_phases_2_coils', + '2d_cartesian_cine_2_cardiac_phases_3_coils', + 'radial_phase_encoding_3_coils_with_oversampling', + 'radial_phase_encoding_2_coils_non_cartesian_sampling', + 'stack_of_stars_3_other_2_coil_with_oversampling', ], ) diff --git a/tests/data/_IsmrmrdRawTestData.py b/tests/data/_IsmrmrdRawTestData.py index efeff6ed1..89e5ac901 100644 --- a/tests/data/_IsmrmrdRawTestData.py +++ b/tests/data/_IsmrmrdRawTestData.py @@ -59,8 +59,8 @@ class IsmrmrdRawTestData: def __init__( self, filename: str | Path, - matrix_size: int = 256, - n_coils: int = 8, + matrix_size: int = 64, + n_coils: int = 4, oversampling: int = 2, repetitions: int = 1, flag_invalid_reps: bool = False, @@ -167,12 +167,12 @@ def __init__( # Encoded and recon spaces encoding_fov = ismrmrd.xsd.fieldOfViewMm() - encoding_fov.x = self.oversampling * 256 - encoding_fov.y = 256 + encoding_fov.x = self.oversampling * matrix_size + encoding_fov.y = matrix_size encoding_fov.z = 5 recon_fov = ismrmrd.xsd.fieldOfViewMm() - recon_fov.x = 256 - recon_fov.y = 256 + recon_fov.x = matrix_size + recon_fov.y = matrix_size recon_fov.z = 5 encoding_matrix = ismrmrd.xsd.matrixSizeType() diff --git a/tests/data/conftest.py b/tests/data/conftest.py index bd37b28c2..1d92b14a5 100644 --- a/tests/data/conftest.py +++ b/tests/data/conftest.py @@ -50,7 +50,7 @@ def random_mandatory_ismrmrd_header(request) -> xsd.ismrmrdschema.ismrmrdHeader: return xsd.ismrmrdschema.ismrmrdHeader(encoding=[encoding], experimentalConditions=experimental_conditions) -@pytest.fixture(params=({'seed': 0, 'n_other': 2, 'n_coils': 16, 'n_z': 32, 'n_y': 128, 'n_x': 256},)) +@pytest.fixture(params=({'seed': 0, 'n_other': 2, 'n_coils': 8, 'n_z': 16, 'n_y': 32, 'n_x': 64},)) def random_test_data(request): seed, n_other, n_coils, n_z, n_y, n_x = ( request.param['seed'], diff --git a/tests/data/test_dcf_data.py b/tests/data/test_dcf_data.py index 314ad2ba2..7df7ea256 100644 --- a/tests/data/test_dcf_data.py +++ b/tests/data/test_dcf_data.py @@ -53,11 +53,9 @@ def test_dcf_spiral_traj_voronoi(n_kr, n_ki, n_ka): def test_dcf_spiral_traj_voronoi_singlespiral(): """For three z-stacked spirals in the x,y plane, the center spiral should be the same as a single 2D spiral. - - Issue #84 """ - n_kr = 100 # points along each spiral ar - n_ki = 5 # turns per spiral arm spirals nka spiral arms + n_kr = 30 # points along each spiral arm + n_ki = 5 # turns per spiral arm trajectory_single = example_traj_spiral_2d(n_kr, n_ki, 1) # A new trajectroy with three spirals stacked in z direction. diff --git a/tests/data/test_kdata.py b/tests/data/test_kdata.py index fa3e4ebd9..42cd061f6 100644 --- a/tests/data/test_kdata.py +++ b/tests/data/test_kdata.py @@ -181,16 +181,16 @@ def test_KData_calibration_lines(ismrmrd_cart_with_calibration_lines): assert kdata.data.shape[-2] == ismrmrd_cart_with_calibration_lines.n_separate_calibration_lines -def test_KData_kspace(ismrmrd_cart): +def test_KData_kspace(ismrmrd_cart_high_res): """Read in data and verify k-space by comparing reconstructed image.""" - kdata = KData.from_file(ismrmrd_cart.filename, DummyTrajectory()) + kdata = KData.from_file(ismrmrd_cart_high_res.filename, DummyTrajectory()) ff_op = FastFourierOp(dim=(-1, -2)) (reconstructed_img,) = ff_op.adjoint(kdata.data) # Due to discretisation artifacts the reconstructed image will be different to the reference image. Using standard # testing functions such as numpy.testing.assert_almost_equal fails because there are few voxels with high # differences along the edges of the elliptic objects. - assert relative_image_difference(reconstructed_img[0, 0, 0, ...], ismrmrd_cart.img_ref) <= 0.05 + assert relative_image_difference(reconstructed_img[0, 0, 0, ...], ismrmrd_cart_high_res.img_ref) <= 0.05 @pytest.mark.parametrize(('field', 'value'), [('lamor_frequency_proton', 42.88 * 1e6), ('tr', torch.tensor([24.3]))]) @@ -423,8 +423,8 @@ def test_KData_split_k2_into_other(consistently_shaped_kdata, monkeypatch, n_oth ('subset_label', 'subset_idx'), [ ('repetition', torch.tensor([1], dtype=torch.int32)), - ('average', torch.tensor([3, 4, 5], dtype=torch.int32)), - ('phase', torch.tensor([2, 2, 8], dtype=torch.int32)), + ('average', torch.tensor([1, 2, 3], dtype=torch.int32)), + ('phase', torch.tensor([2, 2, 3], dtype=torch.int32)), ], ) def test_KData_select_other_subset(consistently_shaped_kdata, monkeypatch, subset_label, subset_idx): @@ -520,9 +520,9 @@ def test_modify_acq_info(random_kheader_shape): assert kheader.acq_info.position.z.shape == (n_other, n_k2, n_k1, 1) -def test_KData_compress_coils(ismrmrd_cart): +def test_KData_compress_coils(ismrmrd_cart_high_res): """Test coil combination does not alter image content (much).""" - kdata = KData.from_file(ismrmrd_cart.filename, DummyTrajectory()) + kdata = KData.from_file(ismrmrd_cart_high_res.filename, DummyTrajectory()) kdata = kdata.compress_coils(n_compressed_coils=4) ff_op = FastFourierOp(dim=(-1, -2)) (reconstructed_img,) = ff_op.adjoint(kdata.data) @@ -530,7 +530,7 @@ def test_KData_compress_coils(ismrmrd_cart): # Image content of each coil is the same. Therefore we only compare one coil image but we need to normalize. reconstructed_img = reconstructed_img[0, 0, 0, ...].abs() reconstructed_img /= reconstructed_img.max() - ref_img = ismrmrd_cart.img_ref[0, 0, 0, ...].abs() + ref_img = ismrmrd_cart_high_res.img_ref[0, 0, 0, ...].abs() ref_img /= ref_img.max() assert relative_image_difference(reconstructed_img, ref_img) <= 0.1 diff --git a/tests/operators/models/conftest.py b/tests/operators/models/conftest.py index 4aab81ae0..7ebc58ad4 100644 --- a/tests/operators/models/conftest.py +++ b/tests/operators/models/conftest.py @@ -47,7 +47,7 @@ def create_parameter_tensor_tuples( - parameter_shape=(10, 5, 100, 100, 100), number_of_tensors=2 + parameter_shape=(10, 5, 12, 14, 16), number_of_tensors=2 ) -> tuple[torch.Tensor, ...]: """Create tuples of tensors as input to operators.""" random_generator = RandomGenerator(seed=0) diff --git a/tests/operators/test_operator_norm.py b/tests/operators/test_operator_norm.py index c6e13dcf9..387254384 100644 --- a/tests/operators/test_operator_norm.py +++ b/tests/operators/test_operator_norm.py @@ -117,17 +117,17 @@ def test_finite_difference_operator_norm(dim): finite_difference_operator = FiniteDifferenceOp(dim=dim, mode='forward') # initialize random image of appropriate shape depending on the dimensionality - image_shape = (1, *tuple([16 for _ in range(len(dim))])) + image_shape = (1, *([8] * len(dim))) random_image = random_generator.complex64_tensor(size=image_shape) # calculate the operator norm - finite_difference_operator_norm = finite_difference_operator.operator_norm(random_image, dim=dim, max_iterations=64) + finite_difference_operator_norm = finite_difference_operator.operator_norm(random_image, dim=dim, max_iterations=32) # closed form solution of the operator norm finite_difference_operator_norm_true = sqrt(len(dim) * 4) torch.testing.assert_close( - finite_difference_operator_norm.item(), finite_difference_operator_norm_true, atol=1e-2, rtol=1e-2 + finite_difference_operator_norm.item(), finite_difference_operator_norm_true, atol=0.1, rtol=0 )