rm CondaPkg environment
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,98 +0,0 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
from skimage.registration import optical_flow_ilk
|
||||
from .test_tvl1 import _sin_flow_gen
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
@pytest.mark.parametrize('gaussian', [True, False])
|
||||
@pytest.mark.parametrize('prefilter', [True, False])
|
||||
def test_2d_motion(dtype, gaussian, prefilter):
|
||||
# Generate synthetic data
|
||||
rnd = np.random.default_rng(0)
|
||||
image0 = rnd.normal(size=(256, 256))
|
||||
gt_flow, image1 = _sin_flow_gen(image0)
|
||||
image1 = image1.astype(dtype, copy=False)
|
||||
float_dtype = _supported_float_type(dtype)
|
||||
# Estimate the flow
|
||||
flow = optical_flow_ilk(image0, image1, gaussian=gaussian,
|
||||
prefilter=prefilter, dtype=float_dtype)
|
||||
assert flow.dtype == _supported_float_type(dtype)
|
||||
# Assert that the average absolute error is less then half a pixel
|
||||
assert abs(flow - gt_flow).mean() < 0.5
|
||||
|
||||
if dtype != float_dtype:
|
||||
with pytest.raises(ValueError):
|
||||
optical_flow_ilk(image0, image1, gaussian=gaussian,
|
||||
prefilter=prefilter, dtype=dtype)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('gaussian', [True, False])
|
||||
@pytest.mark.parametrize('prefilter', [True, False])
|
||||
def test_3d_motion(gaussian, prefilter):
|
||||
# Generate synthetic data
|
||||
rnd = np.random.default_rng(123)
|
||||
image0 = rnd.normal(size=(50, 55, 60))
|
||||
gt_flow, image1 = _sin_flow_gen(image0, npics=3)
|
||||
# Estimate the flow
|
||||
flow = optical_flow_ilk(image0, image1, radius=5,
|
||||
gaussian=gaussian, prefilter=prefilter)
|
||||
|
||||
# Assert that the average absolute error is less then half a pixel
|
||||
assert abs(flow - gt_flow).mean() < 0.5
|
||||
|
||||
|
||||
def test_no_motion_2d():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = rnd.normal(size=(256, 256))
|
||||
|
||||
flow = optical_flow_ilk(img, img)
|
||||
|
||||
assert np.all(flow == 0)
|
||||
|
||||
|
||||
def test_no_motion_3d():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = rnd.normal(size=(64, 64, 64))
|
||||
|
||||
flow = optical_flow_ilk(img, img)
|
||||
|
||||
assert np.all(flow == 0)
|
||||
|
||||
|
||||
def test_optical_flow_dtype():
|
||||
# Generate synthetic data
|
||||
rnd = np.random.default_rng(0)
|
||||
image0 = rnd.normal(size=(256, 256))
|
||||
gt_flow, image1 = _sin_flow_gen(image0)
|
||||
# Estimate the flow at double precision
|
||||
flow_f64 = optical_flow_ilk(image0, image1, dtype='float64')
|
||||
|
||||
assert flow_f64.dtype == 'float64'
|
||||
|
||||
# Estimate the flow at single precision
|
||||
flow_f32 = optical_flow_ilk(image0, image1, dtype='float32')
|
||||
|
||||
assert flow_f32.dtype == 'float32'
|
||||
|
||||
# Assert that floating point precision does not affect the quality
|
||||
# of the estimated flow
|
||||
|
||||
assert abs(flow_f64 - flow_f32).mean() < 1e-3
|
||||
|
||||
|
||||
def test_incompatible_shapes():
|
||||
rnd = np.random.default_rng(0)
|
||||
I0 = rnd.normal(size=(256, 256))
|
||||
I1 = rnd.normal(size=(255, 256))
|
||||
with pytest.raises(ValueError):
|
||||
u, v = optical_flow_ilk(I0, I1)
|
||||
|
||||
|
||||
def test_wrong_dtype():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = rnd.normal(size=(256, 256))
|
||||
with pytest.raises(ValueError):
|
||||
u, v = optical_flow_ilk(img, img, dtype='int')
|
||||
@@ -1,279 +0,0 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import (assert_almost_equal, assert_array_almost_equal,
|
||||
assert_array_equal, assert_array_less, assert_equal)
|
||||
from scipy.ndimage import fourier_shift, shift as real_shift
|
||||
import scipy.fft as fft
|
||||
|
||||
from skimage._shared.testing import fetch
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
from skimage.data import camera, brain
|
||||
|
||||
|
||||
from skimage.io import imread
|
||||
from skimage.registration._masked_phase_cross_correlation import (
|
||||
_masked_phase_cross_correlation as masked_register_translation,
|
||||
cross_correlate_masked)
|
||||
from skimage.registration import phase_cross_correlation
|
||||
|
||||
|
||||
def test_masked_registration_vs_phase_cross_correlation():
|
||||
"""masked_register_translation should give the same results as
|
||||
phase_cross_correlation in the case of trivial masks."""
|
||||
reference_image = camera()
|
||||
shift = (-7, 12)
|
||||
shifted = np.real(fft.ifft2(fourier_shift(
|
||||
fft.fft2(reference_image), shift)))
|
||||
trivial_mask = np.ones_like(reference_image)
|
||||
|
||||
nonmasked_result, *_ = phase_cross_correlation(reference_image, shifted)
|
||||
masked_result = masked_register_translation(reference_image,
|
||||
shifted,
|
||||
reference_mask=trivial_mask,
|
||||
overlap_ratio=1 / 10)
|
||||
|
||||
assert_equal(nonmasked_result, masked_result)
|
||||
|
||||
|
||||
def test_masked_registration_random_masks():
|
||||
"""masked_register_translation should be able to register translations
|
||||
between images even with random masks."""
|
||||
# See random number generator for reproducible results
|
||||
np.random.seed(23)
|
||||
|
||||
reference_image = camera()
|
||||
shift = (-7, 12)
|
||||
shifted = np.real(fft.ifft2(fourier_shift(
|
||||
fft.fft2(reference_image), shift)))
|
||||
|
||||
# Random masks with 75% of pixels being valid
|
||||
ref_mask = np.random.choice(
|
||||
[True, False], reference_image.shape, p=[3 / 4, 1 / 4])
|
||||
shifted_mask = np.random.choice(
|
||||
[True, False], shifted.shape, p=[3 / 4, 1 / 4])
|
||||
|
||||
measured_shift = masked_register_translation(reference_image,
|
||||
shifted,
|
||||
reference_mask=ref_mask,
|
||||
moving_mask=shifted_mask)
|
||||
assert_equal(measured_shift, -np.array(shift))
|
||||
|
||||
|
||||
def test_masked_registration_3d_contiguous_mask():
|
||||
"""masked_register_translation should be able to register translations
|
||||
between volumes with contiguous masks."""
|
||||
ref_vol = brain()[:, ::2, ::2]
|
||||
|
||||
offset = (1, -5, 10)
|
||||
|
||||
# create square mask
|
||||
ref_mask = np.zeros_like(ref_vol, dtype=bool)
|
||||
ref_mask[:-2, 75:100, 75:100] = True
|
||||
ref_shifted = real_shift(ref_vol, offset)
|
||||
|
||||
measured_offset = masked_register_translation(
|
||||
ref_vol, ref_shifted, reference_mask=ref_mask, moving_mask=ref_mask
|
||||
)
|
||||
|
||||
assert_equal(offset, -np.array(measured_offset))
|
||||
|
||||
|
||||
def test_masked_registration_random_masks_non_equal_sizes():
|
||||
"""masked_register_translation should be able to register
|
||||
translations between images that are not the same size even
|
||||
with random masks."""
|
||||
# See random number generator for reproducible results
|
||||
np.random.seed(23)
|
||||
|
||||
reference_image = camera()
|
||||
shift = (-7, 12)
|
||||
shifted = np.real(fft.ifft2(fourier_shift(
|
||||
fft.fft2(reference_image), shift)))
|
||||
|
||||
# Crop the shifted image
|
||||
shifted = shifted[64:-64, 64:-64]
|
||||
|
||||
# Random masks with 75% of pixels being valid
|
||||
ref_mask = np.random.choice(
|
||||
[True, False], reference_image.shape, p=[3 / 4, 1 / 4])
|
||||
shifted_mask = np.random.choice(
|
||||
[True, False], shifted.shape, p=[3 / 4, 1 / 4])
|
||||
|
||||
measured_shift = masked_register_translation(
|
||||
reference_image,
|
||||
shifted,
|
||||
reference_mask=np.ones_like(ref_mask),
|
||||
moving_mask=np.ones_like(shifted_mask))
|
||||
assert_equal(measured_shift, -np.array(shift))
|
||||
|
||||
|
||||
def test_masked_registration_padfield_data():
|
||||
""" Masked translation registration should behave like in the original
|
||||
publication """
|
||||
# Test translated from MATLABimplementation `MaskedFFTRegistrationTest`
|
||||
# file. You can find the source code here:
|
||||
# http://www.dirkpadfield.com/Home/MaskedFFTRegistrationCode.zip
|
||||
|
||||
shifts = [(75, 75), (-130, 130), (130, 130)]
|
||||
for xi, yi in shifts:
|
||||
|
||||
fixed_image = imread(
|
||||
fetch(f'registration/tests/data/OriginalX{xi}Y{yi}.png'))
|
||||
moving_image = imread(
|
||||
fetch(f'registration/tests/data/TransformedX{xi}Y{yi}.png'))
|
||||
|
||||
# Valid pixels are 1
|
||||
fixed_mask = (fixed_image != 0)
|
||||
moving_mask = (moving_image != 0)
|
||||
|
||||
# Note that shifts in x and y and shifts in cols and rows
|
||||
shift_y, shift_x = masked_register_translation(
|
||||
fixed_image, moving_image, reference_mask=fixed_mask,
|
||||
moving_mask=moving_mask, overlap_ratio=0.1)
|
||||
# Note: by looking at the test code from Padfield's
|
||||
# MaskedFFTRegistrationCode repository, the
|
||||
# shifts were not xi and yi, but xi and -yi
|
||||
assert_equal((shift_x, shift_y), (-xi, yi))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_cross_correlate_masked_output_shape(dtype):
|
||||
"""Masked normalized cross-correlation should return a shape
|
||||
of N + M + 1 for each transform axis."""
|
||||
shape1 = (15, 4, 5)
|
||||
shape2 = (6, 12, 7)
|
||||
expected_full_shape = tuple(np.array(shape1) + np.array(shape2) - 1)
|
||||
expected_same_shape = shape1
|
||||
|
||||
arr1 = np.zeros(shape1, dtype=dtype)
|
||||
arr2 = np.zeros(shape2, dtype=dtype)
|
||||
# Trivial masks
|
||||
m1 = np.ones_like(arr1)
|
||||
m2 = np.ones_like(arr2)
|
||||
|
||||
float_dtype = _supported_float_type(dtype)
|
||||
|
||||
full_xcorr = cross_correlate_masked(
|
||||
arr1, arr2, m1, m2, axes=(0, 1, 2), mode='full')
|
||||
assert_equal(full_xcorr.shape, expected_full_shape)
|
||||
assert full_xcorr.dtype == float_dtype
|
||||
|
||||
same_xcorr = cross_correlate_masked(
|
||||
arr1, arr2, m1, m2, axes=(0, 1, 2), mode='same')
|
||||
assert_equal(same_xcorr.shape, expected_same_shape)
|
||||
assert same_xcorr.dtype == float_dtype
|
||||
|
||||
|
||||
|
||||
def test_cross_correlate_masked_test_against_mismatched_dimensions():
|
||||
"""Masked normalized cross-correlation should raise an error if array
|
||||
dimensions along non-transformation axes are mismatched."""
|
||||
shape1 = (23, 1, 1)
|
||||
shape2 = (6, 2, 2)
|
||||
|
||||
arr1 = np.zeros(shape1)
|
||||
arr2 = np.zeros(shape2)
|
||||
|
||||
# Trivial masks
|
||||
m1 = np.ones_like(arr1)
|
||||
m2 = np.ones_like(arr2)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
cross_correlate_masked(arr1, arr2, m1, m2, axes=(1, 2))
|
||||
|
||||
|
||||
def test_cross_correlate_masked_output_range():
|
||||
"""Masked normalized cross-correlation should return between 1 and -1."""
|
||||
# See random number generator for reproducible results
|
||||
np.random.seed(23)
|
||||
|
||||
# Array dimensions must match along non-transformation axes, in
|
||||
# this case
|
||||
# axis 0
|
||||
shape1 = (15, 4, 5)
|
||||
shape2 = (15, 12, 7)
|
||||
|
||||
# Initial array ranges between -5 and 5
|
||||
arr1 = 10 * np.random.random(shape1) - 5
|
||||
arr2 = 10 * np.random.random(shape2) - 5
|
||||
|
||||
# random masks
|
||||
m1 = np.random.choice([True, False], arr1.shape)
|
||||
m2 = np.random.choice([True, False], arr2.shape)
|
||||
|
||||
xcorr = cross_correlate_masked(arr1, arr2, m1, m2, axes=(1, 2))
|
||||
|
||||
# No assert array less or equal, so we add an eps
|
||||
# Also could not find an `assert_array_greater`, Use (-xcorr) instead
|
||||
eps = np.finfo(float).eps
|
||||
assert_array_less(xcorr, 1 + eps)
|
||||
assert_array_less(-xcorr, 1 + eps)
|
||||
|
||||
|
||||
def test_cross_correlate_masked_side_effects():
|
||||
"""Masked normalized cross-correlation should not modify the inputs."""
|
||||
shape1 = (2, 2, 2)
|
||||
shape2 = (2, 2, 2)
|
||||
|
||||
arr1 = np.zeros(shape1)
|
||||
arr2 = np.zeros(shape2)
|
||||
|
||||
# Trivial masks
|
||||
m1 = np.ones_like(arr1)
|
||||
m2 = np.ones_like(arr2)
|
||||
|
||||
for arr in (arr1, arr2, m1, m2):
|
||||
arr.setflags(write=False)
|
||||
|
||||
cross_correlate_masked(arr1, arr2, m1, m2)
|
||||
|
||||
|
||||
def test_cross_correlate_masked_over_axes():
|
||||
"""Masked normalized cross-correlation over axes should be
|
||||
equivalent to a loop over non-transform axes."""
|
||||
# See random number generator for reproducible results
|
||||
np.random.seed(23)
|
||||
|
||||
arr1 = np.random.random((8, 8, 5))
|
||||
arr2 = np.random.random((8, 8, 5))
|
||||
|
||||
m1 = np.random.choice([True, False], arr1.shape)
|
||||
m2 = np.random.choice([True, False], arr2.shape)
|
||||
|
||||
# Loop over last axis
|
||||
with_loop = np.empty_like(arr1, dtype=complex)
|
||||
for index in range(arr1.shape[-1]):
|
||||
with_loop[:, :, index] = cross_correlate_masked(arr1[:, :, index],
|
||||
arr2[:, :, index],
|
||||
m1[:, :, index],
|
||||
m2[:, :, index],
|
||||
axes=(0, 1),
|
||||
mode='same')
|
||||
|
||||
over_axes = cross_correlate_masked(
|
||||
arr1, arr2, m1, m2, axes=(0, 1), mode='same')
|
||||
|
||||
assert_array_almost_equal(with_loop, over_axes)
|
||||
|
||||
|
||||
def test_cross_correlate_masked_autocorrelation_trivial_masks():
|
||||
"""Masked normalized cross-correlation between identical arrays
|
||||
should reduce to an autocorrelation even with random masks."""
|
||||
# See random number generator for reproducible results
|
||||
np.random.seed(23)
|
||||
|
||||
arr1 = camera()
|
||||
|
||||
# Random masks with 75% of pixels being valid
|
||||
m1 = np.random.choice([True, False], arr1.shape, p=[3 / 4, 1 / 4])
|
||||
m2 = np.random.choice([True, False], arr1.shape, p=[3 / 4, 1 / 4])
|
||||
|
||||
xcorr = cross_correlate_masked(arr1, arr1, m1, m2, axes=(0, 1),
|
||||
mode='same', overlap_ratio=0).real
|
||||
max_index = np.unravel_index(np.argmax(xcorr), xcorr.shape)
|
||||
|
||||
# Autocorrelation should have maximum in center of array
|
||||
# uint8 inputs will be processed in float32, so reduce decimal to 5
|
||||
assert_almost_equal(xcorr.max(), 1, decimal=5)
|
||||
assert_array_equal(max_index, np.array(arr1.shape) / 2)
|
||||
@@ -1,268 +0,0 @@
|
||||
import itertools
|
||||
import warnings
|
||||
import re
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_allclose
|
||||
from scipy.ndimage import fourier_shift
|
||||
import scipy.fft as fft
|
||||
|
||||
from skimage import img_as_float
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
from skimage.data import camera, binary_blobs, eagle
|
||||
from skimage.registration._phase_cross_correlation import (
|
||||
phase_cross_correlation, _upsampled_dft
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('normalization', [None, 'phase'])
|
||||
def test_correlation(normalization):
|
||||
reference_image = fft.fftn(camera())
|
||||
shift = (-7, 12)
|
||||
shifted_image = fourier_shift(reference_image, shift)
|
||||
|
||||
# pixel precision
|
||||
result, _, _ = phase_cross_correlation(reference_image,
|
||||
shifted_image,
|
||||
space="fourier",
|
||||
normalization=normalization)
|
||||
assert_allclose(result[:2], -np.array(shift))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('normalization', ['nonexisting'])
|
||||
def test_correlation_invalid_normalization(normalization):
|
||||
reference_image = fft.fftn(camera())
|
||||
shift = (-7, 12)
|
||||
shifted_image = fourier_shift(reference_image, shift)
|
||||
|
||||
# pixel precision
|
||||
with pytest.raises(ValueError):
|
||||
phase_cross_correlation(reference_image,
|
||||
shifted_image,
|
||||
space="fourier",
|
||||
normalization=normalization)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('normalization', [None, 'phase'])
|
||||
def test_subpixel_precision(normalization):
|
||||
reference_image = fft.fftn(camera())
|
||||
subpixel_shift = (-2.4, 1.32)
|
||||
shifted_image = fourier_shift(reference_image, subpixel_shift)
|
||||
|
||||
# subpixel precision
|
||||
result, _, _ = phase_cross_correlation(reference_image,
|
||||
shifted_image,
|
||||
upsample_factor=100,
|
||||
space="fourier",
|
||||
normalization=normalization)
|
||||
assert_allclose(result[:2], -np.array(subpixel_shift), atol=0.05)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_real_input(dtype):
|
||||
reference_image = camera().astype(dtype, copy=False)
|
||||
subpixel_shift = (-2.4, 1.32)
|
||||
shifted_image = fourier_shift(fft.fftn(reference_image), subpixel_shift)
|
||||
shifted_image = fft.ifftn(shifted_image).real.astype(dtype, copy=False)
|
||||
|
||||
# subpixel precision
|
||||
result, error, diffphase = phase_cross_correlation(reference_image,
|
||||
shifted_image,
|
||||
upsample_factor=100)
|
||||
assert result.dtype == _supported_float_type(dtype)
|
||||
assert_allclose(result[:2], -np.array(subpixel_shift), atol=0.05)
|
||||
|
||||
|
||||
def test_size_one_dimension_input():
|
||||
# take a strip of the input image
|
||||
reference_image = fft.fftn(camera()[:, 15]).reshape((-1, 1))
|
||||
subpixel_shift = (-2.4, 4)
|
||||
shifted_image = fourier_shift(reference_image, subpixel_shift)
|
||||
|
||||
# subpixel precision
|
||||
result, error, diffphase = phase_cross_correlation(reference_image,
|
||||
shifted_image,
|
||||
upsample_factor=20,
|
||||
space="fourier")
|
||||
assert_allclose(result[:2], -np.array((-2.4, 0)), atol=0.05)
|
||||
|
||||
|
||||
def test_3d_input():
|
||||
phantom = img_as_float(binary_blobs(length=32, n_dim=3))
|
||||
reference_image = fft.fftn(phantom)
|
||||
shift = (-2., 1., 5.)
|
||||
shifted_image = fourier_shift(reference_image, shift)
|
||||
|
||||
result, error, diffphase = phase_cross_correlation(reference_image,
|
||||
shifted_image,
|
||||
space="fourier")
|
||||
assert_allclose(result, -np.array(shift), atol=0.05)
|
||||
|
||||
# subpixel precision now available for 3-D data
|
||||
|
||||
subpixel_shift = (-2.3, 1.7, 5.4)
|
||||
shifted_image = fourier_shift(reference_image, subpixel_shift)
|
||||
result, error, diffphase = phase_cross_correlation(reference_image,
|
||||
shifted_image,
|
||||
upsample_factor=100,
|
||||
space="fourier")
|
||||
assert_allclose(result, -np.array(subpixel_shift), atol=0.05)
|
||||
|
||||
|
||||
def test_unknown_space_input():
|
||||
image = np.ones((5, 5))
|
||||
with pytest.raises(ValueError):
|
||||
phase_cross_correlation(
|
||||
image, image,
|
||||
space="frank")
|
||||
|
||||
|
||||
def test_wrong_input():
|
||||
# Dimensionality mismatch
|
||||
image = np.ones((5, 5, 1))
|
||||
template = np.ones((5, 5))
|
||||
with pytest.raises(ValueError):
|
||||
phase_cross_correlation(template, image)
|
||||
|
||||
# Size mismatch
|
||||
image = np.ones((5, 5))
|
||||
template = np.ones((4, 4))
|
||||
with pytest.raises(ValueError):
|
||||
phase_cross_correlation(template, image)
|
||||
|
||||
# NaN values in data
|
||||
image = np.ones((5, 5))
|
||||
image[0][0] = np.nan
|
||||
template = np.ones((5, 5))
|
||||
with expected_warnings(
|
||||
[
|
||||
r"invalid value encountered in true_divide"
|
||||
+ r"|"
|
||||
+ r"invalid value encountered in divide"
|
||||
+ r"|\A\Z"
|
||||
]
|
||||
):
|
||||
with pytest.raises(ValueError):
|
||||
phase_cross_correlation(template, image, return_error=True)
|
||||
|
||||
|
||||
def test_4d_input_pixel():
|
||||
phantom = img_as_float(binary_blobs(length=32, n_dim=4))
|
||||
reference_image = fft.fftn(phantom)
|
||||
shift = (-2., 1., 5., -3)
|
||||
shifted_image = fourier_shift(reference_image, shift)
|
||||
result, error, diffphase = phase_cross_correlation(reference_image,
|
||||
shifted_image,
|
||||
space="fourier")
|
||||
assert_allclose(result, -np.array(shift), atol=0.05)
|
||||
|
||||
|
||||
def test_4d_input_subpixel():
|
||||
phantom = img_as_float(binary_blobs(length=32, n_dim=4))
|
||||
reference_image = fft.fftn(phantom)
|
||||
subpixel_shift = (-2.3, 1.7, 5.4, -3.2)
|
||||
shifted_image = fourier_shift(reference_image, subpixel_shift)
|
||||
result, error, diffphase = phase_cross_correlation(reference_image,
|
||||
shifted_image,
|
||||
upsample_factor=10,
|
||||
space="fourier")
|
||||
assert_allclose(result, -np.array(subpixel_shift), atol=0.05)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("return_error", [True, False, "always"])
|
||||
@pytest.mark.parametrize("reference_mask", [None, True])
|
||||
def test_phase_cross_correlation_deprecation(return_error, reference_mask):
|
||||
# For now, assert that phase_cross_correlation raises a warning that
|
||||
# returning only shifts is deprecated. In skimage 0.21, this test should be
|
||||
# updated for the deprecation of the return_error parameter.
|
||||
should_warn = (
|
||||
return_error is False
|
||||
or (return_error != "always" and reference_mask is True)
|
||||
)
|
||||
|
||||
reference_image = np.ones((10, 10))
|
||||
moving_image = np.ones_like(reference_image)
|
||||
if reference_mask is True:
|
||||
# moving_mask defaults to reference_mask, passing moving_mask only is
|
||||
# not supported, so we don't need to test it
|
||||
reference_mask = np.ones_like(reference_image)
|
||||
|
||||
if should_warn:
|
||||
msg = (
|
||||
"In scikit-image 0.21, phase_cross_correlation will start "
|
||||
"returning a tuple or 3 items (shift, error, phasediff) always. "
|
||||
"To enable the new return behavior and silence this warning, use "
|
||||
"return_error='always'."
|
||||
)
|
||||
with pytest.warns(FutureWarning, match=re.escape(msg)):
|
||||
out = phase_cross_correlation(
|
||||
reference_image=reference_image,
|
||||
moving_image=moving_image,
|
||||
return_error=return_error,
|
||||
reference_mask=reference_mask,
|
||||
)
|
||||
assert not isinstance(out, tuple)
|
||||
else:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("error")
|
||||
out = phase_cross_correlation(
|
||||
reference_image=reference_image,
|
||||
moving_image=moving_image,
|
||||
return_error=return_error,
|
||||
reference_mask=reference_mask,
|
||||
)
|
||||
assert isinstance(out, tuple)
|
||||
assert len(out) == 3
|
||||
|
||||
|
||||
def test_mismatch_upsampled_region_size():
|
||||
with pytest.raises(ValueError):
|
||||
_upsampled_dft(
|
||||
np.ones((4, 4)),
|
||||
upsampled_region_size=[3, 2, 1, 4])
|
||||
|
||||
|
||||
def test_mismatch_offsets_size():
|
||||
with pytest.raises(ValueError):
|
||||
_upsampled_dft(np.ones((4, 4)), 3,
|
||||
axis_offsets=[3, 2, 1, 4])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('shift0', 'shift1'),
|
||||
itertools.product((100, -100, 350, -350), (100, -100, 350, -350)),
|
||||
)
|
||||
def test_disambiguate_2d(shift0, shift1):
|
||||
image = eagle()[500:, 900:] # use a highly textured part of image
|
||||
shift = (shift0, shift1)
|
||||
origin0 = []
|
||||
for s in shift:
|
||||
if s > 0:
|
||||
origin0.append(0)
|
||||
else:
|
||||
origin0.append(-s)
|
||||
origin1 = np.array(origin0) + shift
|
||||
slice0 = tuple(slice(o, o+450) for o in origin0)
|
||||
slice1 = tuple(slice(o, o+450) for o in origin1)
|
||||
reference = image[slice0]
|
||||
moving = image[slice1]
|
||||
computed_shift, _, _ = phase_cross_correlation(
|
||||
reference, moving, disambiguate=True, return_error='always'
|
||||
)
|
||||
np.testing.assert_equal(shift, computed_shift)
|
||||
|
||||
|
||||
def test_disambiguate_zero_shift():
|
||||
"""When the shift is 0, disambiguation becomes degenerate.
|
||||
|
||||
Some quadrants become size 0, which prevents computation of
|
||||
cross-correlation. This test ensures that nothing bad happens in that
|
||||
scenario.
|
||||
"""
|
||||
image = camera()
|
||||
computed_shift, _, _ = phase_cross_correlation(
|
||||
image, image, disambiguate=True, return_error='always'
|
||||
)
|
||||
assert computed_shift == (0, 0)
|
||||
@@ -1,117 +0,0 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
from skimage.registration import optical_flow_tvl1
|
||||
from skimage.transform import warp
|
||||
|
||||
|
||||
def _sin_flow_gen(image0, max_motion=4.5, npics=5):
|
||||
"""Generate a synthetic ground truth optical flow with a sinusoid as
|
||||
first component.
|
||||
|
||||
Parameters:
|
||||
----
|
||||
image0: ndarray
|
||||
The base image to be warped.
|
||||
max_motion: float
|
||||
Maximum flow magnitude.
|
||||
npics: int
|
||||
Number of sinusoid pics.
|
||||
|
||||
Returns
|
||||
-------
|
||||
flow, image1 : ndarray
|
||||
The synthetic ground truth optical flow with a sinusoid as
|
||||
first component and the corresponding warped image.
|
||||
|
||||
"""
|
||||
grid = np.meshgrid(*[np.arange(n) for n in image0.shape], indexing='ij')
|
||||
grid = np.stack(grid)
|
||||
gt_flow = np.zeros_like(grid, dtype=float)
|
||||
gt_flow[0, ...] = max_motion * np.sin(grid[0]/grid[0].max()*npics*np.pi)
|
||||
image1 = warp(image0, grid - gt_flow, mode='edge')
|
||||
return gt_flow, image1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_2d_motion(dtype):
|
||||
# Generate synthetic data
|
||||
rnd = np.random.default_rng(0)
|
||||
image0 = rnd.normal(size=(256, 256))
|
||||
gt_flow, image1 = _sin_flow_gen(image0)
|
||||
image1 = image1.astype(dtype, copy=False)
|
||||
float_dtype = _supported_float_type(dtype)
|
||||
# Estimate the flow
|
||||
flow = optical_flow_tvl1(image0, image1, attachment=5, dtype=float_dtype)
|
||||
assert flow.dtype == float_dtype
|
||||
# Assert that the average absolute error is less then half a pixel
|
||||
assert abs(flow - gt_flow) .mean() < 0.5
|
||||
|
||||
if dtype != float_dtype:
|
||||
with pytest.raises(ValueError):
|
||||
optical_flow_tvl1(image0, image1, attachment=5, dtype=dtype)
|
||||
|
||||
def test_3d_motion():
|
||||
# Generate synthetic data
|
||||
rnd = np.random.default_rng(0)
|
||||
image0 = rnd.normal(size=(100, 100, 100))
|
||||
gt_flow, image1 = _sin_flow_gen(image0)
|
||||
# Estimate the flow
|
||||
flow = optical_flow_tvl1(image0, image1, attachment=10)
|
||||
# Assert that the average absolute error is less then half a pixel
|
||||
assert abs(flow - gt_flow) .mean() < 0.5
|
||||
|
||||
|
||||
def test_no_motion_2d():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = rnd.normal(size=(256, 256))
|
||||
|
||||
flow = optical_flow_tvl1(img, img)
|
||||
|
||||
assert np.all(flow == 0)
|
||||
|
||||
|
||||
def test_no_motion_3d():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = rnd.normal(size=(64, 64, 64))
|
||||
|
||||
flow = optical_flow_tvl1(img, img)
|
||||
|
||||
assert np.all(flow == 0)
|
||||
|
||||
|
||||
def test_optical_flow_dtype():
|
||||
# Generate synthetic data
|
||||
rnd = np.random.default_rng(0)
|
||||
image0 = rnd.normal(size=(256, 256))
|
||||
gt_flow, image1 = _sin_flow_gen(image0)
|
||||
# Estimate the flow at double precision
|
||||
flow_f64 = optical_flow_tvl1(image0, image1, attachment=5, dtype=np.float64)
|
||||
|
||||
assert flow_f64.dtype == np.float64
|
||||
|
||||
# Estimate the flow at single precision
|
||||
flow_f32 = optical_flow_tvl1(image0, image1, attachment=5, dtype=np.float32)
|
||||
|
||||
assert flow_f32.dtype == np.float32
|
||||
|
||||
# Assert that floating point precision does not affect the quality
|
||||
# of the estimated flow
|
||||
|
||||
assert np.abs(flow_f64 - flow_f32).mean() < 1e-3
|
||||
|
||||
|
||||
def test_incompatible_shapes():
|
||||
rnd = np.random.default_rng(0)
|
||||
I0 = rnd.normal(size=(256, 256))
|
||||
I1 = rnd.normal(size=(128, 256))
|
||||
with pytest.raises(ValueError):
|
||||
u, v = optical_flow_tvl1(I0, I1)
|
||||
|
||||
|
||||
def test_wrong_dtype():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = rnd.normal(size=(256, 256))
|
||||
with pytest.raises(ValueError):
|
||||
u, v = optical_flow_tvl1(img, img, dtype=np.int64)
|
||||
Reference in New Issue
Block a user