def test_invalid_names(self): shape = (10, 11) img = np.zeros(shape) df = DisplacementField.generate(shape, approach="identity") with pytest.raises(KeyError): df.warp(img, interpolation="fake_interpolation") with pytest.raises(KeyError): df.warp(img, border_mode="fake_border_mode")
def test_id_compose_id_gives_id(self): """Just test that composing identities gives an identity.""" shape = (300, 200) delta_x = np.zeros(shape) delta_y = np.zeros(shape) df = DisplacementField(delta_x, delta_y) # I(I(x)) = I(x) assert df == df(df) # __eq__ implemented
def test_singe_pixel_shift(self, img_dummy, interpolation, border_mode, direction): """Test whether displacing a single pixel to a neighbouring pixel works as expected.""" shape = img_dummy.shape img_dummy[5, 7] = 1 # only one pixel has a nonzero intensity delta_x = np.zeros(shape) delta_y = np.zeros(shape) if direction == "U": delta_y[15, 7] = -10 img_exp = np.zeros(shape) img_exp[[15, 5], 7] = 1 elif direction == "D": delta_y[3, 7] = 2 img_exp = np.zeros(shape) img_exp[[3, 5], 7] = 1 elif direction == "L": delta_x[5, 10] = ( -3 ) # When looking for [5, 4], [5, 5] in the original picture will be used img_exp = np.zeros(shape) img_exp[5, [7, 10]] = 1 elif direction == "R": delta_x[5, 6] = 1 # img_exp = np.zeros(shape) img_exp[5, [6, 7]] = 1 else: raise ValueError("Unknown direction {}".format(direction)) df_dummy = DisplacementField(delta_x, delta_y) img_warped = df_dummy.warp( img_dummy, interpolation=interpolation, border_mode=border_mode) # dummy=moving, img_exp=reference assert np.allclose(img_warped, img_exp)
def test_conversion_possible(self, df_is, metric_name): """List of DisplacementField instances to ndarray.""" custom_list = [ DisplacementField.generate((20, 12), approach="identity") for _ in range(2) ] y_true = np.zeros((2, 20, 12, 2)) if df_is == "pred" else custom_list y_pred = np.zeros((2, 20, 12, 2)) if df_is == "true" else custom_list _, _ = ALL_DVF_METRICS[metric_name](y_true, y_pred)
def test_identity(self): """Test that does not affect identity mapping.""" shape = (20, 23) delta_x = np.zeros(shape) delta_y = np.zeros(shape) df = DisplacementField(delta_x, delta_y) assert df * 3 == df assert 1001.21 * df == df
def test_constant_transform(self, img, interpolation, border_mode): """Make sure that its possible to map all pixels from fixed into a single pixel in input.""" shape = img.shape img_ = img.copy() r_star, c_star = 10, 13 value_star = img_[r_star, c_star] x, y = np.meshgrid(list(range(shape[1])), list(range(shape[0]))) delta_x = c_star - x delta_y = r_star - y df = DisplacementField(delta_x, delta_y) img_warped = df.warp(img_, interpolation=interpolation, border_mode=border_mode) assert np.allclose(np.ones(shape) * value_star, img_warped)
def df_id(request): """Generate an identity transformation. In order to specify a shape one decorates the test function in the following way: `@pytest.mark.parametrize('df_id', [(320, 456)], indirect=True)` """ if hasattr(request, "param"): shape = request.param else: shape = (10, 11) return DisplacementField.generate(shape, approach="identity")
def test_scaled_delta_xy(self): """The goal is to make sure that one can access scaled versions of delta_x and delta_y""" shape = (h, w) = (200, 400) delta_x = np.ones(shape, dtype=np.float32) * 5 delta_y = np.ones(shape, dtype=np.float32) * 8 df = DisplacementField(delta_x, delta_y) assert np.allclose(df.delta_x_scaled, (delta_x / w)) and np.allclose( df.delta_y_scaled, (delta_y / h))
def test_inactive(self): """Create a wild transform without leaving the image space.""" random_state = 55 shape = (_, w) = (40, 30) n_pixels = np.prod(shape) np.random.seed(random_state) perm = np.random.permutation(n_pixels) delta_x = np.zeros(shape) delta_y = np.zeros(shape) for i, x in enumerate(perm): r_inp, c_inp = i // w, i % w r_out, c_out = x // w, x % w delta_x[r_inp, c_inp] = c_out - c_inp delta_y[r_inp, c_inp] = r_out - r_inp df = DisplacementField(delta_x, delta_y) assert df == df.adjust() delta_x_new = delta_x.copy() delta_x_new[0, 0] = -1 df_new = DisplacementField(delta_x_new, delta_y) assert df_new != df_new.adjust()
def test_correct_scale(self): """Create outsider pixels on purpose with clear shrunk vectors.""" shape = (h, w) = (40, 30) delta_x = np.zeros(shape) delta_y = np.zeros(shape) delta_x_correct = np.zeros(shape) delta_y_correct = np.zeros(shape) # Right delta_x[0, 0] = 100 delta_x_correct[0, 0] = w - 1 - 0 # Up delta_y[15, 10] = -100 delta_y_correct[15, 10] = -15 # Down delta_y[25, 11] = 100000 delta_y_correct[25, 11] = h - 1 - 25 # Left delta_x[1, 13] = -131231 delta_x_correct[1, 13] = -13 # random_1 delta_x[2, 4] = 10 delta_y[2, 4] = 100 c_1 = (h - 1 - 2) / 100 delta_x_correct[2, 4] = c_1 * 10 delta_y_correct[2, 4] = c_1 * 100 df = DisplacementField(delta_x, delta_y) df_correct = DisplacementField(delta_x_correct, delta_y_correct) assert TestAdjust.eps_equal(df.adjust(force_inside_border=True), df_correct)
def test_correct_result(self): """Test that giving correct results.""" c = 11.3 delta_x = np.array([[1.2, 123], [13, 12.1]]) delta_y = np.array([[121.2, 12], [13.88, 12.1]]) df_mul = DisplacementField(delta_x, delta_y) * c assert np.allclose(delta_x * c, df_mul.delta_x) assert np.allclose(delta_y * c, df_mul.delta_y)
def test_returns_new_array(self, img, interpolation, border_mode): """Test that new image is saved in a new array.""" shape = img.shape[:2] df = DisplacementField.generate(shape, approach="identity") img_warped = df.warp(img, interpolation=interpolation, border_mode=border_mode) assert not np.may_share_memory(img, img_warped) assert not np.shares_memory(img, img_warped)
def test_wrong_inputs_2(self): """Test that no control points are not allowed. Notes ----- Note that if all 3 inputs are None and anchor_corners is True, then automatically generates 4 control points in the background. """ shape = (40, 50) points = np.zeros((0, 2)) values_delta_x = np.array([]) values_delta_y = np.array([]) with pytest.raises(ValueError): DisplacementField.generate( shape, approach="control_points", points=points, values_delta_x=values_delta_x, values_delta_y=values_delta_y, )
def test_basic(self, monkeypatch): shape = (10, 11) df = DisplacementField.generate(shape, approach="identity") fake_ax = Mock() fake_plt = Mock() fake_plt.subplots = lambda *args, **kwargs: (None, fake_ax) monkeypatch.setattr("atlalign.base.plt", fake_plt) df.plot_ranges() assert fake_ax.scatter.call_count > 0 assert fake_ax.legend.call_count > 0
def test_interpolation_subgrid(self): shape = (14, 16) # center is 7, 8 eps = 1e-6 delta_x = np.random.randint(1, 20, size=shape) delta_y = np.random.randint(1, 20, size=shape) df = DisplacementField(delta_x, delta_y) df_anchored_interpolated = df.anchor(h_kept=4, w_kept=4, ds_f=1, smooth=0) df_anchored_smoothened = df.anchor(h_kept=4, w_kept=4, ds_f=1, smooth=1) assert np.allclose(df_anchored_interpolated.delta_x[5:9, 6:10], df.delta_x[5:9, 6:10]) assert np.allclose(df_anchored_interpolated.delta_y[5:9, 6:10], df.delta_y[5:9, 6:10]) assert not np.allclose(df_anchored_smoothened.delta_x[5:9, 6:10], df.delta_x[5:9, 6:10]) assert not np.allclose(df_anchored_smoothened.delta_y[5:9, 6:10], df.delta_y[5:9, 6:10]) assert abs(df_anchored_interpolated.delta_x[0, 0]) < eps assert abs(df_anchored_interpolated.delta_x[0, -1]) < eps assert abs(df_anchored_interpolated.delta_x[-1, 0]) < eps assert abs(df_anchored_interpolated.delta_x[-1, -1]) < eps assert abs(df_anchored_interpolated.delta_y[0, 0]) < eps assert abs(df_anchored_interpolated.delta_y[0, -1]) < eps assert abs(df_anchored_interpolated.delta_y[-1, 0]) < eps assert abs(df_anchored_interpolated.delta_y[-1, -1]) < eps
def test_is_valid(self): """Focused on the is_valid property""" shape = (3, 4) # 1 - valid delta_x_1 = np.zeros(shape) delta_y_1 = np.zeros(shape) df_1 = DisplacementField(delta_x_1, delta_y_1) # 2 - invalid delta_x_2 = np.zeros(shape) delta_y_2 = np.zeros(shape) delta_x_2[0, 1] = np.nan df_2 = DisplacementField(delta_x_2, delta_y_2) # 3 - valid delta_x_3 = np.zeros(shape) delta_y_3 = np.zeros(shape) delta_x_3[0, 2] = np.inf df_3 = DisplacementField(delta_x_3, delta_y_3) assert df_1.is_valid assert not df_2.is_valid assert not df_3.is_valid
def test_no_edges(self): """Test that no edges lead to identity mapping.""" shape = (20, 13) # No edges df_1 = DisplacementField.generate( shape, approach="edge_stretching", edge_mask=np.zeros(shape, dtype=bool), n_perturbation_points=10, ) # No perturbation points df_2 = DisplacementField.generate( shape, approach="edge_stretching", edge_mask=np.ones(shape, dtype=bool), n_perturbation_points=0, ) df_id = DisplacementField(np.zeros(shape), np.zeros(shape)) assert df_id == df_1 assert df_id == df_2
def test_off_grid(self): """Map one pixel into an offgrid elemenent.""" shape = (30, 20) delta_x = np.zeros(shape) delta_y = np.zeros(shape) delta_x[5, 9] = 0.7 delta_y[5, 9] = 0.7 ix = np.ones(shape, dtype=bool) ix[5, 9] = False df = DisplacementField(delta_x, delta_y) df_inv = df.pseudo_inverse() assert np.allclose(df.delta_x[ix], df_inv.delta_x[ix]) and np.allclose( df.delta_y[ix], df_inv.delta_y[ix]) # Interpolation will never give the precise value assert not np.isclose(df.delta_x[5, 9], df_inv.delta_x[5, 9]) and not np.isclose( df.delta_y[5, 9], df_inv.delta_y[5, 9])
def test_no_new_classes(self, random_state, approach, dtype): """No creation of new classes - exactly one nearest neighbour.""" shape = (9, 10) df = DisplacementField.generate(shape, approach="affine_simple", rotation=np.pi / 10) np.random.seed(random_state) img = np.random.randint(300, size=shape).astype( dtype) # might overflow to something but whatever img_warped = df.warp_annotation(img, approach=approach) assert set(np.unique(img_warped)).issubset(set(np.unique(img)))
def test_approaches_equivalent(self, dtype): """Make sure approaches equivalent""" shape = (10, 11) img = np.random.randint(1, 256, size=shape).astype(dtype) df = DisplacementField.generate(shape, approach="affine_simple", rotation=np.pi / 10) all_results = [ df.warp_annotation(img, approach=x) for x in SUPPORTED_APPROACHES_ANNOTATIONS ] for i in range(len(all_results) - 1): assert np.array_equal(all_results[i], all_results[i + 1])
def test_correct_type(self): """Test that only works for numbers""" shape = (20, 23) c_1 = 123 c_2 = 31241.1121 c_3 = "3213" delta_x = np.zeros(shape) delta_y = np.zeros(shape) df = DisplacementField(delta_x, delta_y) assert isinstance(df * c_1, DisplacementField) assert isinstance(df * c_2, DisplacementField) with pytest.raises(TypeError): df * c_3
def test_symmetric(self, img_grayscale_float, metric): """Test that the order of the input images does not change the metric value. Notes ----- Excluding 'mi_m', 'mi_h' because there is some crazy irreproducibility in ANTsPy. """ size = img_grayscale_float.shape img_true = img_grayscale_float df = DisplacementField.generate(size, approach="affine_simple", rotation=1) img_pred = df.warp(img_grayscale_float) assert ALL_IMG_METRICS[metric]( img_true, img_pred) == ALL_IMG_METRICS[metric](img_pred, img_true)
def test_one_point_collapse(self): """Mapping all points into 1 point results in a noninvertible mapping.""" shape = (3, 4) collapse_point = (2, 2) # r, c delta_x = np.zeros(shape) delta_y = np.zeros(shape) for r in range(shape[0]): for c in range(shape[1]): delta_x[r, c] = collapse_point[1] - c delta_y[r, c] = collapse_point[0] - r df = DisplacementField(delta_x, delta_y) assert np.allclose(df.jacobian, 0)
def get_transform(p, dataset_id, ds_f=1): """Get transformation given a fixed coronal section. Parameters ---------- p : int Coronal slice coordinate in microns. dataset_id : int Id of the section dataset. Used to determine the 3D matrix. ds_f : int, optional Downsampling factor. If set to 1 no downsampling takes place. Note that if `ds_f` = 25, then we obtain the shape (320, 456). Returns ------- df : DisplacementField Displacement field of shape (8000 // `ds_f`, 11400 // `ds_f`, ?) representing reference -> moved transformation. """ output_shape = (8000 // ds_f, 11400 // ds_f) y, x = np.indices(output_shape) grid = np.stack((x.ravel(), y.ravel())).T i_list = list(grid[:, 1] * ds_f) r_list = list(grid[:, 0] * ds_f) output_grid = np.empty_like(grid) x_list, y_list, _, _ = pir_to_xy_local_coronal(p, i_list, r_list, dataset_id=dataset_id) output_grid[:, 1] = y_list output_grid[:, 0] = x_list df = DisplacementField.from_transform( output_grid[:, 0].reshape(output_shape), output_grid[:, 1].reshape(output_shape)) return df
def test_control_points_correspond(self, interpolation_input): """Test that the interpolation is precise on control points. Notes ----- This is a terrible test since we are using SmoothSplines and they do not necessarily construct a interpolation such that it is equal to the known values on the control points. So if the equality tests are failing (which they probably will for more control points) then it does not mean our scheme is not working. """ interpolation_method, interpolator_kwargs = interpolation_input eps = 1e-5 # This is negligible since the control point values are integers (see below) shape = (30, 20) n_pixels = np.prod(shape) n_control_points = 16 random_state = 12 # For bspline to work there needs to be at least (16 points) np.random.seed(random_state) points = np.array([[x // shape[1], x % shape[1]] for x in np.random.choice( n_pixels, size=n_control_points, replace=False) ]) values_delta_x = np.random.randint(10, size=n_control_points) values_delta_y = np.random.randint(11, size=n_control_points) df = DisplacementField.generate( shape, approach="control_points", points=points, values_delta_x=values_delta_x, values_delta_y=values_delta_y, anchor_corners=False, interpolation_method=interpolation_method, interpolator_kwargs=interpolator_kwargs, ) for i, p in enumerate(points): assert abs(values_delta_x[i] - df.delta_x[p[0], p[1]]) < eps assert abs(values_delta_y[i] - df.delta_y[p[0], p[1]]) < eps
def test_augment(self, monkeypatch, path_test_data, tmpdir, n_iter, anchor, is_valid): fake_es = Mock(return_value=DisplacementField.generate( (320, 456), approach="affine_simple", rotation=0.2)) max_corrupted_pixels = 10 if not is_valid: max_corrupted_pixels = 0 # hack that will force to use the original monkeypatch.setattr("atlalign.zoo.edge_stretching", fake_es) da = DatasetAugmenter(path_test_data / "supervised_dataset.h5") output_path = pathlib.Path(str(tmpdir)) / "output.h5" da.augment( output_path, n_iter=n_iter, anchor=anchor, max_trials=2, max_corrupted_pixels=max_corrupted_pixels, ds_f=32, ) assert output_path.exists() keys = [ "dataset_id", "deltas_xy", "image_id", "img", "inv_deltas_xy", "p" ] for key in keys: array = load_dataset_in_memory(output_path, key) assert da.n_orig * n_iter == len(array) assert not np.any(np.isnan(array)) keys = ["dataset_id", "image_id", "p"] for key in keys: original_a = load_dataset_in_memory(da.original_path, key) new_a = load_dataset_in_memory(output_path, key) new_a_expected = np.concatenate(n_iter * [original_a]) np.testing.assert_equal(new_a, new_a_expected)
def test_incorrect_input(self): """Test that wrong inputs lead to an error.""" shape = (40, 10) delta_x = np.zeros(shape) delta_y = np.zeros(shape) df = DisplacementField(delta_x, delta_y) with pytest.raises(ValueError): df.resize((1, 1, 2)) with pytest.raises(TypeError): df.resize("some_str")
def test_different_results(self, tmp_path, img_grayscale_uint, registration_type): """Make sure that the registration is not reproducible if some environment variables not set. The environment variables are ANTS_RANDOM_SEED and ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS. """ size = img_grayscale_uint.shape df = DisplacementField.generate( size, approach="affine_simple", translation_x=20 ) moving_img = df.warp(img_grayscale_uint) df_final1, meta1 = antspy_registration( img_grayscale_uint, moving_img, path=tmp_path ) df_final2, meta2 = antspy_registration( img_grayscale_uint, moving_img, path=tmp_path ) assert not np.allclose(df_final1.delta_x, df_final2.delta_x, atol=0.1) assert not np.allclose(df_final1.delta_y, df_final2.delta_y, atol=0.1)
def test_mask(self, img_grayscale_float, metric): """Test that the mask is working.""" size = img_grayscale_float.shape img_true = img_grayscale_float df = DisplacementField.generate(size, approach="affine_simple", rotation=1) img_pred_1 = df.warp(img_grayscale_float) mask = np.zeros(size, dtype=bool) mask[size[0] // 3:2 * (size[1] // 3), size[0] // 3:2 * (size[1] // 3)] = True img_pred_2 = img_pred_1.copy() img_pred_2[~mask] = np.random.rand() eps = 1e-2 # just because of ANTsPy assert (abs(ALL_IMG_METRICS[metric](img_true, img_pred_1, mask=mask) - ALL_IMG_METRICS[metric](img_true, img_pred_2, mask=mask)) < eps)
def test_same_results( self, tmp_path, monkeypatch, img_grayscale, registration_type ): """Make sure that the registration is reproducible. Done by checking if the displacement field extracted are equal if we run the registration with the same parameters. Notes ----- Marked as `todo` because we did not find a way how to force ANTsPY to be always reproducible. """ monkeypatch.setenv("ANTS_RANDOM_SEED", "1") monkeypatch.setenv("ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS", "1") size = img_grayscale.shape p = img_grayscale / np.sum(img_grayscale) df = DisplacementField.generate(size, approach="paper", p=p, random_state=1) moving_img = df.warp(img_grayscale) df_final1, meta1 = antspy_registration( img_grayscale, moving_img, registration_type=registration_type, path=tmp_path, verbose=True, ) df_final2, meta2 = antspy_registration( img_grayscale, moving_img, registration_type=registration_type, path=tmp_path, verbose=True, ) assert np.allclose(df_final1.delta_x, df_final2.delta_x, atol=1) assert np.allclose(df_final1.delta_y, df_final2.delta_y, atol=1)