Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add class method from_align_vectors() to Quaternion, Misorientation and Orientation #401

Merged
merged 22 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
110b869
Added functionality from_align_vectors() for Quaternion, Misorientati…
anderscmathisen Oct 9, 2022
82bb2e8
Improved docstrings and code of from_align_vectors() for quaternion
anderscmathisen Oct 11, 2022
76b5274
Improved docstrings and return value implementation of from_align_vec…
anderscmathisen Oct 11, 2022
e0189fc
removed excess links in docstring of from_align_vectors, added more d…
anderscmathisen Oct 12, 2022
a7d2b30
Type restrictions for arguments of .from_align_vectors()
anderscmathisen Oct 13, 2022
edb1256
import Miller added to same line as others
anderscmathisen Oct 13, 2022
be3f986
Changed argument names, added examples in docs and changed point_grou…
anderscmathisen Oct 13, 2022
836f85e
Added test for from_align_vectors and from_scipy_rotation method of q…
anderscmathisen Oct 13, 2022
b44eb7f
Added test for from_align_vectors method of Orientation and Misorient…
anderscmathisen Oct 13, 2022
153d2cd
moved test_from_scipy_rotation() to Rotation tests
anderscmathisen Oct 14, 2022
d1bd618
Updated docstrings and examples of from_align_vectors()
anderscmathisen Oct 14, 2022
649757e
Updated tests for from_align_vectors()
anderscmathisen Oct 14, 2022
e897808
Updated docstring of from_scipy_rotation
anderscmathisen Oct 14, 2022
169afc1
Added the required shape of vectors in from_align_vectors to docstring
anderscmathisen Oct 17, 2022
3882808
Add @anderscmathisen to credits
hakonanes Oct 18, 2022
dfb88c7
Mention new class methods for Quaternion and children to changelog
hakonanes Oct 18, 2022
9d75cf5
from_scipy_rotation inverts the rotation
anderscmathisen Oct 26, 2022
6d98478
Tests updated for from_scipy_rotation inverts rotation
anderscmathisen Oct 26, 2022
c65b993
rebranching fixed merge issues (changelog might be wrong)
anderscmathisen Dec 1, 2022
752a7aa
updated from_scipy_rotation to use matrix
anderscmathisen Dec 1, 2022
2c5fb06
fixed error in docstring example of from_matrix
anderscmathisen Dec 1, 2022
f31cd66
Fixed '(s)' messing up format of Quaternions in changelog
anderscmathisen Dec 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 171 additions & 1 deletion orix/quaternion/orientation.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from orix.quaternion.orientation_region import OrientationRegion
from orix.quaternion.rotation import Rotation
from orix.quaternion.symmetry import C1, Symmetry, _get_unique_symmetry_elements
from orix.vector import AxAngle, NeoEuler, Vector3d
from orix.vector import AxAngle, Miller, NeoEuler, Vector3d


