Skip to content

Commit

Permalink
modified tests to include all affine transform types
Browse files Browse the repository at this point in the history
  • Loading branch information
Thanushi Peiris committed Dec 12, 2023
1 parent 984c6df commit cd85f71
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 177 deletions.
92 changes: 18 additions & 74 deletions src/affinder/_tests/test_affinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,12 @@ def generate_all_layer_types(image, pts, labels):
)
nuc3D = generate_all_layer_types(nuclei3D, nuclei3D_pts, nuclei3D_labels)

################
################
################


# 2D as reference, 2D as moving
@pytest.mark.parametrize(
"reference,moving", [p for p in product(nuc2D, nuc2D_t)]
"reference,moving,model_class", [p for p in product(nuc2D, nuc2D_t, [t for t in AffineTransformChoices])]
)
def test_2D_2D(make_napari_viewer, tmp_path, reference, moving):
def test_2D_2D(make_napari_viewer, tmp_path, reference, moving, model_class):

viewer = make_napari_viewer()

Expand All @@ -107,17 +103,18 @@ def test_2D_2D(make_napari_viewer, tmp_path, reference, moving):
viewer=viewer,
reference=l0,
moving=l1,
model=AffineTransformChoices.affine,
model=model_class,
output=tmp_path / 'my_affine.txt'
)

viewer.layers['layer0_pts'].data = nuclei2D_2Dpts
viewer.layers['layer1_pts'].data = nuclei2D_transformed_2Dpts

actual_affine = np.asarray(viewer.layers['layer1'].affine)
expected_affine = np.array([[0.54048889, 0.8468468, -30.9685414],
[-0.78297398, 0.52668962, 177.6241674],
[0., 0., 1.]])
actual_affine = np.asarray(l1.affine)

model = model_class.value(dimensionality=2)
model.estimate(viewer.layers['layer1_pts'].data, viewer.layers['layer0_pts'].data)
expected_affine = model.params

np.testing.assert_allclose(
actual_affine, expected_affine, rtol=10, atol=1e-10
Expand All @@ -126,9 +123,9 @@ def test_2D_2D(make_napari_viewer, tmp_path, reference, moving):

# 3D as reference, 2D as moving
@pytest.mark.parametrize(
"reference,moving", [p for p in product(nuc3D, nuc2D)]
"reference,moving,model_class", [p for p in product(nuc3D, nuc2D_t, [t for t in AffineTransformChoices])]
)
def test_3D_2D(make_napari_viewer, tmp_path, reference, moving):
def test_3D_2D(make_napari_viewer, tmp_path, reference, moving, model_class):

viewer = make_napari_viewer()

Expand All @@ -138,7 +135,7 @@ def test_3D_2D(make_napari_viewer, tmp_path, reference, moving):

# affinder currently changes the moving layer data when dims are different
# so need to copy
l1 = viewer.add_layer(copy(moving))
l1 = viewer.add_layer(moving)
viewer.layers[-1].name = "layer1"
viewer.layers[-1].colormap = "magenta"

Expand All @@ -147,71 +144,18 @@ def test_3D_2D(make_napari_viewer, tmp_path, reference, moving):
viewer=viewer,
reference=l0,
moving=l1,
model=AffineTransformChoices.Euclidean,
model=model_class,
output=tmp_path / 'my_affine.txt'
)

viewer.layers['layer0_pts'].data = nuclei3D_2Dpts
viewer.layers['layer1_pts'].data = nuclei2D_2Dpts

actual_affine = np.asarray(viewer.layers['layer1'].affine)
# start_affinder currently makes a clone of moving layer when it's of
# type Points of Vectors and not same dimensions as reference layer - so l1
# is a redundant layer that is no longer used as the real moving layer -
# this is why we use viewer.layers['layer1] instead of l1

expected_affine = np.array(
[[0.00000000e+00, 0.00000000e+00, 3.00000000e+01],
[1.00000000e+00, 2.89023467e-17, 0.00000000e+00],
[-7.90288925e-18, 1.00000000e+00, 1.42108547e-14]]
)

