Skip to content

Commit

Permalink
Merge #210 from harripj/transpose_feature
Browse files Browse the repository at this point in the history
Add transpose method to all 3D classes
  • Loading branch information
pc494 authored Jun 29, 2021
2 parents c74faf1 + abfcfe2 commit 05311f6
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Added
Rodrigues fundamental zone, without having to set it up via Matplotlib.
- Method Object3d.get_random_sample(), inherited by all 3D objects, returning a new
flattened instance with elements drawn randomly from the original instance.
- Add transpose method to all 3D classes to transpose navigation dimensions.
- Reading of a crystal map from orientation data in Bruker's HDF5 file format.

2021-05-23 - version 0.6.0
Expand Down
5 changes: 5 additions & 0 deletions doc/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ Orientation
get_distance_matrix
scatter
set_symmetry
transpose
.. autoclass:: orix.quaternion.Orientation
:show-inheritance:
:members:
Expand All @@ -300,6 +301,7 @@ Misorientation
distance
equivalent
set_symmetry
transpose
.. autoclass:: orix.quaternion.Misorientation
:show-inheritance:
:members:
Expand Down Expand Up @@ -342,6 +344,7 @@ Rotation
random_vonmises
to_euler
to_matrix
transpose
unique
.. autoclass:: orix.quaternion.Rotation
:show-inheritance:
Expand Down Expand Up @@ -438,6 +441,7 @@ Miller
rotate
scatter
symmetrise
transpose
unique
.. autoclass:: orix.vector.Miller
:members:
Expand Down Expand Up @@ -485,6 +489,7 @@ Vector3d
rotate
scatter
to_polar
transpose
xvector
yvector
zvector
Expand Down
41 changes: 38 additions & 3 deletions orix/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ def __init__(self, this, data):


class Object3d:
"""Base class for 3d objects.
"""
"""Base class for 3d objects."""

dim = None
"""int : The number of dimensions for this object."""
Expand Down Expand Up @@ -206,6 +204,43 @@ def reshape(self, *shape):
obj._data = self._data.reshape(*shape, -1)
return obj

def transpose(self, *axes):
"""Returns a new object containing the same data transposed.
If ndim is originally 2, then order may be undefined.
In this case the first two dimensions will be transposed.
Parameters
----------
axes : int, optional
The transposed axes order. Only navigation axes need to be defined.
May be undefined if self only contains two navigation dimensions.
Returns
-------
obj :
A transposed instance of the object.
"""
# 1d object should not be transposed
if len(self.shape) == 1:
return self

# allow 2d object to be transposed without specifying axes
if not len(axes):
if len(self.shape) != 2:
raise ValueError("Axes must be defined for more than two dimensions.")
else:
# swap first two axes
axes = (1, 0)

if len(axes) != len(self.shape):
raise ValueError(
f"Number of axes is ill-defined: {tuple(axes)} does not fit with {self.shape}."
)

return self.__class__(self.data.transpose(*axes, -1))

def get_plot_data(self):
return self

Expand Down
22 changes: 22 additions & 0 deletions orix/quaternion/orientation.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,28 @@ def distance(self, verbose=False, split_size=100):
distance = _distance(self, verbose, split_size)
return distance.reshape(self.shape + self.shape)

def transpose(self, *axes):
"""Returns a new Misorientation containing the same data transposed.
If ndim is originally 2, then order may be undefined.
In this case the first two dimensions will be transposed.
Parameters
----------
axes: int, optional
The transposed axes order. Only navigation axes need to be defined.
May be undefined if self only contains two navigation dimensions.
Returns
-------
Misorientation
The transposed Misorientation.
"""
mori = super().transpose(*axes)
mori._symmetry = self._symmetry
return mori

def __repr__(self):
"""String representation."""
cls = self.__class__.__name__
Expand Down
25 changes: 25 additions & 0 deletions orix/tests/test_miller.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,31 @@ def test_various_shapes(self):
assert np.allclose(m6.hkl, v.reshape(shape2 + (3,)))
assert m1._compatible_with(m6) # Phase carries over

def test_transpose(self):
# test 2d
shape = (11, 5)
v = np.random.randint(-5, 5, shape + (3,))