class Misorientation(Rotation):
Expand Down Expand Up @@ -375,6 +375,92 @@ def get_distance_matrix(

return angles

@classmethod
def from_align_vectors(
hakonanes marked this conversation as resolved.
Show resolved Hide resolved
cls,
other: Miller,
initial: Miller,
weights: Optional[np.ndarray] = None,
return_rmsd: bool = False,
return_sensitivity: bool = False,
) -> Misorientation:
"""Return an estimated misorientation to optimally align two
sets of vectors, one set in each crystal.

This method wraps :meth:`scipy.spatial.transform.Rotation.align_vectors`,
see that method for further explanations of parameters and
returns.

Parameters
----------
other
Directions of shape ``(N,)`` in the other crystal.
initial
Directions of shape ``(N,)`` in the initial crystal.
weights
The relative importance of the different vectors.
return_rmsd
Whether to return the root mean square distance (weighted)
between ``other`` and ``initial`` after alignment.
return_sensitivity
Whether to return the sensitivity matrix.

Returns
-------
estimated misorientation
Best estimate of the ``misorientation`` that transforms
``initial`` to ``other``. The symmetry of the
``misorientation`` is inferred from the phase of ``other``
and ``initial``, if given.
rmsd
Returned when ``return_rmsd=True``.
sensitivity
Returned when ``return_sensitivity=True``.

Raises
------
ValueError
If ``other`` and ``initial`` are not Miller instances.

Examples
--------
>>> from orix.quaternion import Misorientation
>>> from orix.vector import Miller
>>> from orix.crystal_map.phase_list import Phase
>>> uvw1 = Miller(
... uvw=[[1, 0, 0], [0, 1, 0]], phase=Phase(point_group="m-3m")
... )
>>> uvw2 = Miller(
... uvw=[[1, 0, 0], [0, 0, 1]], phase=Phase(point_group="m-3m")
... )
>>> mori12 = Misorientation.from_align_vectors(uvw2, uvw1)
>>> mori12 * uvw1
Miller (2,), point group m-3m, uvw
[[1. 0. 0.]
[0. 0. 1.]]
"""
hakonanes marked this conversation as resolved.
Show resolved Hide resolved
if not isinstance(other, Miller) or not isinstance(initial, Miller):
raise ValueError(
"Arguments other and initial must both be of type Miller, "
f"but are of type {type(other)} and {type(initial)}."
)

out = super().from_align_vectors(
other=other,
initial=initial,
weights=weights,
return_rmsd=return_rmsd,
return_sensitivity=return_sensitivity,
)
out = list(out)

try:
out[0].symmetry = (initial.phase.point_group, other.phase.point_group)
except (AttributeError, ValueError):
pass

return out[0] if len(out) == 1 else out


class Orientation(Misorientation):
r"""Orientations represent misorientations away from a reference of
Expand Down Expand Up @@ -474,6 +560,90 @@ def from_euler(
ori.symmetry = symmetry
return ori

@classmethod
def from_align_vectors(
hakonanes marked this conversation as resolved.
Show resolved Hide resolved
cls,
other: Miller,
initial: Vector3d,
weights: Optional[np.ndarray] = None,
return_rmsd: bool = False,
return_sensitivity: bool = False,
) -> Orientation:
"""Return an estimated orientation to optimally align vectors in
the crystal and sample reference frames.

This method wraps :meth:`scipy.spatial.transform.Rotation.align_vectors`,
see that method for further explanations of parameters and
returns.

Parameters
----------
other
Directions of shape ``(N,)`` in the other crystal.
initial
Directions of shape ``(N,)`` in the initial crystal.
weights
The relative importance of the different vectors.
return_rmsd
Whether to return the root mean square distance (weighted)
between ``other`` and ``initial`` after alignment.
return_sensitivity
Whether to return the sensitivity matrix.

Returns
-------
estimated orientation
Best estimate of the ``orientation`` that transforms
``initial`` to ``other``. The symmetry of the ``orientaiton``
is inferred form the point group of the phase of ``other``,
if given.
rmsd
Returned when ``return_rmsd=True``.
sensitivity
Returned when ``return_sensitivity=True``.

Raises
------
ValueError
If ``other`` is not a Miller instance.

Examples
--------
>>> from orix.quaternion import Orientation
>>> from orix.vector import Vector3d, Miller
>>> from orix.crystal_map.phase_list import Phase
>>> crystal_millers = Miller(
... uvw=[[0, 1 ,0], [1, 0, 0]], phase=Phase(point_group="m-3m")
... )
>>> sample_vectors = Vector3d([[0, -1, 0], [0, 0, 1]])
>>> ori = Orientation.from_align_vectors(crystal_millers, sample_vectors)
>>> ori * sample_vectors
Vector3d (2,)
[[0. 1. 0.]
[1. 0. 0.]]
"""
if not isinstance(other, Miller):
raise ValueError(
f"Argument other must be of type Miller, but has type {type(other)}"
)

out = Rotation.from_align_vectors(
other=other,
initial=initial,
weights=weights,
return_rmsd=return_rmsd,
return_sensitivity=return_sensitivity,
)
out = list(out)
out[0] = cls(out[0].data)

try:
out[0].symmetry = other.phase.point_group
except (AttributeError, ValueError):
pass

return out[0] if len(out) == 1 else out

@classmethod
def from_matrix(
cls, matrix: np.ndarray, symmetry: Optional[Symmetry] = None
Expand Down
105 changes: 104 additions & 1 deletion orix/quaternion/quaternion.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@

from __future__ import annotations

from typing import Union
from typing import Optional, Union
import warnings

import dask.array as da
from dask.diagnostics import ProgressBar
import numpy as np
import quaternion
from scipy.spatial.transform import Rotation as SciPyRotation

from orix.base import Object3d
from orix.vector import Miller, Vector3d
Expand Down Expand Up @@ -498,3 +499,105 @@ def _outer_dask(

new_chunks = tuple(chunks1[:-1]) + tuple(chunks2[:-1]) + (-1,)
return out.rechunk(new_chunks)

@classmethod
def from_scipy_rotation(cls, rotation: SciPyRotation) -> Quaternion:
"""Return quaternion(s) from :class:`scipy.spatial.transform.Rotation`.

Parameters
----------
rotation
SciPy rotation(s).

Returns
-------
quaternion
Quaternion(s).

Examples
--------
>>> from orix.quaternion import Quaternion, Rotation
>>> from scipy.spatial.transform import Rotation as SciPyRotation
>>> euler = np.array([90, 0, 0]) * np.pi / 180
>>> scipy_rot = SciPyRotation.from_euler("ZXZ", euler)
>>> ori = Quaternion.from_scipy_rotation(scipy_rot)
hakonanes marked this conversation as resolved.
Show resolved Hide resolved
>>> ori
Quaternion (1,)
[[0.7071 0. 0. 0.7071]]
>>> Rotation.from_euler(euler, direction="crystal2lab")
Rotation (1,)
[[0.7071 0. 0. 0.7071]]
"""
b, c, d, a = rotation.as_quat()

return cls([a, b, c, d])

@classmethod
def from_align_vectors(
cls,
other: Union[Vector3d, tuple, list],
initial: Union[Vector3d, tuple, list],
weights: Optional[np.ndarray] = None,
return_rmsd: bool = False,
return_sensitivity: bool = False,
) -> Quaternion:
"""Return an estimated quaternion to optimally align two sets of
vectors.

hakonanes marked this conversation as resolved.
Show resolved Hide resolved
This method wraps :meth:`scipy.spatial.transform.Rotation.align_vectors`,
see that method for further explanations of parameters and
returns.

Parameters
----------
other
Vectors of shape ``(N,)`` in the other reference frame.
initial
Vectors of shape ``(N,)`` in the initial reference frame.
weights
The relative importance of the different vectors.
return_rmsd
Whether to return the root mean square distance (weighted)
between ``other`` and ``initial`` after alignment.
return_sensitivity
Whether to return the sensitivity matrix.

Returns
-------
estimated quaternion
Best estimate of the quaternion
that transforms ``initial`` to ``other``.
rmsd
Returned when ``return_rmsd=True``.
sensitivity
Returned when ``return_sensitivity=True``.

hakonanes marked this conversation as resolved.
Show resolved Hide resolved
Examples
--------
>>> from orix.quaternion import Quaternion
>>> from orix.vector import Vector3d
>>> vecs1 = Vector3d([[1, 0, 0], [0, 1, 0]])
>>> vecs2 = Vector3d([[0, -1, 0], [0,0, 1]])
>>> quat12 = Quaternion.from_align_vectors(vecs2, vecs1)
>>> quat12 * vecs1
Vector3d (2,)
[[ 0. -1. 0.]
[ 0. 0. 1.]]
"""
if not isinstance(other, Vector3d):
other = Vector3d(other)
if not isinstance(initial, Vector3d):
initial = Vector3d(initial)
vec1 = other.unit.data
vec2 = initial.unit.data

out = SciPyRotation.align_vectors(
vec1, vec2, weights=weights, return_sensitivity=return_sensitivity
)
out = list(out)
out[0] = cls.from_scipy_rotation(out[0])

if not return_rmsd:
del out[1]

return out[0] if len(out) == 1 else out
hakonanes marked this conversation as resolved.
Show resolved Hide resolved
Loading