def test_composition_intermediate(self, interpolation, border_mode): """Test that composing two opposite translations results in an identity (almost)""" shape = (400, 200) matrix_1 = np.array([[1, 0, 1], [0, 1, 1], [0, 0, 1]]) matrix_2 = np.array([[1, 0, -1], [0, 1, -1], [0, 0, 1]]) matrix_id = np.eye(3) df_1 = DisplacementField.generate(shape, approach="affine", matrix=matrix_1) df_2 = DisplacementField.generate(shape, approach="affine", matrix=matrix_2) df_id = DisplacementField.generate(shape, approach="affine", matrix=matrix_id) res = df_2(df_1, interpolation=interpolation, border_mode=border_mode) # Works only inside:D assert np.allclose(res.delta_x[2:-2, 2:-2], df_id.delta_x[2:-2, 2:-2]) assert np.allclose(res.delta_y[2:-2, 2:-2], df_id.delta_y[2:-2, 2:-2])
def test_no_inputs_anchor_corners(self): """Test that can/cannot instantiate with no inputs and with/without anchoring corners.""" shape = (40, 50) with pytest.raises(ValueError): DisplacementField.generate(shape, approach="control_points", anchor_corners=False) DisplacementField.generate(shape, approach="control_points", anchor_corners=True)
def test_illegal_kwarg(self): """Test that illegal kwarg not accepted""" with pytest.raises(ValueError): shape = (20, 13) DisplacementField.generate( shape, approach="edge_stretching", edge_mask=np.zeros(shape, dtype=bool), n_perturbation_points=10, i_am_illegal=1, )
def test_displacement_field(self, tmp_path, img_grayscale): """Make sure that the displacement extracted can reproduce the registered moving image. Done by comparing with the image contained in the registration output. """ fixed_img = img_grayscale size = fixed_img.shape df = DisplacementField.generate( size, approach="affine_simple", translation_x=20, translation_y=20 ) moving_img = df.warp(img_grayscale) df_final, meta = antspy_registration(fixed_img, moving_img, path=tmp_path) img1 = meta["warpedmovout"].numpy() img2 = df_final.warp( moving_img, interpolation="linear", border_mode="constant", c=0 ) if img_grayscale.dtype == "uint8": epsilon = 1 else: epsilon = 0.005 assert abs(img1 - img2).mean() < epsilon
def test_different_types( self, tmp_path, monkeypatch, img_grayscale_uint, img_grayscale_float ): """Make sure that the registration does not depend on the type of input images. Notes ----- Marked as `todo` because we did not find a way how to force ANTsPY to be always reproducible. """ monkeypatch.setenv("ANTS_RANDOM_SEED", "4") monkeypatch.setenv("ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS", "1") size = img_grayscale_uint.shape p = img_grayscale_uint / np.sum(img_grayscale_uint) df = DisplacementField.generate(size, approach="paper", p=p) moving_img_uint = df.warp(img_grayscale_uint) moving_img_float = df.warp(img_grayscale_float) df_final1, meta1 = antspy_registration( img_grayscale_uint, moving_img_uint, path=tmp_path, verbose=False ) df_final2, meta2 = antspy_registration( img_grayscale_float, moving_img_float, path=tmp_path, verbose=False ) assert np.allclose(df_final1.delta_x, df_final2.delta_x, atol=0.1) assert np.allclose(df_final1.delta_y, df_final2.delta_y, atol=0.1)
def generate_mov2art(img_mov, verbose=True, radius_max=60, use_normal=True): """Generate geometric augmentation and its inverse.""" shape = img_mov.shape img_mov_float = img_as_float32(img_mov) edge_mask = canny(img_mov_float) if use_normal: c = np.random.normal(0.7, 0.3) else: c = np.random.random() if verbose: print("Scalar: {}".format(c)) mov2art = c * DisplacementField.generate( shape, approach="edge_stretching", edge_mask=edge_mask, interpolation_method="rbf", interpolator_kwargs={"function": "linear"}, n_perturbation_points=6, radius_max=radius_max, ) return mov2art
def test_warp_invariance(self, img, h_delta, w_delta): """Test that df_resized.warp(img) ~ resized(df.warp(img)).""" shape = img.shape new_shape = (shape[0] + h_delta, shape[1] + w_delta) assert (new_shape[0] > 0 and new_shape[1] > 0 ) # if not satisfied fixture image too small # Zoom in df = DisplacementField.generate(shape, approach="affine_simple", scale_x=0.8) df_resized = df.resize(new_shape) img_1 = df_resized.warp(img) img_2 = resize(df.warp(img), new_shape, preserve_range=True) # preserving dtype # WARNING MAKE SURE THAT DOES NOT RUN INTO OVERFLOW FOR UINT8 adiff = abs(np.subtract(img_1, img_2, dtype="float")) if img.dtype == np.uint8: eps = 25 elif img.dtype == np.float32: eps = 1e-1 else: raise ValueError( "This test is only made for uint8 and float32 images.") assert np.median(adiff) < eps
def test_small_shift(self, translation_xy, approach, dtype): """Test that small shifts do not affect the warp. Notes ----- Anything that is withing a diameter of 1 (=radius of 0.5) should be assigned to the same point. """ translation_x, translation_y = translation_xy shape = (9, 10) random_state = 1 df = DisplacementField.generate( shape, approach="affine_simple", translation_x=translation_x, translation_y=translation_y, ) np.random.seed(random_state) img = np.random.randint(256, size=shape).astype(dtype) img_warped = df.warp_annotation(img, approach=approach) assert np.all(img_warped == img)
def test_wrong_inputs_1(self): """Test that inconsistent input lengths lead to an error.""" shape = (40, 50) points = np.array([[0, 1], [10, 10]]) # len(points) = 2 values_delta_x = np.array([0, 10, 0]) # len(values_delta_x) = 3 values_delta_y = np.array([0, 10]) # len(values_delta_y) = 2 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_construction(approach): """Just check default factory methods are able to construct the class with no additional parameters.""" shape = (500, 500) inst = DisplacementField.generate(shape, approach=approach) assert isinstance(inst, DisplacementField)
def test_basic(self, batch_size, a): """Basic functionality""" (h, w) = 356 // 2, 420 // 2 a_tensor = Input((2, 3)) layer = Affine2DVF((h, w)) x = layer(a_tensor) assert isinstance(x, tf.Tensor) assert x.shape.ndims == 4 assert K.int_shape(x) == (None, h, w, 2) model = Model(inputs=a_tensor, outputs=x) # Initialize a_input = np.zeros((batch_size, 2, 3)) dvfs_true = np.zeros((batch_size, h, w, 2)) for i in range(batch_size): # add random translation for each sample a_input[i] = a + np.hstack((np.zeros( (2, 2)), np.random.uniform(-10, 10, size=(2, 1)))) df = DisplacementField.generate((h, w), approach="affine", matrix=a_input[i]) dvfs_true[i, :, :, 0] = df.delta_x dvfs_true[i, :, :, 1] = df.delta_y # run dvfs_pred = model.predict(a_input) assert dvfs_pred.shape == (batch_size, h, w, 2) assert np.allclose(dvfs_true, dvfs_pred, atol=0.01)
def test_float_values(self, img_grayscale_float, metric): """Test that every image similarity metrics output a float.""" 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) metric_callable = ALL_IMG_METRICS[metric] # UNMASKED assert isinstance(metric_callable(img_true, img_pred), float) # MASKED mask = np.zeros(size, dtype=bool) mask[size[0] // 3:2 * (size[1] // 3), size[0] // 3:2 * (size[1] // 3)] = True try: assert isinstance(metric_callable(img_true, img_pred, mask=mask), float) except TypeError: # mask keyword argument not present pass
def test_reproducible(random_state, approach): """Test that the same random seeds lead to identical result whereas different ones do not.""" shape = (100, 120) df_1 = DisplacementField.generate(shape, approach=approach, random_state=random_state) df_2 = DisplacementField.generate(shape, approach=approach, random_state=random_state) df_3 = DisplacementField.generate(shape, approach=approach, random_state=random_state + 1) assert df_1 == df_2 assert df_1 != df_3
def test_wrong_inputs_0(self): """ Test that other types then np.ndarray lead to an error""" shape = (40, 50) points = "fake" values_delta_x = (1, 2, 3) values_delta_y = [1, 1312, "aaa"] with pytest.raises(TypeError): DisplacementField.generate( shape, approach="control_points", points=points, values_delta_x=values_delta_x, values_delta_y=values_delta_y, )
def test_wrong_inputs_3(self): """Test that other dimensions are also correct.""" shape = (40, 50) points = np.zeros([1, 2, 3, 4, 5]) # Wrong - needs to be a 2d array values_delta_x = np.array([1, 2, 3, 4, 5]) values_delta_y = np.array([1, 2, 3, 4, 5]) 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_identity(self): shape = (10, 11) df = DisplacementField.generate(shape, approach="identity") df_anchored = df.anchor(ds_f=1) assert df == df_anchored
def test_wrong_inputs_4(self): """Test that out of range control points lead to an error.""" shape = (40, 50) points = np.array([[100, 10], [12, 12]]) # Wrong - needs to be a 2d array values_delta_x = np.array([1, 2]) values_delta_y = np.array([0, 13]) with pytest.raises(IndexError): DisplacementField.generate( shape, approach="control_points", points=points, values_delta_x=values_delta_x, values_delta_y=values_delta_y, )
def minimal_vol(monkeypatch): sn = [1, 13] mov_imgs = 2 * [np.zeros((12, 13))] dvfs = 2 * [DisplacementField.generate((12, 13), approach="identity")] nvol_mock = MagicMock() nvol_mock.__getitem__.return_value = np.zeros((len(mov_imgs), 12, 13, 1)) monkeypatch.setattr("atlalign.volume.nissl_volume", lambda: nvol_mock) return Volume(sn, mov_imgs, dvfs)
def test_wrong_inputs_5(self, metric_name): """Wrong input type""" y_true = np.zeros((2, 20, 12, 2)) y_pred = [ DisplacementField.generate((20, 12), approach="identity"), "faaake" ] with pytest.raises(TypeError): ALL_DVF_METRICS[metric_name](y_true, y_pred)
def test_dtype_conversions(self, img, interpolation, border_mode): """Test that dtype is conserved (at least for uint8 and float32)""" df = DisplacementField.generate(img.shape, approach="identity") warped_img = df.warp(img, interpolation=interpolation, border_mode=border_mode) assert warped_img.dtype == img.dtype
def test_animation(self, img): """Possible to generate animations.""" df = DisplacementField.generate(img.shape, approach="identity") ani = create_animation(df, img) ani_many = create_animation([df, df], img) assert isinstance(ani, animation.Animation) assert isinstance(ani_many, animation.Animation)
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_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 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_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_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_scale(self, scale): """Make sure that one can scale.""" shape = (40, 50) matrix = scale * np.array([[1, 0, 0], [0, 1, 0]]) x, y = np.meshgrid(list(range(shape[1])), list(range(shape[0]))) delta_x = scale * x - x delta_y = scale * y - y df = DisplacementField.generate(shape=shape, approach="affine", matrix=matrix) df_true = DisplacementField(delta_x, delta_y) assert df == df_true
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])