np.testing.assert_allclose(
actual_affine, expected_affine, rtol=10, atol=1e-10
)


# 2D as reference, 3D as moving
@pytest.mark.parametrize(
"reference,moving", [p for p in product(nuc2D, nuc3D)]
)
def test_2D_3D(make_napari_viewer, tmp_path, reference, moving):

viewer = make_napari_viewer()

l0 = viewer.add_layer(reference)
viewer.layers[-1].name = "layer0"
viewer.layers[-1].colormap = "green"

# affinder currently changes the moving layer data when dims are different
# so need to copy
l1 = viewer.add_layer(copy(moving))
viewer.layers[-1].name = "layer1"
viewer.layers[-1].colormap = "magenta"
viewer.layers['layer1_pts'].data = nuclei2D_transformed_2Dpts

my_widget_factory = start_affinder()
my_widget_factory(
viewer=viewer,
reference=l0,
moving=l1,
model=AffineTransformChoices.Euclidean,
output=tmp_path / 'my_affine.txt'
)
actual_affine = np.asarray(l1.affine)

viewer.layers['layer0_pts'].data = nuclei2D_2Dpts
viewer.layers['layer1_pts'].data = nuclei3D_2Dpts

actual_affine = np.asarray(viewer.layers['layer1'].affine)
# start_affinder currently makes a clone of moving layer when it's of
# type Points of Vectors and not same dimensions as reference layer - so l1
# is a redundant layer that is no longer used as the real moving layer -
# this is why we use viewer.layers['layer1] instead of l1
expected_affine = np.array(
[[1.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
[0.00000000e+00, 1.000000e+00, 2.890235e-17, 0.000000e+00],
[0.00000000e+00, -7.902889e-18, 1.000000e+00, 1.421085e-14],
[0.00000000e+00, 0.000000e+00, 0.000000e+00, 1.000000e+00]]
)
model = model_class.value(dimensionality=2)
model.estimate(viewer.layers['layer1_pts'].data, viewer.layers['layer0_pts'].data)
expected_affine = model.params

np.testing.assert_allclose(
actual_affine, expected_affine, rtol=10, atol=1e-10
Expand Down Expand Up @@ -294,4 +238,4 @@ def test_load_affine(tmp_path):
widget = load_affine()
widget(layer, affile)

np.testing.assert_allclose(layer.affine, affine)
np.testing.assert_allclose(layer.affine, affine)
105 changes: 2 additions & 103 deletions src/affinder/affinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,9 @@ def next_layer_callback(
# must shrink ndims of affine matrix if dims of image layer is bigger than moving layer #####
if reference_image_layer.ndim > moving_image_layer.ndim:
ref_mat = convert_affine_matrix_to_ndims(ref_mat, moving_image_layer.ndim)
moving_points_layer.affine = (ref_mat @ mat.params)
# must pad affine matrix with identity matrix if dims of moving layer smaller #####
moving_image_layer.affine = convert_affine_matrix_to_ndims(
moving_points_layer.affine.affine_matrix, ndims(moving_image_layer))
(ref_mat @ mat.params), moving_image_layer.ndim)
if output is not None:
np.savetxt(output, np.asarray(mat.params), delimiter=',')
viewer.layers.selection.active = reference_points_layer
Expand All @@ -94,32 +93,6 @@ def close_affinder(layers, callback):
layer.mode = 'pan_zoom'



def ndims(layer):
if isinstance(layer, Image) or isinstance(layer, Labels):
return layer.data.ndim
elif isinstance(layer, Shapes):
# list of s shapes, containing n * D of n points with D dimensions
return layer.data[0].shape[1]
elif isinstance(layer, Points):
# (n, D) array of n points with D dimensions
return layer.data.shape[-1]
elif isinstance(layer, Vectors):
# (n, 2, D) of n vectors with start pt and projections in D dimensions
return layer.data.shape[-1]
else:
raise Warning(
layer, "layer type is not currently supported - cannot "
"find its ndims."
)

def add_zeros_at_start_of_last_axis(arr):
upsize_last_axis = lambda size: size[:-1] + (size[-1] + 1,)
new_arr = np.zeros(upsize_last_axis(arr.shape))
new_arr[..., 1:] = arr
return new_arr


def convert_affine_to_ndims(affine, target_ndims):
if affine.ndim == target_ndims:
return affine
Expand All @@ -146,57 +119,6 @@ def convert_affine_matrix_to_ndims(matrix, target_ndims):
else:
return matrix

# this will take a long time for vectors and points if lots of dimensions need
# to be padded
def expand_dims(layer, target_ndims, viewer, extract_index=0):
"""
will add empty dimensions to layer until its dimensions are target_ndims
"""
while ndims(layer) < target_ndims:
if isinstance(layer, Image) or isinstance(layer, Labels):
# add dimension to beginning of dimension list
layer.data = np.expand_dims(layer.data, axis=0)
elif isinstance(layer, Shapes):
# list of s shapes, containing n * D of n points with D dimensions
layer.data = [
add_zeros_at_start_of_last_axis(l) for l in layer.data
]
elif isinstance(layer, Points):
# (n, D) array of n points with D dimensions
#layer.data = add_zeros_at_start_of_last_axis(layer.data)
new_arr = add_zeros_at_start_of_last_axis(layer.data)
new_layer = napari.layers.Points(
new_arr, name=layer.name, properties=layer.properties
)
viewer.layers.remove(layer.name)
viewer.add_layer(new_layer)
layer = new_layer

elif isinstance(layer, Vectors):
# (n, 2, D) of n vectors with start pt and projections in D dimensions
n, b, D = layer.data.shape
new_arr = np.zeros((n, b, D + 1))
new_arr[:, 0, :] = add_zeros_at_start_of_last_axis(
layer.data[:, 0, :]
)
new_arr[:, 1, :] = add_zeros_at_start_of_last_axis(
layer.data[:, 1, :]
)
#layer.data = new_arr
new_layer = napari.layers.Vectors(
new_arr, name=layer.name, properties=layer.properties
)
viewer.layers.remove(layer.name)
viewer.add_layer(new_layer)
layer = new_layer

else:
raise Warning(
layer, "layer type is not currently supported - cannot "
"expand its dimensions."
)
return layer


def _update_unique_choices(widget, choice_name):
"""Update the selected choice in a ComboBox widget to be unique.
Expand Down Expand Up @@ -246,35 +168,12 @@ def start_affinder(
moving: 'napari.layers.Layer',
moving_points: Optional['napari.layers.Points'] = None,
model: AffineTransformChoices,
output: Optional[pathlib.Path] = None,
keep_original_moving_layer=False,
output: Optional[pathlib.Path] = None
):
mode = start_affinder._call_button.text # can be "Start" or "Finish"

if mode == 'Start':

#if model == AffineTransformChoices.affine:
# if (ndims(moving) != 2) or (ndims(reference) != 2):
# raise ValueError(
# "Choose different model: Affine transform "
# "cannot be used if layers are not both 2D. "
# "Please choose a different model "
# "type (not \"affine\")"
# )

if ndims(moving) != ndims(reference):
# make copy of moving layer if selected
if keep_original_moving_layer:
print("keep og moving layer selected")
og_layer = deepcopy(moving)
og_layer.name = og_layer.name + " original"
viewer.add_layer(og_layer)

# pad dimensions of moving image if it's less than reference
#moving = expand_or_extract_ndims(moving, ndims(reference), viewer) # do not destructively change layers
#if ndims(moving) < ndims(reference):
# moving = expand_dims(moving, target_ndims=ndims(reference), viewer=viewer)

# focus on the reference layer
reset_view(viewer, reference)
# set points layer for each image
Expand Down

0 comments on commit cd85f71

Please sign in to comment.