Skip to content
Snippets Groups Projects
Commit da4c68a0 authored by Alexander Clausen's avatar Alexander Clausen
Browse files

Work on tests and fixes for Field


 * fixed rotate with reshape=True
 * 2d rotation: fix sign of x component
 * clip: negative values are no longer cut off for scalars
 * re-organized tests and fixtures
 * improve coverage: clip, __getitem__, rotate: scalars, comparisons, ...

Co-authored-by: default avatar <d.weber@fz-juelich.de>
Co-authored-by: default avatar <j.caron@fz-juelich.de>
parent 7a63cc5d
No related branches found
No related tags found
No related merge requests found
Pipeline #29612 failed
......@@ -12,3 +12,4 @@ docs/_build/*
dist/*
src/empyre/version.py
htmlcov/
coverage.xml
......@@ -92,6 +92,7 @@ class Field(NDArrayOperatorsMixin):
def scale(self):
return self.__scale
@scale.setter
def scale(self, scale):
if isinstance(scale, Number): # Scale is the same for each dimension!
......@@ -437,7 +438,7 @@ class Field(NDArrayOperatorsMixin):
# Create Quaternion, note that they have (x, y, z) order instead of (z, y, x):
quat_axis = tuple([levi_civita if i == rot_axis else 0 for i in (2, 1, 0)])
quat = Quaternion.from_axisangle(quat_axis, np.deg2rad(angle))
data_new = quat.matrix.dot(data_new.reshape((-1, 3)).T).T.reshape(self.shape) # T needed b.c. ordering!
data_new = quat.matrix.dot(data_new.reshape((-1, 3)).T).T.reshape(data_new.shape) # T needed b.c. ordering!
elif self.ncomp == 2:
u_comp, v_comp = comps
u_rot = interpolation.rotate(u_comp, np_angle, axes=axes, **kwargs)
......@@ -504,7 +505,7 @@ class Field(NDArrayOperatorsMixin):
if len(self.dim) == 2: # 2D:
assert self.ncomp == 2, 'rot90 currently only works for vector fields with 2 components in 2D!'
comp_x, comp_y = self.comp
comp_x_rot = np.rot90(comp_y, k=k, axes=axes)
comp_x_rot = -np.rot90(comp_y, k=k, axes=axes)
comp_y_rot = np.rot90(comp_x, k=k, axes=axes)
data_new = np.stack((comp_x_rot, comp_y_rot), axis=-1)
# Return result:
......@@ -797,21 +798,25 @@ class Field(NDArrayOperatorsMixin):
"""
self._log.debug('Calling clip')
amp = self.amp.data
# Get a scalar indicator array for where clipping needs to happen:
if self.vector: # For vector fields, it is the amplitude of the data:
indicator = self.amp.data
else: # For scalar fields this is the data itself:
indicator = self.data
if mask is None: # If no mask is set yet, default to True everywhere:
mask = np.full(self.dim, True)
if sigma is not None: # Mark outliers that are outside `sigma` standard deviations:
sigma_mask = (amp - amp.mean()) < (sigma * amp.std())
sigma_mask = (indicator - indicator.mean()) < (sigma * indicator.std())
mask = np.logical_and(mask, sigma_mask)
# Determine vmin and vmax if they are not set by the user:
amp_masked = np.where(mask, amp, np.nan)
indicator_masked = np.where(mask, indicator, np.nan)
if vmin is None:
vmin = np.nanmin(amp_masked)
vmin = np.nanmin(indicator_masked)
if vmax is None:
vmax = np.nanmax(amp_masked)
if self.vector: # Vector fields need to scale components according to masked amplitude
mask_vec = (amp <= vmax)[..., None] # Only vmax is important for vectors! mask_vec broadcast to components!
data = np.where(mask_vec, self.data, vmax * self.data/amp[..., None]) # Scale outliers to vmax!
vmax = np.nanmax(indicator_masked)
if self.vector: # Vector fields need to scale components according to masked amplitude:
mask_vec = (indicator <= vmax)[..., None] # Only vmax is important for vectors! mask_vec broadcast to components!
data_new = np.where(mask_vec, self.data, vmax * self.data/indicator[..., None]) # Scale outliers to vmax!
else: # For scalar fields, just delegate to the numpy function:
data = np.clip(self.data, vmin, vmax)
return Field(data, self.scale, self.vector)
data_new = np.clip(self.data, vmin, vmax)
return Field(data_new, self.scale, self.vector)
import os
import pytest
import numpy as np
from empyre.fields import Field
@pytest.fixture
def fielddata_path():
return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_fielddata')
@pytest.fixture
def vector_data():
magnitude = np.zeros((4, 4, 4, 3))
magnitude[1:-1, 1:-1, 1:-1] = 1
return Field(magnitude, 10.0, vector=True)
@pytest.fixture
def vector_data_asymm():
shape = (5, 7, 11, 3)
data = np.linspace(0, 1, np.prod(shape))
return Field(data.reshape(shape), 10.0, vector=True)
@pytest.fixture
def vector_data_asymm_2d():
shape = (5, 7, 2)
data = np.linspace(0, 1, np.prod(shape))
return Field(data.reshape(shape), 10.0, vector=True)
@pytest.fixture
def vector_data_asymmcube():
shape = (3, 3, 3, 3)
data = np.linspace(0, 1, np.prod(shape))
return Field(data.reshape(shape), 10.0, vector=True)
@pytest.fixture
def scalar_data():
magnitude = np.zeros((4, 4, 4))
magnitude[1:-1, 1:-1, 1:-1] = 1
return Field(magnitude, 10.0, vector=False)
@pytest.fixture
def scalar_data_asymm():
shape = (5, 7, 2)
data = np.linspace(0, 1, np.prod(shape))
return Field(data.reshape(shape), 10.0, vector=False)
# -*- coding: utf-8 -*-
"""Testcase for the magdata module."""
import os
import unittest
import pytest
from numbers import Number
......@@ -10,54 +8,9 @@ from numbers import Number
import numpy as np
import numpy.testing
# from pyramid.fielddata import VectorData
# from pyramid import load_vectordata
from empyre.fields import Field
def assert_allclose(actual, desired, rtol=1e-07, atol=1e-08, equal_nan=True, err_msg='', verbose=True):
return numpy.testing.assert_allclose(
actual=actual,
desired=desired,
rtol=rtol,
atol=atol,
equal_nan=equal_nan,
err_msg=err_msg,
verbose=verbose,
)
@pytest.fixture
def fielddata_path():
return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_fielddata')
@pytest.fixture
def vector_data():
magnitude = np.zeros((4, 4, 4, 3))
magnitude[1:-1, 1:-1, 1:-1] = 1
return Field(magnitude, 10.0, vector=True)
@pytest.fixture
def vector_data_asymm():
shape = (5, 7, 11, 3)
data = np.linspace(0, 1, np.prod(shape))
return Field(data.reshape(shape), 10.0, vector=True)
@pytest.fixture
def vector_data_asymmcube():
shape = (3, 3, 3, 3)
data = np.linspace(0, 1, np.prod(shape))
return Field(data.reshape(shape), 10.0, vector=True)
@pytest.fixture
def scalar_data():
magnitude = np.zeros((4, 4, 4))
magnitude[1:-1, 1:-1, 1:-1] = 1
return Field(magnitude, 10.0, vector=False)
from utils import assert_allclose
def test_copy(vector_data):
......@@ -252,39 +205,148 @@ def test_flip(vector_data_asymm):
err_msg='Unexpected behavior in flip()! (components)')
@pytest.mark.parametrize(
'axis', ['x', 'y', 'z']
)
def test_rotate_360(vector_data_asymm, axis):
assert_allclose(vector_data_asymm.rot90(axis=axis).rot90(axis=axis).rot90(axis=axis).rot90(axis=axis), vector_data_asymm,
err_msg=f'Unexpected behavior in rot()! {axis}')
def test_unknown_num_of_components():
shape = (5, 7, 7)
data = np.linspace(0, 1, np.prod(shape))
with pytest.raises(AssertionError):
Field(data.reshape(shape), 10.0, vector=True)
@pytest.mark.parametrize(
'rot_axis,flip_axes', [
('x', (0, 1)),
('y', (0, 2)),
('z', (1, 2))
]
)
def test_rotate_180(vector_data_asymm, rot_axis, flip_axes):
res = vector_data_asymm.rot90(axis=rot_axis).rot90(axis=rot_axis)
ref = vector_data_asymm.flip(axis=flip_axes)
assert_allclose(res, ref, err_msg=f'Unexpected behavior in rot90()! {rot_axis}')
def test_repr(vector_data_asymm):
string_repr = repr(vector_data_asymm)
data_str = str(vector_data_asymm.data)
string_ref = f'Field(data={data_str}, scale=(10.0, 10.0, 10.0), vector=True)'
print(f'reference: {string_ref}')
print(f'repr output: {string_repr}')
assert string_repr == string_ref, 'Unexpected behavior in __repr__()!'
def test_str(vector_data_asymm):
string_str = str(vector_data_asymm)
string_ref = 'Field(dim=(5, 7, 11), scale=(10.0, 10.0, 10.0), vector=True, ncomp=3)'
print(f'reference: {string_str}')
print(f'str output: {string_str}')
assert string_str == string_ref, 'Unexpected behavior in __str__()!'
@pytest.mark.parametrize(
'rot_axis', [
'x',
'y',
'z',
"index,t,scale", [
((0, 1, 2), tuple, None),
((0, ), Field, (2., 3.)),
(0, Field, (2., 3.)),
((0, 1, 2, 0), float, None),
((0, 1, 2, 0), float, None),
((..., 0), Field, (1., 2., 3.)),
((0, slice(1, 3), 2), Field, (2.,)),
]
)
def test_rotate_compare_rot90_1(vector_data_asymmcube, rot_axis):
res = vector_data_asymmcube.rotate(angle=90, axis=rot_axis)
ref = vector_data_asymmcube.rot90(axis=rot_axis)
print("input", vector_data_asymmcube.data)
print("ref", res.data)
print("res", ref.data)
assert_allclose(res, ref, err_msg=f'Unexpected behavior in rotate()! {rot_axis}')
def test_getitem(vector_data, index, t, scale):
vector_data.scale = (1., 2., 3.)
data_index = index
res = vector_data[index]
assert_allclose(res, vector_data.data[data_index])
assert isinstance(res, t)
if t is Field:
assert res.scale == scale
def test_from_scalar_field(scalar_data):
sca_x, sca_y, sca_z = [i * scalar_data for i in range(1, 4)]
field_comb = Field.from_scalar_fields([sca_x, sca_y, sca_z])
assert field_comb.vector
assert field_comb.scale == scalar_data.scale
assert_allclose(sca_x, field_comb.comp[0])
assert_allclose(sca_y, field_comb.comp[1])
assert_allclose(sca_z, field_comb.comp[2])
def test_squeeze():
magnitude = np.zeros((4, 1, 4, 3))
field = Field(magnitude, (1., 2., 3.), vector=True)
sq = field.squeeze()
assert sq.shape == (4, 4, 3)
assert sq.dim == (4, 4)
assert sq.scale == (1., 3.)
def test_gradient():
pass
def test_gradient_1d():
pass
def test_curl():
pass
def test_curl_2d():
pass
def test_clip_scalar_noop():
shape = (3, 3, 3)
data = np.linspace(-2, 1, np.prod(shape)).reshape(shape)
field = Field(data, (1., 2., 3.), vector=False)
assert_allclose(field, field.clip())
def test_clip_scalar_minmax():
shape = (3, 3, 3)
data = np.linspace(-2, 1, np.prod(shape)).reshape(shape)
field = Field(data, (1., 2., 3.), vector=False)
assert_allclose(np.clip(data, -1, 0.1), field.clip(vmin=-1, vmax=0.1))
def test_clip_scalar_sigma():
shape = (3, 3, 3)
data = np.linspace(-2, 1, np.prod(shape)).reshape(shape)
data[0, 0, 0] = 1e6
field = Field(data, (1., 2., 3.), vector=False)
# We clip off the one outlier
assert_allclose(np.clip(data, -2, 1), field.clip(sigma=5))
assert field.clip(sigma=5)[0, 0, 0] == 1
def test_clip_scalar_mask():
shape = (3, 3, 3)
data = np.linspace(-2, 1, np.prod(shape)).reshape(shape)
mask = np.zeros(shape, dtype=bool)
mask[0, 0, 0] = True
mask[0, 0, 1] = True
field = Field(data, (1., 2., 3.), vector=False)
assert_allclose(np.clip(data, data[0, 0, 0], data[0, 0, 1]), field.clip(mask=mask))
def test_clip_vector_noop():
shape = (3, 3, 3, 3)
data = np.linspace(-2, 1, np.prod(shape)).reshape(shape)
field = Field(data, (1., 2., 3.), vector=True)
assert_allclose(field, field.clip())
def test_clip_vector_max():
shape = (3, 3, 3, 3)
data = np.linspace(-2, 1, np.prod(shape)).reshape(shape)
field = Field(data, (1., 2., 3.), vector=True)
res = field.clip(vmax=0.1)
assert_allclose(np.max(res.amp), 0.1)
def test_clip_vector_sigma():
shape = (3, 3, 3, 3)
data = np.linspace(-2, 1, np.prod(shape)).reshape(shape)
data[0, 0, 0] = (1e6, 1e6, 1e6)
field = Field(data, (1., 2., 3.), vector=True)
# We clip off the one outlier
res = field.clip(sigma=5)
assert np.max(res.amp) < 1e3
# TODO: HyperSpy would need to be installed for the following tests (slow...):
# def test_from_signal()
# raise NotImplementedError()
#
# def test_to_signal()
# raise NotImplementedError()
import pytest
from utils import assert_allclose
from empyre.fields import Field
import numpy as np
@pytest.mark.parametrize(
'axis', ['x', 'y', 'z']
)
def test_rot90_360(vector_data_asymm, axis):
assert_allclose(vector_data_asymm.rot90(axis=axis).rot90(axis=axis).rot90(axis=axis).rot90(axis=axis),
vector_data_asymm,
err_msg=f'Unexpected behavior in rot90()! {axis}')
@pytest.mark.parametrize(
'rot_axis,flip_axes', [
('x', (0, 1)),
('y', (0, 2)),
('z', (1, 2))
]
)
def test_rot90_180(vector_data_asymm, rot_axis, flip_axes):
res = vector_data_asymm.rot90(axis=rot_axis).rot90(axis=rot_axis)
ref = vector_data_asymm.flip(axis=flip_axes)
assert_allclose(res, ref, err_msg=f'Unexpected behavior in rot90()! {rot_axis}')
@pytest.mark.parametrize(
'rot_axis', [
'x',
'y',
'z',
]
)
def test_rotate_compare_rot90_1(vector_data_asymmcube, rot_axis):
res = vector_data_asymmcube.rotate(angle=90, axis=rot_axis)
ref = vector_data_asymmcube.rot90(axis=rot_axis)
print("input", vector_data_asymmcube.data)
print("ref", res.data)
print("res", ref.data)
assert_allclose(res, ref, err_msg=f'Unexpected behavior in rotate()! {rot_axis}')
def test_rot90_manual():
data = np.zeros((3, 3, 3, 3))
diag = np.array((1, 1, 1))
diag_unity = diag / np.sqrt(np.sum(diag**2))
data[0, 0, 0] = diag_unity
data = Field(data, 10, vector=True)
print("data", data.data)
rot90_x = np.zeros((3, 3, 3, 3))
# Axis order z, y, x; vector components x, y, z
rot90_x[0, 2, 0] = diag_unity * (1, -1, 1)
rot90_x = Field(rot90_x, 10, vector=True)
print("rot90_x", rot90_x.data)
print("data rot90 x", data.rot90(axis='x').data)
rot90_y = np.zeros((3, 3, 3, 3))
# Axis order z, y, x; vector components x, y, z
rot90_y[2, 0, 0] = diag_unity * (1, 1, -1)
rot90_y = Field(rot90_y, 10, vector=True)
print("rot90_y", rot90_y.data)
print("data rot90 y", data.rot90(axis='y').data)
rot90_z = np.zeros((3, 3, 3, 3))
# Axis order z, y, x; vector components x, y, z
rot90_z[0, 0, 2] = diag_unity * (-1, 1, 1)
rot90_z = Field(rot90_z, 10, vector=True)
print("rot90_z", rot90_z.data)
print("data rot90 z", data.rot90(axis='z').data)
assert_allclose(rot90_x, data.rot90(axis='x'), err_msg='Unexpected behavior in rot90("x")!')
assert_allclose(rot90_y, data.rot90(axis='y'), err_msg='Unexpected behavior in rot90("y")!')
assert_allclose(rot90_z, data.rot90(axis='z'), err_msg='Unexpected behavior in rot90("z")!')
def test_rot45_manual():
data = np.zeros((3, 3, 3, 3))
data[0, 0, 0] = (1, 1, 1)
data = Field(data, 10, vector=True)
print("data", data.data)
rot45_x = np.zeros((3, 3, 3, 3))
# Axis order z, y, x; vector components x, y, z
rot45_x[0, 1, 0] = (1, 0, np.sqrt(2))
rot45_x = Field(rot45_x, 10, vector=True)
print("rot45_x", rot45_x.data)
# Disable spline interpolation, use nearest instead
res_rot45_x = data.rotate(45, axis='x', order=0)
print("data rot45 x", res_rot45_x.data)
rot45_y = np.zeros((3, 3, 3, 3))
# Axis order z, y, x; vector components x, y, z
rot45_y[1, 0, 0] = (np.sqrt(2), 1, 0)
rot45_y = Field(rot45_y, 10, vector=True)
print("rot45_y", rot45_y.data)
# Disable spline interpolation, use nearest instead
res_rot45_y = data.rotate(45, axis='y', order=0)
print("data rot45 y", res_rot45_y.data)
rot45_z = np.zeros((3, 3, 3, 3))
# Axis order z, y, x; vector components x, y, z
rot45_z[0, 0, 1] = (0, np.sqrt(2), 1)
rot45_z = Field(rot45_z, 10, vector=True)
print("rot45_z", rot45_z.data)
# Disable spline interpolation, use nearest instead
res_rot45_z = data.rotate(45, axis='z', order=0)
print("data rot45 z", res_rot45_z.data)
assert_allclose(rot45_x, res_rot45_x, err_msg='Unexpected behavior in rotate(45, "x")!')
assert_allclose(rot45_y, res_rot45_y, err_msg='Unexpected behavior in rotate(45, "y")!')
assert_allclose(rot45_z, res_rot45_z, err_msg='Unexpected behavior in rotate(45, "z")!')
def test_rot90_2d_360(vector_data_asymm_2d):
assert_allclose(vector_data_asymm_2d.rot90().rot90().rot90().rot90(), vector_data_asymm_2d,
err_msg=f'Unexpected behavior in 2D rot90()!')
def test_rot90_2d_180(vector_data_asymm_2d):
res = vector_data_asymm_2d.rot90().rot90()
ref = vector_data_asymm_2d.flip()
assert_allclose(res, ref, err_msg=f'Unexpected behavior in 2D rot90()!')
@pytest.mark.parametrize(
'k', [0, 1, 2, 3, 4]
)
def test_rot90_comp_2d_with_3d(vector_data_asymm_2d, k):
data_x, data_y = [comp.data[np.newaxis, :, :] for comp in vector_data_asymm_2d.comp]
data_z = np.zeros_like(data_x)
data_3d = np.stack([data_x, data_y, data_z], axis=-1)
vector_data_asymm_3d = Field(data_3d, scale=10, vector=True)
print(f'2D shape, scale: {vector_data_asymm_2d.shape, vector_data_asymm_2d.scale}')
print(f'3D shape, scale: {vector_data_asymm_3d.shape, vector_data_asymm_3d.scale}')
vector_data_rot_2d = vector_data_asymm_2d.rot90(k=k)
vector_data_rot_3d = vector_data_asymm_3d.rot90(k=k, axis='z')
print(f'2D shape after rot: {vector_data_rot_2d.shape}')
print(f'3D shape after rot: {vector_data_rot_3d.shape}')
assert_allclose(vector_data_rot_2d, vector_data_rot_3d[0, :, :, :2], err_msg=f'Unexpected behavior in 2D rot90()!')
@pytest.mark.parametrize(
'angle', [90, 45, 23, 11.5]
)
def test_rotate_comp_2d_with_3d(vector_data_asymm_2d, angle):
data_x, data_y = [comp.data[np.newaxis, :, :] for comp in vector_data_asymm_2d.comp]
data_z = np.zeros_like(data_x)
data_3d = np.stack([data_x, data_y, data_z], axis=-1)
vector_data_asymm_3d = Field(data_3d, scale=10, vector=True)
print(f'2D shape, scale: {vector_data_asymm_2d.shape, vector_data_asymm_2d.scale}')
print(f'3D shape, scale: {vector_data_asymm_3d.shape, vector_data_asymm_3d.scale}')
r2d = vector_data_asymm_2d.rotate(angle)
r3d = vector_data_asymm_3d.rotate(angle, axis='z')
print(f'2D shape after rot: {r2d.shape}')
print(f'3D shape after rot: {r3d.shape}')
assert_allclose(r2d, r3d[0, :, :, :2], err_msg=f'Unexpected behavior in 2D rotate()!')
@pytest.mark.parametrize(
'angle', [180, 360, 90, 45, 23, 11.5],
)
@pytest.mark.parametrize(
'axis', ['x', 'y', 'z'],
)
def test_rotate_scalar(vector_data_asymm, angle, axis):
data = np.zeros((1, 2, 2, 3))
data[0, 0, 0] = 1
field = Field(data, scale=10., vector=True)
print(field)
print(field.amp)
assert_allclose(
field.rotate(angle, axis=axis).amp,
field.amp.rotate(angle, axis=axis)
)
@pytest.mark.parametrize(
'angle,order', [(180, 3), (360, 3), (90, 3), (45, 0), (23, 0), (11.5, 0)],
)
@pytest.mark.parametrize(
'axis', ['x', 'y', 'z'],
)
@pytest.mark.parametrize(
'reshape', [True, False],
)
def test_rotate_scalar_asymm(vector_data_asymm, angle, axis, order, reshape):
assert_allclose(
vector_data_asymm.rotate(angle, axis=axis, reshape=reshape, order=order).amp,
vector_data_asymm.amp.rotate(angle, axis=axis, reshape=reshape, order=order)
)
@pytest.mark.parametrize(
'axis', ['x', 'y', 'z'],
)
@pytest.mark.parametrize(
'k', [0, 1, 2, 3, 4],
)
def test_rot90_scalar(vector_data_asymm, axis, k):
assert_allclose(
vector_data_asymm.amp.rot90(k=k, axis=axis),
vector_data_asymm.rot90(k=k, axis=axis).amp
)
# -*- coding: utf-8 -*-
"""Testcase for the magdata module."""
# import os
import unittest
# import numpy as np
# from numpy.testing import assert_allclose
# from empyre.fields.field import Field
class TestCaseField(unittest.TestCase):
def test_stupid(self):
print('FOUND')
assert True
def test_simple():
print('METHOD')
assert True
import numpy
import numpy.testing
def assert_allclose(actual, desired, rtol=1e-07, atol=1e-08, equal_nan=True, err_msg='', verbose=True):
return numpy.testing.assert_allclose(
actual=actual,
desired=desired,
rtol=rtol,
atol=atol,
equal_nan=equal_nan,
err_msg=err_msg,
verbose=verbose,
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment