Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
df9ca1d
adding ancillary capabilities from PR442
argerlt May 19, 2025
e67ff70
add tests for 'from_path_ends`
argerlt May 27, 2025
5ffecb3
black formatting
argerlt May 27, 2025
b15fff8
adding requested changes fomr #558
argerlt Jul 13, 2025
4b3d35b
made Quaternion.from_path_ends blind to symmetry, and added symmetry-…
argerlt Jul 17, 2025
6fe959f
fixing docstring styling
argerlt Jul 18, 2025
18088ef
formatting and clarifying tests
argerlt Jul 18, 2025
892054a
clarifying tests and adding from_path_ends example
argerlt Jul 18, 2025
269996c
formatting
argerlt Jul 18, 2025
e5c307f
including suggestions and updating example
argerlt Sep 4, 2025
70a877c
hotfix for projections
argerlt Sep 4, 2025
81c6b1d
adding ancillary capabilities from PR442
argerlt May 19, 2025
d01b78b
add tests for 'from_path_ends`
argerlt May 27, 2025
e8afae0
black formatting
argerlt May 27, 2025
fcdc166
adding requested changes fomr #558
argerlt Jul 13, 2025
7704d64
made Quaternion.from_path_ends blind to symmetry, and added symmetry-…
argerlt Jul 17, 2025
57b5688
fixing docstring styling
argerlt Jul 18, 2025
f842e0b
formatting and clarifying tests
argerlt Jul 18, 2025
d089947
clarifying tests and adding from_path_ends example
argerlt Jul 18, 2025
ef9f77c
formatting
argerlt Jul 18, 2025
03827e0
including suggestions and updating example
argerlt Sep 4, 2025
1aeb7a9
add HomochroicPlot, cleaned up examples, formatting
argerlt Sep 5, 2025
4b27b9f
Merge branch 'adopt-`reduce`-and-other-updates-from-#442' of https://…
argerlt Sep 5, 2025
3209fbc
Delete plot_non_euclidean_paths.py
argerlt Sep 5, 2025
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
8 changes: 6 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ Changed

Removed
-------

- ``verbose`` parameter in ``reduce()``
(replacing ``map_into_symmetry_reduced_zone()``).

Deprecated
----------

- ``map_into_symmetry_reduced_zone()`` is deprecated since 0.14 and will be removed in
0.15. Use ``reduce()`` instead.

Fixed
-----
- minor speedups to 'pytest'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"# Stack and map into the Oh fundamental zone\n",
"ori = Orientation.stack([cluster1, cluster2, cluster3]).flatten()\n",
"ori.symmetry = Oh\n",
"ori = ori.map_into_symmetry_reduced_zone()"
"ori = ori.reduce()"
]
},
{
Expand Down Expand Up @@ -158,7 +158,7 @@
"mori2 = (~ori).outer(ori)\n",
"\n",
"mori2.symmetry = Oh\n",
"mori2 = mori2.map_into_symmetry_reduced_zone()\n",
"mori2 = mori2.reduce()\n",
"\n",
"D2 = mori2.angle"
]
Expand Down
8 changes: 4 additions & 4 deletions doc/tutorials/clustering_misorientations.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
"metadata": {},
"outputs": [],
"source": [
"ori = ori.map_into_symmetry_reduced_zone()"
"ori = ori.reduce()"
]
},
{
Expand Down Expand Up @@ -213,7 +213,7 @@
"outputs": [],
"source": [
"mori.symmetry = (D6, D6)\n",
"mori = mori.map_into_symmetry_reduced_zone()"
"mori = mori.reduce()"
]
},
{
Expand Down Expand Up @@ -305,7 +305,7 @@
"\n",
" # Map into the fundamental zone\n",
" mori_i.symmetry = (D6, D6)\n",
" mori_i = mori_i.map_into_symmetry_reduced_zone()\n",
" mori_i = mori_i.reduce()\n",
"\n",
" # Get the cluster mean\n",
" mori_i = mori_i.mean()\n",
Expand All @@ -318,7 +318,7 @@
"\n",
"# Map into the fundamental zone\n",
"cluster_means.symmetry = (D6, D6)\n",
"cluster_means = cluster_means.map_into_symmetry_reduced_zone()\n",
"cluster_means = cluster_means.reduce()\n",
"cluster_means"
]
},
Expand Down
6 changes: 3 additions & 3 deletions doc/tutorials/clustering_orientations.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
"metadata": {},
"outputs": [],
"source": [
"ori = ori.map_into_symmetry_reduced_zone()"
"ori = ori.reduce()"
]
},
{
Expand Down Expand Up @@ -320,7 +320,7 @@
"\n",
"# Map into the fundamental zone\n",
"cluster_means.symmetry = D6\n",
"cluster_means = cluster_means.map_into_symmetry_reduced_zone()\n",
"cluster_means = cluster_means.reduce()\n",
"cluster_means"
]
},
Expand Down Expand Up @@ -393,7 +393,7 @@
"\n",
"# Map into the fundamental zone\n",
"ori_recentered.symmetry = D6\n",
"ori_recentered = ori_recentered.map_into_symmetry_reduced_zone()\n",
"ori_recentered = ori_recentered.reduce()\n",
"\n",
"cluster_means_recentered = Orientation.stack(\n",
" [ori_recentered[all_labels == l].mean() for l in unique_cluster_labels]\n",
Expand Down
141 changes: 141 additions & 0 deletions examples/plotting/plotting_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
r"""
=========================================
Plot Paths In Rotation and Vector Space
=========================================

This example shows how paths though either rotation or vector space
can be plotted using ORIX. These are the shortest paths through their
respective spaces, and thus not always straight lines in euclidean
projections (Rodrigues, stereographic, etc.).

This functionality is available in :class:`~orix.vector.Vector3d`,
:class:`~orix.quaternions.Rotation`,
:class:`~orix.quaternions.Orientation`,
and :class:`~orix.quaternions.Misorientation`.
"""

