Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make tests faster #589

Merged
merged 9 commits into from
Jan 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions tests/algorithms/dcf/test_dcf_voronoi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand All @@ -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."""
Expand All @@ -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
Expand Down
9 changes: 5 additions & 4 deletions tests/algorithms/test_optimizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@

@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
a, b = 1.0, 100.0
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]
Expand All @@ -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'
Expand Down
100 changes: 57 additions & 43 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'),
[
Expand Down Expand Up @@ -331,77 +345,77 @@ 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
'non-uniform', # type_k0
'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
'non-uniform', # type_k0
'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
'uniform', # type_k0
'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
'uniform', # type_k0
'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
'non-uniform', # type_k0
'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
Expand All @@ -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',
],
)
12 changes: 6 additions & 6 deletions tests/data/_IsmrmrdRawTestData.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion tests/data/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
6 changes: 2 additions & 4 deletions tests/data/test_dcf_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 8 additions & 8 deletions tests/data/test_kdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]))])
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -520,17 +520,17 @@ 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)

# 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
Expand Down
2 changes: 1 addition & 1 deletion tests/operators/models/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions tests/operators/test_operator_norm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)


Expand Down
Loading