m1 = Miller(hkl=v, phase=TETRAGONAL_PHASE)
m2 = m1.transpose()

assert m1.shape == m2.shape[::-1]
assert m1.phase == m2.phase

# test 2d
shape = (11, 5, 4)
v = np.random.randint(-5, 5, shape + (3,))

m1 = Miller(hkl=v, phase=TETRAGONAL_PHASE)
m2 = m1.transpose(0, 2, 1)

assert m2.shape == (11, 4, 5)
assert m1.phase == m2.phase

m2 = m1.transpose(1, 0, 2)
assert m2.shape == (5, 11, 4)
assert m1.phase == m2.phase


class TestMillerBravais:
def test_uvw2UVTW(self):
Expand Down
27 changes: 26 additions & 1 deletion orix/tests/test_orientation.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def test_getitem(orientation, symmetry):

@pytest.mark.parametrize("Gl", [C4, C2])
def test_equivalent(Gl):
""" Tests that the property Misorientation.equivalent runs without error,
"""Tests that the property Misorientation.equivalent runs without error,
use grain_exchange=True as this falls back to grain_exchange=False when
Gl!=Gr:
Expand Down Expand Up @@ -149,6 +149,31 @@ def test_sub_orientation_and_other():
_ = m - 3


def test_transpose_2d():
o1 = Orientation.random_vonmises((11, 3))
o2 = o1.transpose()

assert o1.shape == o2.shape[::-1]


@pytest.mark.parametrize(
"shape, expected_shape, axes",
[((11, 3, 5), (11, 5, 3), (0, 2, 1)), ((11, 3, 5), (3, 5, 11), (1, 2, 0))],
)
def test_transpose_3d(shape, expected_shape, axes):
o1 = Orientation.random_vonmises(shape)
o2 = o1.transpose(*axes)

assert o2.shape == tuple(expected_shape)


def test_transpose_symmetry():
o1 = Orientation.random_vonmises((11, 3)).set_symmetry(Oh)
o2 = o1.transpose()

assert o1.symmetry == o2.symmetry


class TestOrientationInitialization:
def test_from_euler_symmetry(self):
euler = np.deg2rad([90, 45, 90])
Expand Down
120 changes: 111 additions & 9 deletions orix/tests/test_vector3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,29 @@
vectors = [
(1, 0, 0),
(0, 0, 1),
((0.5, 0.5, 0.5), (-1, 0, 0),),
[[[-0.707, 0.707, 1], [2, 2, 2]], [[0.1, -0.3, 0.2], [-5, -6, -7]],],
(
(0.5, 0.5, 0.5),
(-1, 0, 0),
),
[
[[-0.707, 0.707, 1], [2, 2, 2]],
[[0.1, -0.3, 0.2], [-5, -6, -7]],
],
np.random.rand(3),
]

singles = [
(1, -1, 1),
(-5, -5, -6),
[[9, 9, 9], [0.001, 0.0001, 0.00001],],
[
[9, 9, 9],
[0.001, 0.0001, 0.00001],
],
np.array(
[[[0.5, 0.25, 0.125], [-0.125, 0.25, 0.5]], [[1, 2, 4], [1, -0.3333, 0.1667]],]
[
[[0.5, 0.25, 0.125], [-0.125, 0.25, 0.5]],
[[1, 2, 4], [1, -0.3333, 0.1667]],
]
),
]

Expand Down Expand Up @@ -141,8 +153,16 @@ def test_mul(vector, other, expected):
),
([4, 8, 12], Scalar([4]), [1, 2, 3]),
([0.5, 1.0, 1.5], 0.5, [1, 2, 3]),
([1, 2, 3], [-1, 2], [[-1, -2, -3], [1 / 2, 1, 3 / 2]],),
([1, 2, 3], np.array([-1, 1]), [[-1, -2, -3], [1, 2, 3]],),
(
[1, 2, 3],
[-1, 2],
[[-1, -2, -3], [1 / 2, 1, 3 / 2]],
),
(
[1, 2, 3],
np.array([-1, 1]),
[[-1, -2, -3], [1, 2, 3]],
),
pytest.param([1, 2, 3], "dracula", None, marks=pytest.mark.xfail),
],
indirect=["vector"],
Expand Down Expand Up @@ -202,7 +222,12 @@ def test_polar(azimuth, polar, radial, expected):