from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np

from orix import plot
from orix.plot.direction_color_keys import DirectionColorKeyTSL
from orix.quaternion import Orientation, OrientationRegion, Quaternion
from orix.quaternion.symmetry import C1, Oh
from orix.sampling import sample_S2
from orix.vector import Vector3d

fig = plt.figure(figsize=(6, 6))
n_steps = 30

# ========= #
# Example 1: Plotting multiple paths into a user defined axis
# ========= #
# This subplot shows several paths through the cubic (m3m) fundamental zone
# created by rotating 20 randomly chosen points 30 degrees around the z axis.
# these paths are drawn in rodrigues space, which is an equal-angle projection
# of rotation space. As such, notice how all lines tracing out axial rotations
# are straight, but lines starting closer to the center of the fundamental zone
# appear shorter.
# the sampe paths are then also plotted on an Inverse Pole Figure (IPF) plot.
rod_ax = fig.add_subplot(2, 2, 1, projection="rodrigues", proj_type="ortho")
ipf_ax = fig.add_subplot(2, 2, 2, projection="ipf", symmetry=Oh)

# 10 random orientations with the cubic m3m ('Oh' in the schoenflies notation)
# crystal symmetry.
oris = Orientation(
data=np.array(
[
[0.69, 0.24, 0.68, 0.01],
[0.26, 0.59, 0.32, 0.7],
[0.07, 0.17, 0.93, 0.31],
[0.6, 0.03, 0.61, 0.52],
[0.51, 0.38, 0.34, 0.69],
[0.31, 0.86, 0.22, 0.35],
[0.68, 0.67, 0.06, 0.31],
[0.01, 0.12, 0.05, 0.99],
[0.39, 0.45, 0.34, 0.72],
[0.65, 0.59, 0.46, 0.15],
]
),
symmetry=Oh,
)
# reduce them to their crystallographically identical representations
oris = oris.reduce()
# define a 20 degree rotation around the z axis
shift = Orientation.from_axes_angles([0, 0, 1], 30, degrees=True)
# for each orientation, calculate and plot the path they would take during a
# 45 degree shift.
segment_colors = cm.inferno(np.linspace(0, 1, n_steps))
for ori in oris:
points = Orientation.stack([ori, (shift * ori)]).reduce()
points.symmetry = Oh
path = Orientation.from_path_ends(points, steps=n_steps)
rod_ax.scatter(path, c=segment_colors)
ipf_ax.scatter(path, c=segment_colors)

# add the wireframe and clean up the plot.
fz = OrientationRegion.from_symmetry(path.symmetry)
rod_ax.plot_wireframe(fz)
rod_ax._correct_aspect_ratio(fz)
rod_ax.axis("off")
rod_ax.set_title(r"Rodrigues, multiple paths")
ipf_ax.set_title(r"IPF, multiple paths ")


# %%
# ========= #
# Example 2: Plotting a path using `Rotation.scatter'
# ========= #
# This subplot traces the path of an object rotated 90 degrees around the
# X axis, then 90 degrees around the Y axis. The path is plotted in
# homochoric space, which is an equal-volume projection of rotation space
rots = Orientation.from_axes_angles(
[[1, 0, 0], [1, 0, 0], [0, 1, 0]], [0, 90, 90], degrees=True, symmetry=C1
)
rots[2] = rots[1] * rots[2]
path = Orientation.from_path_ends(rots, steps=n_steps)
# create a list of RGBA color values for a gradient red line and blue line
path_colors = np.vstack(
[cm.Reds(np.linspace(0.5, 1, n_steps)), cm.Blues(np.linspace(0.5, 1, n_steps))]
)

