Skip to content

Commit

Permalink
Rewrite similarity check for phase lists from orix and PyEBSDIndex
Browse files Browse the repository at this point in the history
Signed-off-by: Håkon Wiik Ånes <hwaanes@gmail.com>
  • Loading branch information
hakonanes committed Oct 25, 2023
1 parent 413b283 commit 8c9b16a
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 96 deletions.
18 changes: 11 additions & 7 deletions kikuchipy/detectors/ebsd_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,7 @@ def get_indexer(
self,
phase_list: PhaseList,
reflectors: Optional[
List[Union["ReciprocalLatticeVector", np.ndarray, None]]
List[Union["ReciprocalLatticeVector", np.ndarray, list, tuple, None]]
] = None,
**kwargs,
) -> "EBSDIndexer":
Expand All @@ -1123,13 +1123,13 @@ def get_indexer(
only supports a list containing one face-centered cubic
(FCC) phase, one body-centered cubic (BCC) phase or both.
reflectors
List of unique reflectors :math:`\{hkl\}` to use in indexing
for each phase. If not passed, the default in
List of reflectors or pole families :math:`\{hkl\}` to use
in indexing for each phase. If not passed, the default in
:func:`pyebsdindex.tripletvote.addphase` is used. For each
phase, the reflector list can either be None, a NumPy array,
or a
:class:`~diffsis.crystallography.ReciprocalLatticeVector`.
If None is passed, the default in PyEBSDIndex is used.
phase, the reflectors can either be a NumPy array, a list,
a tuple, a
:class:`~diffsis.crystallography.ReciprocalLatticeVector`,
or None.
**kwargs
Keyword arguments passed to
:class:`~pyebsdindex.ebsd_index.EBSDIndexer`, except for the
Expand All @@ -1151,6 +1151,10 @@ def get_indexer(
Requires that PyEBSDIndex is installed, which is an optional
dependency of kikuchipy. See :ref:`optional-dependencies` for
details.
See Also
--------
pyebsdindex.tripletvote.addphase
"""
return _get_indexer_from_detector(
phase_list=phase_list,
Expand Down
49 changes: 18 additions & 31 deletions kikuchipy/detectors/tests/test_ebsd_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -1052,47 +1052,34 @@ def test_get_indexer_raises(self): # pragma: no cover
not kp._pyebsdindex_installed, reason="pyebsdindex is not installed"
)
def test_get_indexer_invalid_phase_lists(self):
# More than two phases
with pytest.raises(ValueError, match="Hough indexing only supports indexing "):
_ = self.det.get_indexer(
PhaseList(names=["a", "b", "c"], space_groups=[225, 227, 229])
)

# Not all phases have space groups
pl = PhaseList(names=["a", "b"], point_groups=["m-3m", "432"])
pl["a"].space_group = 225
with pytest.raises(ValueError, match="Space group for each phase must be set,"):
_ = self.det.get_indexer(pl)

# Not FCC or BCC
with pytest.raises(ValueError, match="Hough indexing only supports indexing "):
_ = self.det.get_indexer(PhaseList(names="sic", space_groups=186))

@pytest.mark.skipif(
not kp._pyebsdindex_installed, reason="pyebsdindex is not installed"
)
def test_get_indexer(self):
indexer1 = self.det.get_indexer(
PhaseList(names=["a", "b"], space_groups=[225, 229]), nBands=6
)
assert indexer1.vendor == "KIKUCHIPY"
assert np.isclose(indexer1.sampleTilt, self.det.sample_tilt)
assert np.isclose(indexer1.camElev, self.det.tilt)
assert tuple(indexer1.bandDetectPlan.patDim) == self.det.shape
assert indexer1.bandDetectPlan.nBands == 6
assert np.allclose(indexer1.PC, self.det.pc_flattened)
assert indexer1.phaselist == ["FCC", "BCC"]

indexer2 = self.det.get_indexer(PhaseList(names="a", space_groups=225))
assert indexer2.phaselist == ["FCC"]

indexer3 = self.det.get_indexer(PhaseList(names="a", space_groups=220))
assert indexer3.phaselist == ["BCC"]

indexer4 = self.det.get_indexer(
PhaseList(names=["a", "b"], space_groups=[220, 225])
)
assert indexer4.phaselist == ["BCC", "FCC"]
# fmt: off
# -1 2/m 222 -3 -3m 4/m 4/mmm 6/m 6/mmm m-3 m-3m
space_groups = [ 1, 15, 74, 75, 142, 143, 167, 168, 194, 195, 207]
laue_codes = [ 1, 2, 22, 4, 42, 3, 32, 6, 62, 23, 43]
# fmt: on
names = "abcdefghijk"

pl = PhaseList(names=list(names), space_groups=space_groups)
indexer = self.det.get_indexer(pl, nBands=6)
assert indexer.vendor == "KIKUCHIPY"
assert np.isclose(indexer.sampleTilt, self.det.sample_tilt)
assert np.isclose(indexer.camElev, self.det.tilt)
assert tuple(indexer.bandDetectPlan.patDim) == self.det.shape
assert indexer.bandDetectPlan.nBands == 6
assert np.allclose(indexer.PC, self.det.pc_flattened)
for phase, sg, code in zip(indexer.phaselist, pl.space_groups, laue_codes):
assert phase.spacegroup == sg.number
assert phase.lauecode == code


class TestSaveLoadDetector:
Expand Down
154 changes: 119 additions & 35 deletions kikuchipy/indexing/_hough_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ def _get_indexer_from_detector(
pc: np.ndarray,
sample_tilt: float,
tilt: float,
reflectors: Optional[List[Union[ReciprocalLatticeVector, np.ndarray, None]]] = None,
reflectors: Optional[
List[Union[ReciprocalLatticeVector, np.ndarray, list, tuple, None]]
] = None,
**kwargs,
) -> "EBSDIndexer":
r"""Return a PyEBSDIndex EBSD indexer.
Expand All @@ -135,13 +137,13 @@ def _get_indexer_from_detector(
tilt
Detector tilt in degrees.
reflectors
List of unique reflectors :math:`\{hkl\}` to use in indexing for
each phase. If not passed, the default in
List of reflectors or pole families :math:`\{hkl\}` to use in
indexing for each phase. If not passed, the default in
:func:`pyebsdindex.tripletvote.addphase` is used. For each
phase, the reflector list can either be None, a NumPy array,
or a
:class:`~diffsis.crystallography.ReciprocalLatticeVector`.
If None is passed, the default in PyEBSDIndex is used.
phase, the reflectors can either be a NumPy array, a list, a
tuple, a
:class:`~diffsis.crystallography.ReciprocalLatticeVector`, or
None.
**kwargs
Keyword arguments passed to
:class:`~pyebsdindex.ebsd_index.EBSDIndexer`.
Expand Down Expand Up @@ -250,28 +252,35 @@ def _hough_indexing(

def _get_pyebsdindex_phaselist(
phase_list: PhaseList,
reflectors: Optional[List[Union[ReciprocalLatticeVector, np.ndarray, None]]] = None,
reflectors: Optional[
List[Union[ReciprocalLatticeVector, np.ndarray, list, tuple, None]]
] = None,
) -> List["BandIndexer"]:
r"""Return a phase list compatible with PyEBSDIndex or raise a
``ValueError`` if the given phase list is incompatible.
r"""Return a phase list compatible with PyEBSDIndex from an orix
phase list.
A ``ValueError`` is raised if the orix phase list contains phases
without the space group set or if the length of the reflector list
is unequal to the list of phases.
Parameters
----------
phase_list
Phase list to convert to one compatible with PyEBSDIndex.
reflectors
List of unique reflectors :math:`\{hkl\}` to use in indexing for
each phase. If not passed, the default in
List of reflectors or pole families :math:`\{hkl\}` to use in
indexing for each phase. If not passed, the default in
:func:`pyebsdindex.tripletvote.addphase` is used. For each
phase, the reflector list can either be None, a NumPy array,
or a
:class:`~diffsis.crystallography.ReciprocalLatticeVector`.
If None is passed, the default in PyEBSDIndex is used.
phase, the reflectors can either be a NumPy array, a list, a
tuple, a
:class:`~diffsis.crystallography.ReciprocalLatticeVector`, or
None.
Returns
-------
phase_list_pei
Phase list compatible with PyEBSDIndex.
phase_list_pei : list of pyebsdindex.tripletvote.BandIndexer
Phase list of phases (band indexers) compatible with
PyEBSDIndex.
Raises
------
Expand All @@ -281,17 +290,17 @@ def _get_pyebsdindex_phaselist(
"""
from pyebsdindex.tripletvote import addphase

# Make list of reflectors iterable
if reflectors is None:
reflectors = [None] * phase_list.size
elif isinstance(reflectors, (np.ndarray, ReciprocalLatticeVector)):
reflectors = [
reflectors,
]
reflectors = [reflectors]
elif isinstance(reflectors, (list, tuple)) and len(reflectors) != phase_list.size:
reflectors = [reflectors]

if len(reflectors) != phase_list.size:
raise ValueError(
"One list of reflectors (or None) must be passed per phase in the phase "
"list."
"One set of reflectors or None must be passed per phase in the phase list."
)

phase_list_pei = []
Expand All @@ -307,8 +316,7 @@ def _get_pyebsdindex_phaselist(
ref = reflectors[i]
if isinstance(ref, ReciprocalLatticeVector):
ref = ref.unique(use_symmetry=True).hkl
elif isinstance(ref, np.ndarray):
# Ensure uniqueness
elif isinstance(ref, (np.ndarray, list, tuple)):
ref = ReciprocalLatticeVector(phase, hkl=ref).unique(use_symmetry=True).hkl
else:
ref = None
Expand All @@ -334,8 +342,9 @@ def _indexer_is_compatible_with_kikuchipy(
check_pc: bool = True,
raise_if_not: bool = False,
) -> bool:
"""Check whether an indexer is compatible with kikuchipy or raise a
``ValueError`` if it is not.
"""Check whether an indexer is compatible with kikuchipy.
A ``ValueError`` is raised if it is not.
Parameters
----------
Expand All @@ -350,8 +359,8 @@ def _indexer_is_compatible_with_kikuchipy(
check_pc
Whether to check ``indexer.PC`` (default is ``True``).
raise_if_not
Whether to raise a ``ValueError`` if the indexer is not
compatible with the signal. Default is ``False``.
Whether to raise a ``ValueError`` if the indexer is incompatible
with the signal. Default is ``False``.
Returns
-------
Expand Down Expand Up @@ -390,18 +399,93 @@ def _indexer_is_compatible_with_kikuchipy(
f"{pc_shape} instead."
)

# phase_list_pei = indexer.phaselist
# allowed_lists = [["FCC"], ["BCC"], ["FCC", "BCC"], ["BCC", "FCC"]]
# if compatible and phase_list_pei not in allowed_lists:
# compatible = False
# error_msg = f"`indexer.phaselist` must be one of {allowed_lists}"

if raise_if_not and not compatible:
raise ValueError(error_msg)
else:
return compatible


def _phase_lists_are_compatible(
phase_list: PhaseList,
indexer,
raise_if_not: bool = False,
) -> bool:
"""Check whether phase lists made with orix and PyEBSDIndex are
compatible.
A ``ValueError`` is raised if they are not.
They are compatible if the lists have an equal number of phases in
the same order and if the corresponding phases have equal lattice
parameters (to the 12th decimal) and the same space group number.
Parameters
----------
phase_list
Phase list made with orix.
indexer : pyebsdindex.ebsd_index.EBSDIndexer
EBSD indexer with a phase list from PyEBSDIndex.
raise_if_not
Whether to raise a ``ValueError`` if the phase lists are
incompatible. Default is ``False``.
Returns
-------
compatible
Whether the phase lists are compatible.
Raises
------
ValueError
Raised if the phase lists are incompatible.
"""
compatible = True

phase_list_pei = indexer.phaselist

msg = (
f"`indexer.phaselist` {phase_list_pei} and the list determined from"
f" `phase_list` must be the same"
)

n_phases = phase_list.size
n_phases_pei = len(phase_list_pei)
if n_phases != n_phases_pei:
compatible = False
msg = (
f"`phase_list` and `indexer.phaselist` have unequal lengths {n_phases} and "
f"{n_phases_pei}"
)
else:
for (_, phase), phase_pei in zip(phase_list, phase_list_pei):
lat = phase.structure.lattice.abcABG()
lat_pei = phase_pei.latticeparameter
sg = phase.space_group.number
sg_pei = phase_pei.spacegroup

if not np.allclose(lat, lat_pei, atol=1e-12):
compatible = False
msg = (
f"Phase '{phase.name}' in `phase_list` and {phase_pei} in "
f"`indexer.phaselist` have unequal lattice parameters {lat} and "
f"{lat_pei}"
)
break
elif sg != sg_pei:
compatible = False
msg = (
f"Phase '{phase.name}' in `phase_list` and {phase_pei} in "
f"`indexer.phaselist` have unequal space group numbers {sg} and "
f"{sg_pei}"
)
break

if raise_if_not and not compatible:
raise ValueError(msg)
else:
return compatible


def _get_info_message(nav_size: int, chunksize: int, indexer: "EBSDIndexer") -> str:
from kikuchipy import _pyopencl_context_available

Expand Down
16 changes: 5 additions & 11 deletions kikuchipy/signals/ebsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
from kikuchipy.filters.window import Window
from kikuchipy.indexing._dictionary_indexing import _dictionary_indexing
from kikuchipy.indexing._hough_indexing import (
_get_pyebsdindex_phaselist,
_indexer_is_compatible_with_kikuchipy,
_hough_indexing,
_optimize_pc,
_phase_lists_are_compatible,
)
from kikuchipy.indexing._refinement._refinement import (
_refine_orientation,
Expand Down Expand Up @@ -1606,9 +1606,8 @@ def hough_indexing(
Parameters
----------
phase_list
List of phases. The list can only contain one face-centered
cubic (FCC) phase, one body-centered cubic (BCC) phase or
both types.
List of phases. The list must correspond to the phase list
in the passed.
indexer
PyEBSDIndex EBSD indexer instance of which the
:meth:`~pyebsdindex.ebsd_index.EBSDIndexer.index_pats`
Expand Down Expand Up @@ -1675,15 +1674,10 @@ def hough_indexing(
step_sizes = tuple([a.scale for a in am.navigation_axes[::-1]])

# Check indexer (but not the reflectors)
phase_list_pei = _get_pyebsdindex_phaselist(phase_list)
_ = _indexer_is_compatible_with_kikuchipy(
indexer, sig_shape, nav_size, raise_if_not=True
)
# if indexer.phaselist != phase_list_pei:
# raise ValueError(
# f"`indexer.phaselist` {indexer.phaselist} and the list determined from"
# f" `phase_list` {phase_list_pei} must be the same"
# )
_ = _phase_lists_are_compatible(phase_list, indexer, raise_if_not=True)

# Prepare patterns
chunksize = min(chunksize, max(am.navigation_size, 1))
Expand Down Expand Up @@ -1724,7 +1718,7 @@ def hough_indexing_optimize_pc(
pattern optimized using Hough indexing from :mod:`pyebsdindex`.
See :class:`~pyebsdindex.ebsd_index.EBSDIndexer` and
:mod:`~pyebsdindex.pcopt.optimize` for details.
:mod:`~pyebsdindex.pcopt` for details.
Parameters
----------
Expand Down
Loading

0 comments on commit 8c9b16a

Please sign in to comment.