@pytest.mark.parametrize(
"shape", [(1,), (2, 2), (5, 4, 3),],
"shape",
[
(1,),
(2, 2),
(5, 4, 3),
],
)
def test_zero(shape):
v = Vector3d.zero(shape)
Expand All @@ -229,7 +254,10 @@ def test_mul_array(vector):

@pytest.mark.parametrize(
"vector, x, y, z",
[([1, 2, 3], 1, 2, 3), ([[0, 2, 3], [2, 2, 3]], [0, 2], [2, 2], [3, 3]),],
[
([1, 2, 3], 1, 2, 3),
([[0, 2, 3], [2, 2, 3]], [0, 2], [2, 2], [3, 3]),
],
indirect=["vector"],
)
def test_xyz(vector, x, y, z):
Expand Down Expand Up @@ -302,7 +330,12 @@ def test_assign_z(vector, data, expected):


@pytest.mark.parametrize(
"vector", [[(1, 0, 0)], [(0.5, 0.5, 1.25), (-1, -1, -1)],], indirect=["vector"],
"vector",
[
[(1, 0, 0)],
[(0.5, 0.5, 1.25), (-1, -1, -1)],
],
indirect=["vector"],
)
def test_perpendicular(vector: Vector3d):
assert np.allclose(vector.dot(vector.perpendicular).data, 0)
Expand All @@ -316,6 +349,75 @@ def test_mean_xyz():
np.allclose(t.mean().data, 1)


def test_transpose_1d():
v1 = Vector3d(np.random.rand(7, 3))
v2 = v1.transpose()

assert np.allclose(v1.data, v2.data)


@pytest.mark.parametrize(
"shape, expected_shape",
[
([6, 4, 3], [4, 6, 3]),
([11, 5, 3], [5, 11, 3]),
],
)
def test_transpose_2d_data_shape(shape, expected_shape):
v1 = Vector3d(np.random.rand(*shape))
v2 = v1.transpose()

assert v2.data.shape == tuple(expected_shape)


def test_transpose_3d_no_axes():
v1 = Vector3d(np.random.rand(5, 4, 2, 3))
with pytest.raises(ValueError, match="Axes must be defined for more than"):
_ = v1.transpose()


def test_transpose_3d_wrong_number_of_axes():
v1 = Vector3d(np.random.rand(5, 4, 2, 3))
with pytest.raises(ValueError, match="Number of axes is ill-defined"):
_ = v1.transpose(0, 2)


@pytest.mark.parametrize(
"shape, expected_shape",
[
([6, 4], [4, 6]),
([11, 5], [5, 11]),
],
)
def test_transpose_2d_shape(shape, expected_shape):
v1 = Vector3d(np.random.rand(*shape, 3))
v2 = v1.transpose()

assert v2.shape == tuple(expected_shape)


@pytest.mark.parametrize(
"shape, expected_shape, axes",
[([6, 4, 5, 3], [4, 5, 6, 3], [1, 2, 0]), ([6, 4, 5, 3], [5, 4, 6, 3], [2, 1, 0])],
)
def test_transpose_3d_data_shape(shape, expected_shape, axes):
v1 = Vector3d(np.random.rand(*shape))
v2 = v1.transpose(*axes)

assert v2.data.shape == tuple(expected_shape)


@pytest.mark.parametrize(
"shape, expected_shape, axes",
[([6, 4, 5], [4, 5, 6], [1, 2, 0]), ([6, 4, 5], [5, 4, 6], [2, 1, 0])],
)
def test_transpose_3d_shape(shape, expected_shape, axes):
v1 = Vector3d(np.random.rand(*shape, 3))
v2 = v1.transpose(*axes)

assert v2.shape == tuple(expected_shape)


@pytest.mark.xfail(strict=True, reason=ValueError)
def test_zero_perpendicular():
t = Vector3d(np.asarray([0, 0, 0]))
Expand Down
Loading

0 comments on commit 05311f6

Please sign in to comment.