# Here, we instead use the in-built plotting tool from
# Orientation.scatter to auto-generate the subplot. This is especially handy when
# plotting only a single Orientation object.
path.scatter(figure=fig, position=[2, 2, 3], marker=">", c=path_colors)
fig.axes[2].set_title(r"Homochoric, two $90^\circ$ rotations")

# %%

# ========= #
# Example 3: paths in stereographic plots
# ========= #
# This is similar to the second example, but now vectors are being rotated
# 20 degrees around the [1,1,1] axis on a stereographic plot.

vec_ax = plt.subplot(2, 2, 4, projection="stereographic", hemisphere="upper")
ipf_colormap = DirectionColorKeyTSL(C1)

# define a mesh of vectors with approximately 20 degree spacing, and
# within 80 degrees of the Z axis
vecs = sample_S2(20)
vecs = vecs[vecs.polar < (80 * np.pi / 180)]

# define a 15 degree rotation around [1,1,1]
rots = Quaternion.from_axes_angles([1, 1, 1], [0, 15], degrees=True)

for vec in vecs:
path_ends = rots * vec
# color each path using a gradient pased on the IPF coloring.
c = ipf_colormap.direction2color(vec)
if np.abs(path_ends.cross(path_ends[::-1])[0].norm) > 1e-12:
path = Vector3d.from_path_ends(path_ends, steps=100)
segment_c = c * np.linspace(0.25, 1, path.size)[:, np.newaxis]
vec_ax.scatter(path, c=segment_c)
else:
vec_ax.scatter(path_ends[0], c=c)

vec_ax.set_title(r"Stereographic")
vec_ax.set_labels("X", "Y")
plt.tight_layout()
17 changes: 16 additions & 1 deletion orix/plot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,25 @@
:class:`~orix.quaternion.Orientation`,
:class:`~orix.quaternion.Misorientation`, and
:class:`~orix.crystal_map.CrystalMap`.
"""

NOTE: While lazy loading is preferred, the following six classes
are explicitly imported in order to populate matplotlib.projections
"""
import lazy_loader

from orix.plot.crystal_map_plot import CrystalMapPlot
from orix.plot.rotation_plot import (
AxAnglePlot,
HomochoricPlot,
RodriguesPlot,
RotationPlot,
)
from orix.plot.stereographic_plot import StereographicPlot

# Must be imported below StereographicPlot since it imports it
from orix.plot.inverse_pole_figure_plot import InversePoleFigurePlot # isort: skip


# Imports from stub file (see contributor guide for details)
__getattr__, __dir__, __all__ = lazy_loader.attach_stub(__name__, __file__)

Expand Down
6 changes: 0 additions & 6 deletions orix/plot/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,9 @@ from .inverse_pole_figure_plot import InversePoleFigurePlot # isort: skip
# Lazily imported in module init
__all__ = [
# Classes
"AxAnglePlot",
"CrystalMapPlot",
"DirectionColorKeyTSL",
"EulerColorKey",
"InversePoleFigurePlot",
"IPFColorKeyTSL",
"RodriguesPlot",
"RotationPlot",
"StereographicPlot",
# Functions
"format_labels",
]
14 changes: 11 additions & 3 deletions orix/plot/rotation_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

from orix.vector import AxAngle, Rodrigues
from orix.vector import AxAngle, Homochoric, Rodrigues


class RotationPlot(Axes3D):
Expand Down Expand Up @@ -62,7 +62,7 @@ def transform(
raise TypeError("fundamental_zone is not an OrientationRegion object.")
# if any in xs are out of fundamental_zone, calculate symmetry reduction
if not (xs < fundamental_zone).all():
xs = xs.map_into_symmetry_reduced_zone()
xs = xs.reduce()

if isinstance(xs, Rotation):
if isinstance(xs, OrientationRegion):
Expand Down Expand Up @@ -152,16 +152,24 @@ class AxAnglePlot(RotationPlot):
transformation_class = AxAngle


class HomochoricPlot(RotationPlot):
"""Plot rotations in a axis-angle space."""

name = "homochoric"
transformation_class = Homochoric


projections.register_projection(RodriguesPlot)
projections.register_projection(AxAnglePlot)
projections.register_projection(HomochoricPlot)


def _setup_rotation_plot(
figure: Optional[plt.Figure] = None,
projection: str = "axangle",
position: Union[int, tuple, SubplotSpec, None] = (1, 1, 1),
figure_kwargs: Optional[dict] = None,
) -> Tuple[plt.Figure, Union[AxAnglePlot, RodriguesPlot]]:
) -> Tuple[plt.Figure, Union[AxAnglePlot, RodriguesPlot, HomochoricPlot]]:
"""Return a figure and rotation plot axis of the correct type.

This is a convenience method used in e.g.
Expand Down
Loading
Loading