Example #1
0
    def test_merging_refined_maps(self):
        ny, nx = (3, 3)
        nav_size = ny * nx
        r = Rotation.from_euler(np.ones((nav_size, 3)))
        x = np.tile(np.arange(ny), nx)
        y = np.repeat(np.arange(nx), ny)

        # Simulation indices
        n_sim_indices = 10
        sim_indices1 = np.random.randint(low=0,
                                         high=1000,
                                         size=n_sim_indices *
                                         nav_size).reshape(
                                             (nav_size, n_sim_indices))
        sim_indices2 = np.random.randint(low=0,
                                         high=1000,
                                         size=n_sim_indices *
                                         nav_size).reshape(
                                             (nav_size, n_sim_indices))

        # Scores
        scores1 = np.ones(nav_size)
        scores1[0] = 3
        scores2 = 2 * np.ones(nav_size)

        xmap1 = CrystalMap(
            rotations=r,
            phase_id=np.ones(nav_size) * 0,
            phase_list=PhaseList(Phase(name="a")),
            x=x,
            y=y,
            prop={
                "simulation_indices": sim_indices1,
                "scores": scores1
            },
        )
        xmap2 = CrystalMap(
            rotations=r,
            phase_id=np.ones(nav_size),
            phase_list=PhaseList(Phase(name="b")),
            x=x,
            y=y,
            prop={
                "simulation_indices": sim_indices2,
                "scores": scores2
            },
        )
        xmap_merged = merge_crystal_maps(crystal_maps=[xmap1, xmap2])

        assert "simulation_indices" not in xmap_merged.prop.keys()
        assert "merged_simulation_indices" not in xmap_merged.prop.keys()

        with pytest.raises(ValueError, match="Cannot merge maps with more"):
            _ = merge_crystal_maps(
                crystal_maps=[xmap1, xmap2],
                simulation_indices_prop="simulation_indices",
            )
Example #2
0
    def test_phase2dict_spacegroup(self):
        """Space group is written to dict as an int or "None"."""
        sg100 = 100
        phase = Phase(space_group=sg100)
        phase_dict1 = phase2dict(phase)
        assert phase_dict1["space_group"] == sg100

        sg200 = GetSpaceGroup(200)
        phase.space_group = sg200
        phase_dict2 = phase2dict(phase)
        assert phase_dict2["space_group"] == sg200.number

        phase.space_group = None
        phase_dict3 = phase2dict(phase)
        assert phase_dict3["space_group"] == "None"
Example #3
0
 def test_symmetrise(self):
     assert np.allclose(
         ReciprocalLatticePoint(phase=Phase(space_group=225), hkl=[1, 1, 1])
         .symmetrise()
         .hkl.data,
         np.array(
             [
                 [1, 1, 1],
                 [-1, 1, 1],
                 [-1, -1, 1],
                 [1, -1, 1],
                 [1, -1, -1],
                 [1, 1, -1],
                 [-1, 1, -1],
                 [-1, -1, -1],
             ]
         ),
     )
     rlp2, multiplicity = ReciprocalLatticePoint(
         phase=Phase(space_group=186), hkl=[2, 2, 0]
     ).symmetrise(return_multiplicity=True)
     assert multiplicity == 12
     assert np.allclose(
         rlp2.hkl.data,
         [
             [2, 2, 0],
             [-2, 0, 0],
             [0, -2, 0],
             [-2, -2, 0],
             [2, 0, 0],
             [0, 2, 0],
             [-2, 2, 0],
             [0, -2, 0],
             [2, 0, 0],
             [2, -2, 0],
             [0, 2, 0],
             [-2, 0, 0],
         ],
     )
     rlp3 = ReciprocalLatticePoint(
         phase=Phase(space_group=186), hkl=[2, 2, 0]
     ).symmetrise(antipodal=False)
     assert np.allclose(
         rlp3.hkl.data,
         [[2, 2, 0], [-2, 0, 0], [0, -2, 0], [-2, -2, 0], [2, 0, 0], [0, 2, 0]],
     )
Example #4
0
def nickel_phase():
    return Phase(
        name="nickel",
        space_group=225,
        structure=Structure(
            lattice=Lattice(3.5236, 3.5236, 3.5236, 90, 90, 90),
            atoms=[Atom(xyz=[0, 0, 0], atype="Ni", Uisoequiv=0.006332)],
        ),
    )
 def __init__(self, *args, **kwargs):
     """Create an :class:`~kikuchipy.signals.EBSDMasterPattern`
     object from a :class:`hyperspy.signals.Signal2D` or a
     :class:`numpy.ndarray`.
     """
     Signal2D.__init__(self, *args, **kwargs)
     self.phase = kwargs.pop("phase", Phase())
     self.projection = kwargs.pop("projection", None)
     self.hemisphere = kwargs.pop("hemisphere", None)
Example #6
0
 def test_allowed_b_centering(self):
     """B centering will never fire since no diffpy.structure space group has
     'B' first in the name.
     """
     phase = Phase(space_group=15)
     phase.space_group.short_name = "B"
     rlp = ReciprocalLatticePoint(
         phase=phase, hkl=[[1, 2, 2], [1, 1, -1], [1, 1, 2]]
     )
     assert np.allclose(rlp.allowed, [False, True, False])
Example #7
0
    def test_dict2phase_spacegroup(self):
        """Space group number int or None is properly parsed from a dict.
        """
        phase1 = Phase(space_group=200)
        phase_dict = phase2dict(phase1)
        phase2 = dict2phase(phase_dict)
        assert phase1.space_group.number == phase2.space_group.number

        phase_dict.pop("space_group")
        phase3 = dict2phase(phase_dict)
        assert phase3.space_group is None
 def __init__(self, *args, **kwargs):
     """Create an :class:`~kikuchipy.signals.EBSDMasterPattern`
     instance from a :class:`hyperspy.signals.Signal2D` or a
     :class:`numpy.ndarray`. See the docstring of
     :class:`hyperspy.signal.BaseSignal` for optional input
     parameters.
     """
     Signal2D.__init__(self, *args, **kwargs)
     self.phase = kwargs.pop("phase", Phase())
     self.projection = kwargs.pop("projection", None)
     self.hemisphere = kwargs.pop("hemisphere", None)
Example #9
0
def ferrite_phase():
    return Phase(
        name="ferrite",
        space_group=229,
        structure=Structure(
            lattice=Lattice(2.8665, 2.8665, 2.8665, 90, 90, 90),
            atoms=[
                Atom(xyz=[0, 0, 0], atype="Fe", Uisoequiv=0.006332),
                Atom(xyz=[0.5, 0.5, 0.5], atype="Fe", Uisoequiv=0.006332),
            ],
        ),
    )
Example #10
0
    def test_init_no_metadata(self):
        s = EBSDMasterPattern(
            np.zeros((2, 10, 11, 11)),
            projection="lambert",
            hemisphere="both",
            phase=Phase("a"),
        )

        assert isinstance(s.phase, Phase)
        assert s.phase.name == "a"
        assert s.projection == "lambert"
        assert s.hemisphere == "both"
Example #11
0
def silicon_carbide_phase():
    """Silicon Carbide 4H polytype (hexagonal, space group 186)."""
    return Phase(
        space_group=186,
        structure=Structure(
            lattice=Lattice(3.073, 3.073, 10.053, 90, 90, 120),
            atoms=[
                Atom(atype="Si", xyz=[0, 0, 0]),
                Atom(atype="Si", xyz=[0.33, 0.667, 0.25]),
                Atom(atype="C", xyz=[0, 0, 0.188]),
                Atom(atype="C", xyz=[0.333, 0.667, 0.438]),
            ],
        ),
    )
Example #12
0
    def test_extra_phases(self, crystal_map, tmp_path, extra_phase_names):
        crystal_map.phases.add_not_indexed()
        for i, name in enumerate(extra_phase_names):
            crystal_map.phases.add(Phase(name=name))
            crystal_map[i].phase_id = crystal_map.phases.id_from_name(name)
        fname = tmp_path / "test_extra_phases.ang"
        save(fname, crystal_map)
        xmap_reload = load(fname)

        crystal_map.phases[0].name = "phase1"
        assert np.allclose(xmap_reload.phase_id - 1, crystal_map.phase_id)

        pl = crystal_map.phases
        del pl[-1]
        assert xmap_reload.phases.names == pl.names
Example #13
0
    def __init__(
        self,
        detector: EBSDDetector,
        phase: Phase,
        rotations: Rotation,
    ):
        """A generator storing necessary parameters to simulate
        geometrical EBSD patterns.

        Parameters
        ----------
        detector
            Detector describing the detector-sample geometry.
        phase
            A phase container with a crystal structure and a space and
            point group describing the allowed symmetry operations.
        rotations
            Unit cell rotations to simulate patterns for. The
            navigation shape of the resulting simulation is determined
            from the rotations' shape, with a maximum dimension of 2.

        Examples
        --------
        >>> from orix.crystal_map import Phase
        >>> from orix.quaternion import Rotation
        >>> from kikuchipy.detectors import EBSDDetector
        >>> from kikuchipy.generators import EBSDSimulationGenerator
        >>> det = EBSDDetector(
        ...     shape=(60, 60), sample_tilt=70, pc=[0.5,] * 3
        ... )
        >>> p = Phase(name="ni", space_group=225)
        >>> p.structure.lattice.setLatPar(3.52, 3.52, 3.52, 90, 90, 90)
        >>> simgen = EBSDSimulationGenerator(
        ...     detector=det,
        ...     phase=p,
        ...     rotations=Rotation.from_euler([90, 45, 90])
        ... )
        >>> simgen
        EBSDSimulationGenerator (1,)
        EBSDDetector (60, 60), px_size 1 um, binning 1, tilt 0, pc
        (0.5, 0.5, 0.5)
        <name: . space group: None. point group: None. proper point
        group: None. color: tab:blue>
        Rotation (1,)
        """
        self.detector = detector.deepcopy()
        self.phase = phase.deepcopy()
        self.rotations = deepcopy(rotations)
Example #14
0
def dict2phase(dictionary):
    """Get a :class:`~orix.crystal_map.phase_list.Phase` object from a
    dictionary.

    Parameters
    ----------
    dictionary : dict
        Dictionary with phase information.

    Returns
    -------
    Phase
    """
    dictionary = copy.deepcopy(dictionary)
    structure = dict2structure(dictionary["structure"])
    structure.title = dictionary["name"]
    # TODO: Remove this check in v0.6.0, since space_group was introduced in v0.4.0
    try:
        space_group = dictionary["space_group"]  # Either "None" or int
    except KeyError:  # v0.3.0
        space_group = "None"
    if space_group == "None":
        space_group = None
    else:
        space_group = int(space_group)
    # TODO: Remove this check in v0.6.0, since name change was introduced in v0.4.0
    try:
        point_group = dictionary["point_group"]
    except KeyError:  # v0.3.0
        point_group = dictionary["symmetry"]
    if point_group == "None":
        point_group = None
    return Phase(
        name=dictionary["name"],
        space_group=space_group,
        point_group=point_group,
        structure=structure,
        color=dictionary["color"],
    )
Example #15
0
 def _get_single_phase_xmap(
     nav_shape,
     rotations_per_point=5,
     prop_names=["scores", "simulation_indices"],
     name="a",
     phase_id=0,
 ):
     d, map_size = _get_spatial_array_dicts(nav_shape)
     rot_idx = np.random.choice(np.arange(rotations.size),
                                map_size * rotations_per_point)
     data_shape = (map_size, )
     if rotations_per_point > 1:
         data_shape += (rotations_per_point, )
     d["rotations"] = rotations[rot_idx].reshape(*data_shape)
     d["phase_id"] = np.ones(map_size) * phase_id
     d["phase_list"] = PhaseList(Phase(name=name))
     # Scores and simulation indices
     d["prop"] = {
         prop_names[0]: np.ones(data_shape, dtype=np.float32),
         prop_names[1]: np.arange(np.prod(data_shape)).reshape(data_shape),
     }
     return CrystalMap(**d)
Example #16
0
def _crystaldata2phase(dictionary: dict) -> Phase:
    """Return a :class:`~orix.crystal_map.Phase` object from a
    dictionary with EMsoft CrystalData group content.

    Parameters
    ----------
    dictionary
        Dictionary with phase information.

    Returns
    -------
    Phase
    """
    # TODO: Move this to orix.io.plugins.emsoft_h5ebsd as part of v0.6
    # Get list of atoms
    n_atoms = dictionary["Natomtypes"]
    atom_data = dictionary["AtomData"]
    atom_types = dictionary["Atomtypes"]
    if n_atoms == 1:
        atom_types = (atom_types, )  # Make iterable
    atoms = []
    for i in range(n_atoms):
        # TODO: Convert atom type integer to element name, like Ni for 26
        atoms.append(
            Atom(
                atype=atom_types[i],
                xyz=atom_data[:3, i],
                occupancy=atom_data[3, i],
                Uisoequiv=atom_data[4, i] / (8 * np.pi**2) * 1e2,  # Å^-2
            ))

    # TODO: Use space group setting
    return Phase(
        space_group=int(dictionary["SpaceGroupNumber"]),
        structure=Structure(
            lattice=Lattice(*dictionary["LatticeParameters"].T), atoms=atoms),
    )
Example #17
0
    def test_check_master_pattern_and_get_data(self):
        axes = [
            dict(name="hemisphere", size=2, scale=1),
            dict(name="energy", size=5, offset=16, scale=1),
            dict(name="dy", size=5, scale=1),
            dict(name="dx", size=5, scale=1),
        ]
        mp_data = np.random.rand(2, 5, 5, 5).astype(np.float64)
        mp = kp.signals.EBSDMasterPattern(
            mp_data,
            axes=axes,
            projection="lambert",
            hemisphere="both",
            phase=Phase("ni", 225),
        )
        (
            mpn,
            mps,
            npx,
            npy,
            scale,
        ) = kp.indexing._refinement._refinement._check_master_pattern_and_get_data(
            master_pattern=mp,
            energy=20,
        )

        assert npx == mp_data.shape[3]
        assert npy == mp_data.shape[2]
        assert scale == (mp_data.shape[3] - 1) / 2

        # Master patterns are rescaled
        assert mpn.dtype == np.float32
        assert not np.allclose(mpn, mp_data[0, -1])
        assert not np.allclose(mps, mp_data[1, -1])
        assert np.min(mpn) >= -1
        assert np.max(mpn) <= 1
Example #18
0
    def test_get_patterns(self):
        emsoft_key = load(EMSOFT_EBSD_FILE)
        emsoft_key = emsoft_key.data[0]

        r = Rotation.from_euler(np.radians([120, 45, 60]))
        mp1 = nickel_ebsd_master_pattern_small(projection="lambert",
                                               hemisphere="both")
        kp_pattern = mp1.get_patterns(rotations=r,
                                      detector=self.detector,
                                      energy=20,
                                      dtype_out=emsoft_key.dtype)
        kp_pat = kp_pattern.data[0].compute()
        assert kp_pat.dtype == emsoft_key.dtype

        ncc = NormalizedCrossCorrelationMetric(1, 1)
        ncc1 = ncc(kp_pat, emsoft_key)
        assert ncc1 >= 0.935

        ndp = NormalizedDotProductMetric(1, 1)
        ndp1 = ndp(kp_pat, emsoft_key)
        assert ndp1 >= 0.935

        detector_shape = self.detector.shape
        r2 = Rotation.from_euler(((0, 0, 0), (1, 1, 1), (2, 2, 2)))
        mp2 = kp.signals.EBSDMasterPattern(np.zeros((2, 10, 11, 11)))
        mp2.axes_manager[0].name = "hemisphere"
        mp2.axes_manager[1].name = "energy"
        mp2.projection = "lambert"
        mp2.phase = Phase("Ni", 225)
        out2 = mp2.get_patterns(r2, self.detector, 5)
        assert isinstance(out2, kp.signals.LazyEBSD)
        desired_data_shape = (3, ) + detector_shape[::-1]
        assert out2.axes_manager.shape == desired_data_shape

        mp3 = kp.signals.EBSDMasterPattern(np.zeros((10, 11, 11)))
        mp3.axes_manager[0].name = "energy"
        mp3.projection = "lambert"
        mp3.phase = Phase("Ni", 225)
        out3 = mp3.get_patterns(r2, self.detector, 5)
        assert isinstance(out3, kp.signals.LazyEBSD)
        assert out3.axes_manager.shape == desired_data_shape

        mp4 = kp.signals.EBSDMasterPattern(np.zeros((11, 11)))
        mp4.projection = "lambert"
        mp4.phase = Phase("Ni", 225)
        out41 = mp4.get_patterns(r2, self.detector, 5)
        out42 = mp4.get_patterns(r2, self.detector, 5, compute=True)

        assert isinstance(out41, kp.signals.LazyEBSD)
        assert isinstance(out42, kp.signals.EBSD)
        assert out41.axes_manager.shape == desired_data_shape

        mp5 = kp.signals.EBSDMasterPattern(np.zeros((11, 11)))
        mp5.projection = "lambert"
        mp5.phase = Phase("!Ni", 220)
        with pytest.raises(AttributeError):
            _ = mp5.get_patterns(r2, self.detector, 5)

        mp6 = kp.signals.EBSDMasterPattern(np.zeros((2, 11, 11)))
        with pytest.raises(AttributeError,
                           match="Master pattern `phase` attribute"):
            _ = mp6.get_patterns(r2, self.detector, 5)

        mp7 = kp.signals.EBSDMasterPattern(np.zeros((10, 11, 11)))
        mp7.axes_manager[0].name = "energy"
        mp7.projection = "lambert"
        mp7.phase = Phase("!Ni", 220)
        with pytest.raises(AttributeError,
                           match="For point groups without inversion"):
            _ = mp7.get_patterns(r2, self.detector, 5)

        # More than one PC is currently not supported so should fail
        d2 = kp.detectors.EBSDDetector(
            shape=(10, 10),
            px_size=50,
            pc=((0, 0, 15000), (0, 0, 15000)),
            convention="emsoft4",
            sample_tilt=70,
        )
        with pytest.raises(NotImplementedError):
            _ = mp4.get_patterns(r2, d2, 5)
Example #19
0
    def test_get_patterns(self):
        # Ni Test
        EMSOFT_EBSD_FILE = os.path.join(
            DIR_PATH, "../../data/emsoft_ebsd/EBSD_TEST_Ni.h5")
        emsoft_key = load(EMSOFT_EBSD_FILE)
        emsoft_key = emsoft_key.data[0]

        angles = np.array((120, 45, 60))
        r = Rotation.from_euler(np.radians(angles))
        kp_mp = nickel_ebsd_master_pattern_small(projection="lambert",
                                                 hemisphere="both")
        kp_pattern = kp_mp.get_patterns(rotations=r,
                                        detector=self.detector,
                                        energy=20,
                                        dtype_out=np.uint8)
        kp_pat = kp_pattern.data[0].compute()

        ncc1 = ncc(kp_pat, emsoft_key)
        ndp1 = ndp(kp_pat, emsoft_key)

        assert ncc1 >= 0.935
        assert ndp1 >= 0.935

        detector_shape = self.detector.shape
        r2 = Rotation.from_euler(((0, 0, 0), (1, 1, 1), (2, 2, 2)))
        mp_a = EBSDMasterPattern(np.zeros((2, 10, 11, 11)))
        print(mp_a.axes_manager)
        mp_a.axes_manager[0].name = "hemisphere"
        mp_a.axes_manager[1].name = "energy"
        mp_a.projection = "lambert"
        mp_a.phase = Phase("Ni", 225)
        out_a = mp_a.get_patterns(r2, self.detector, 5)

        assert isinstance(out_a, LazyEBSD)
        desired_data_shape = (3, ) + detector_shape[::-1]
        assert out_a.axes_manager.shape == desired_data_shape

        mp_b = EBSDMasterPattern(np.zeros((10, 11, 11)))
        mp_b.axes_manager[0].name = "energy"
        mp_b.projection = "lambert"
        mp_b.phase = Phase("Ni", 225)
        out_b = mp_b.get_patterns(r2, self.detector, 5)

        assert isinstance(out_b, LazyEBSD)
        assert out_b.axes_manager.shape == desired_data_shape

        mp_c = EBSDMasterPattern(np.zeros((11, 11)))
        mp_c.projection = "lambert"
        mp_c.phase = Phase("Ni", 225)
        out_c = mp_c.get_patterns(r2, self.detector, 5)
        out_c_2 = mp_c.get_patterns(r2, self.detector, 5, compute=True)

        assert isinstance(out_c, LazyEBSD)
        assert isinstance(out_c_2, EBSD)
        assert out_c.axes_manager.shape == desired_data_shape

        mp_c2 = EBSDMasterPattern(np.zeros((11, 11)))
        mp_c2.projection = "lambert"
        mp_c2.phase = Phase("!Ni", 220)
        with pytest.raises(AttributeError):
            mp_c2.get_patterns(r2, self.detector, 5)

        mp_d = EBSDMasterPattern(np.zeros((2, 11, 11)))
        with pytest.raises(NotImplementedError):
            mp_d.get_patterns(r2, self.detector, 5)

        mp_e = EBSDMasterPattern(np.zeros((10, 11, 11)))
        mp_e.axes_manager[0].name = "energy"
        mp_e.projection = "lambert"
        mp_e.phase = Phase("!Ni", 220)
        with pytest.raises(AttributeError):
            mp_e.get_patterns(r2, self.detector, 5)

        # More than one Projection center is currently not supported so
        # it should fail
        d2 = EBSDDetector(
            shape=(10, 10),
            px_size=50,
            pc=((0, 0, 15000), (0, 0, 15000)),
            convention="emsoft4",
            tilt=0,
            sample_tilt=70,
        )
        with pytest.raises(NotImplementedError):
            mp_c.get_patterns(r2, d2, 5)
class EBSDMasterPattern(CommonImage, Signal2D):
    """Simulated Electron Backscatter Diffraction (EBSD) master pattern.

    This class extends HyperSpy's Signal2D class for EBSD master
    patterns. Methods inherited from HyperSpy can be found in the
    HyperSpy user guide. See the docstring of
    :class:`hyperspy.signal.BaseSignal` for a list of additional
    attributes.

    Attributes
    ----------
    projection : str
        Which projection the pattern is in, "stereographic" or
        "lambert".
    hemisphere : str
        Which hemisphere the data contains: "north", "south" or "both".
    phase : orix.crystal_map.phase_list.Phase
        Phase describing the crystal structure used in the master
        pattern simulation.
    """

    _signal_type = "EBSDMasterPattern"
    _alias_signal_types = ["ebsd_master_pattern", "master_pattern"]
    _lazy = False

    phase = Phase()
    projection = None
    hemisphere = None

    def __init__(self, *args, **kwargs):
        """Create an :class:`~kikuchipy.signals.EBSDMasterPattern`
        object from a :class:`hyperspy.signals.Signal2D` or a
        :class:`numpy.ndarray`.
        """
        Signal2D.__init__(self, *args, **kwargs)
        self.phase = kwargs.pop("phase", Phase())
        self.projection = kwargs.pop("projection", None)
        self.hemisphere = kwargs.pop("hemisphere", None)

    def get_patterns(
        self,
        rotations: Rotation,
        detector: EBSDDetector,
        energy: Union[int, float],
        dtype_out: type = np.float32,
        compute: bool = False,
        **kwargs,
    ) -> Union[EBSD, LazyEBSD]:
        """Return a dictionary of EBSD patterns projected onto a
        detector from a master pattern in the square Lambert
        projection :cite:`callahan2013dynamical`, for a set of crystal
        rotations relative to the EDAX TSL sample reference frame (RD,
        TD, ND) and a fixed detector-sample geometry.

        Parameters
        ----------
        rotations
            Set of crystal rotations to get patterns from. The shape of
            this object, a maximum of two dimensions, determines the
            navigation shape of the output signal.
        detector
            EBSD detector describing the detector dimensions and the
            detector-sample geometry with a single, fixed
            projection/pattern center.
        energy
            Acceleration voltage, in kV, used to simulate the desired
            master pattern to create a dictionary from.
        dtype_out
            Data type of the returned patterns, by default np.float32.
        compute
            Whether to return a lazy result, by default False. For more
            information see :func:`~dask.array.Array.compute`.
        kwargs
            Keyword arguments passed to
            :func:`~kikuchipy.signals.util.get_chunking` to control the
            number of chunks the dictionary creation and the output data
            array is split into. Only `chunk_shape`, `chunk_bytes` and
            `dtype_out` (to `dtype`) are passed on.

        Returns
        -------
        EBSD or LazyEBSD
            Signal with navigation and signal shape equal to the
            rotation object's and detector shape, respectively.

        Notes
        -----
        If the master pattern phase has a non-centrosymmetric point
        group, both the northern and southern hemispheres must be
        provided. For more details regarding the reference frame visit
        the reference frame user guide at:
        https://kikuchipy.org/en/latest/reference_frames.html.
        """
        if self.projection != "lambert":
            raise NotImplementedError(
                "Master pattern must be in the square Lambert projection"
            )
        if len(detector.pc) > 1:
            raise NotImplementedError(
                "Detector must have exactly one projection center"
            )

        # Get suitable chunks when iterating over the rotations. The
        # signal axes are not chunked.
        nav_shape = rotations.shape
        nav_dim = len(nav_shape)
        if nav_dim > 2:
            raise ValueError(
                "The rotations object can only have one or two dimensions, but "
                f"an object with {nav_dim} was passed"
            )
        data_shape = nav_shape + detector.shape
        chunks = get_chunking(
            data_shape=data_shape,
            nav_dim=nav_dim,
            sig_dim=len(detector.shape),
            chunk_shape=kwargs.pop("chunk_shape", None),
            chunk_bytes=kwargs.pop("chunk_bytes", None),
            dtype=dtype_out,
        )

        # Get the master pattern arrays created by a desired energy
        north_slice = ()
        if "energy" in [i.name for i in self.axes_manager.navigation_axes]:
            energies = self.axes_manager["energy"].axis
            north_slice += ((np.abs(energies - energy)).argmin(),)
        south_slice = north_slice
        if self.hemisphere == "both":
            north_slice = (0,) + north_slice
            south_slice = (1,) + south_slice
        elif not self.phase.point_group.contains_inversion:
            raise AttributeError(
                "For crystals of point groups without inversion symmetry, like "
                f"the current {self.phase.point_group.name}, both hemispheres "
                "must be present in the master pattern signal"
            )
        master_north = self.data[north_slice]
        master_south = self.data[south_slice]

        # Whether to rescale pattern intensities after projection
        rescale = False
        if dtype_out != np.float32:
            rescale = True

        # Get direction cosines for each detector pixel relative to the
        # source point
        dc = _get_direction_cosines(detector)

        # Get dask array from rotations
        r_da = da.from_array(rotations.data, chunks=chunks[:nav_dim] + (-1,))

        # Which axes to drop and add when iterating over the rotations
        # dask array to produce the EBSD signal array, i.e. drop the
        # (4,)-shape quaternion axis and add detector shape axes, e.g.
        # (60, 60)
        if nav_dim == 1:
            drop_axis = 1
            new_axis = (1, 2)
        else:  # nav_dim == 2
            drop_axis = 2
            new_axis = (2, 3)

        # Project simulated patterns onto detector
        npx, npy = self.axes_manager.signal_shape
        scale = (npx - 1) / 2
        simulated = r_da.map_blocks(
            _get_patterns_chunk,
            dc=dc,
            master_north=master_north,
            master_south=master_south,
            npx=npx,
            npy=npy,
            scale=scale,
            rescale=rescale,
            dtype_out=dtype_out,
            drop_axis=drop_axis,
            new_axis=new_axis,
            chunks=chunks,
            dtype=dtype_out,
        )

        # Add crystal map and detector to keyword arguments
        kwargs = dict(
            xmap=CrystalMap(
                phase_list=PhaseList(self.phase), rotations=rotations,
            ),
            detector=detector,
        )

        # Specify navigation and signal axes for signal initialization
        names = ["y", "x", "dy", "dx"]
        scales = np.ones(4)
        ndim = simulated.ndim
        if ndim == 3:
            names = names[1:]
            scales = scales[1:]
        axes = [
            dict(
                size=data_shape[i],
                index_in_array=i,
                name=names[i],
                scale=scales[i],
                offset=0.0,
                units="px",
            )
            for i in range(ndim)
        ]

        if compute:
            with ProgressBar():
                print(
                    f"Creating a dictionary of {nav_shape} simulated patterns:",
                    file=sys.stdout,
                )
                patterns = simulated.compute()
            out = EBSD(patterns, axes=axes, **kwargs)
        else:
            out = LazyEBSD(simulated, axes=axes, **kwargs)

        return out
Example #21
0
def nickel_phase(nickel_structure):
    """A orix.crystal_map.Phase with a Nickel crystal structure and
    symmetry operations.
    """
    return Phase(name="ni", structure=nickel_structure, space_group=225)
class TestCrystallographicComputations:
    @pytest.mark.parametrize(
        "hkl, desired_uvw",
        [
            ([1, 1, 1], np.array([]).reshape((0, 3))),
            ([[1, 1, 1], [2, 2, 2]], np.array([]).reshape((0, 3))),
            ([[1, 1, 1], [1, 1, -1]], [[-1, 1, 0], [1, -1, 0]]),
        ],
    )
    def test_get_uvw_from_hkl(self, hkl, desired_uvw):
        """Desired uvw from the cross product of hkl."""
        assert np.allclose(_get_uvw_from_hkl(hkl), desired_uvw)

    @pytest.mark.parametrize(
        ("hkl, desired_family_keys, desired_family_values, desired_indices, "
         "reduce"),
        [
            ([1, 1, 1], [[1, 1, 1]], [1, 1, 1], [0], False),
            ([1, 1, 1], [[1, 1, 1]], [1, 1, 1], [0], True),
            (
                [[1, 1, 1], [2, 0, 0]],
                [[1, 1, 1], [2, 0, 0]],
                [[[1, 1, 1]], [[2, 0, 0]]],
                [[0], [1]],
                False,
            ),
            (
                ReciprocalLatticePoint(phase=Phase(space_group=225),
                                       hkl=[1, 1, 1]).symmetrise().hkl.data,
                [1, 1, 1],
                [[
                    [1, 1, 1],
                    [-1, 1, 1],
                    [-1, -1, 1],
                    [1, -1, 1],
                    [1, -1, -1],
                    [1, 1, -1],
                    [-1, 1, -1],
                    [-1, -1, -1],
                ]],
                [np.arange(8)],
                False,
            ),
            (
                ReciprocalLatticePoint(phase=Phase(space_group=225),
                                       hkl=[[1, 1, 1], [2, 0, 0]
                                            ]).symmetrise().hkl.data,
                [[1, 1, 1], [2, 0, 0]],
                [
                    [
                        [1, 1, 1],
                        [-1, 1, 1],
                        [-1, -1, 1],
                        [1, -1, 1],
                        [1, -1, -1],
                        [1, 1, -1],
                        [-1, 1, -1],
                        [-1, -1, -1],
                    ],
                    [
                        [2, 0, 0],
                        [0, 2, 0],
                        [-2, 0, 0],
                        [0, -2, 0],
                        [0, 0, 2],
                        [0, 0, -2],
                    ],
                ],
                [np.arange(8).tolist(),
                 np.arange(8, 14).tolist()],
                False,
            ),
            (
                ReciprocalLatticePoint(phase=Phase(space_group=225),
                                       hkl=[[1, 1, 1], [2, 2, 2]
                                            ]).symmetrise().hkl.data,
                [1, 1, 1],
                [[
                    [1, 1, 1],
                    [-1, 1, 1],
                    [-1, -1, 1],
                    [1, -1, 1],
                    [1, -1, -1],
                    [1, 1, -1],
                    [-1, 1, -1],
                    [-1, -1, -1],
                    [2, 2, 2],
                    [-2, 2, 2],
                    [-2, -2, 2],
                    [2, -2, 2],
                    [2, -2, -2],
                    [2, 2, -2],
                    [-2, 2, -2],
                    [-2, -2, -2],
                ]],
                [np.arange(16)],
                True,
            ),
        ],
    )
    def test_get_hkl_family(self, hkl, desired_family_keys,
                            desired_family_values, desired_indices, reduce):
        """Desired sets of families and indices."""
        families, families_idx = _get_hkl_family(hkl, reduce=reduce)

        for i, (k, v) in enumerate(families.items()):
            assert np.allclose(k, desired_family_keys[i])
            assert np.allclose(v, desired_family_values[i])
            assert np.allclose(families_idx[k], desired_indices[i])

    @pytest.mark.parametrize(
        "highest_hkl, color_cycle, desired_hkl_colors",
        [
            ([1, 1, 1], ["C0", "C1"], [[1, 1, 1], [0.12, 0.47, 0.71]]),
            (
                [2, 2, 2],
                ["g", "b"],
                [
                    [[1, 1, 1], [0, 0.5, 0]],
                    [[2, 0, 0], [0, 0, 1]],
                    [[2, 2, 0], [0, 0.5, 0]],
                    [[2, 2, 2], [0, 0.5, 0]],
                ],
            ),
            (
                [2, 2, 2],
                None,
                [
                    [[1, 1, 1], [1, 0, 0]],
                    [[2, 0, 0], [1, 1, 0]],
                    [[2, 2, 0], [0, 1, 0]],
                    [[2, 2, 2], [1, 0, 0]],
                ],
            ),
        ],
    )
    def test_get_colors_for_allowed_bands(self, nickel_phase, highest_hkl,
                                          color_cycle, desired_hkl_colors):
        """Desired colors for bands."""
        hkl_colors = _get_colors_for_allowed_bands(phase=nickel_phase,
                                                   highest_hkl=highest_hkl,
                                                   color_cycle=color_cycle)

        assert np.allclose(hkl_colors, desired_hkl_colors, atol=1e-2)

    def test_get_colors_for_allowed_bands_999(self, nickel_phase):
        """Not passing `highest_hkl` works fine."""
        hkl_colors = _get_colors_for_allowed_bands(phase=nickel_phase)

        assert np.shape(hkl_colors) == (69, 2, 3)
Example #23
0
    def test_get_allowed_without_space_group_raises(self):
        phase = Phase(point_group="432")
        rlp = ReciprocalLatticePoint(phase=phase, hkl=[1, 1, 1])

        with pytest.raises(ValueError, match=f"The phase {phase} must have a"):
            _ = rlp.allowed
Example #24
0
 def test_init_without_point_group_raises(self):
     phase = Phase()
     with pytest.raises(ValueError, match=f"The phase {phase} must have a"):
         _ = ReciprocalLatticePoint(phase=phase, hkl=[1, 1, 1])
class EBSDMasterPattern(CommonImage, Signal2D):
    """Simulated Electron Backscatter Diffraction (EBSD) master pattern.

    This class extends HyperSpy's Signal2D class for EBSD master
    patterns. Methods inherited from HyperSpy can be found in the
    HyperSpy user guide. See the docstring of
    :class:`hyperspy.signal.BaseSignal` for a list of additional
    attributes.

    Attributes
    ----------
    projection : str
        Which projection the pattern is in, "stereographic" or
        "lambert".
    hemisphere : str
        Which hemisphere the data contains: "north", "south" or "both".
    phase : orix.crystal_map.phase_list.Phase
        Phase describing the crystal structure used in the master
        pattern simulation.
    """

    _signal_type = "EBSDMasterPattern"
    _alias_signal_types = ["ebsd_master_pattern", "master_pattern"]
    _lazy = False

    # ---------------------- Custom properties ----------------------- #

    phase = Phase()
    projection = None
    hemisphere = None

    def __init__(self, *args, **kwargs):
        """Create an :class:`~kikuchipy.signals.EBSDMasterPattern`
        instance from a :class:`hyperspy.signals.Signal2D` or a
        :class:`numpy.ndarray`. See the docstring of
        :class:`hyperspy.signal.BaseSignal` for optional input
        parameters.
        """
        Signal2D.__init__(self, *args, **kwargs)
        self.phase = kwargs.pop("phase", Phase())
        self.projection = kwargs.pop("projection", None)
        self.hemisphere = kwargs.pop("hemisphere", None)

    def get_patterns(
        self,
        rotations: Rotation,
        detector: EBSDDetector,
        energy: Union[int, float],
        dtype_out: Union[type, np.dtype] = np.float32,
        compute: bool = False,
        **kwargs,
    ) -> Union[EBSD, LazyEBSD]:
        """Return a dictionary of EBSD patterns projected onto a
        detector from a master pattern in the square Lambert
        projection :cite:`callahan2013dynamical`, for a set of crystal
        rotations relative to the EDAX TSL sample reference frame (RD,
        TD, ND) and a fixed detector-sample geometry.

        Parameters
        ----------
        rotations
            Crystal rotations to get patterns from. The shape of this
            instance, a maximum of two dimensions, determines the
            navigation shape of the output signal.
        detector
            EBSD detector describing the detector dimensions and the
            detector-sample geometry with a single, fixed
            projection/pattern center.
        energy
            Acceleration voltage, in kV, used to simulate the desired
            master pattern to create a dictionary from. If only a single
            energy is present in the signal, this will be returned no
            matter its energy.
        dtype_out
            Data type of the returned patterns, by default np.float32.
        compute
            Whether to return a lazy result, by default False. For more
            information see :func:`~dask.array.Array.compute`.
        kwargs
            Keyword arguments passed to
            :func:`~kikuchipy.signals.util.get_chunking` to control the
            number of chunks the dictionary creation and the output data
            array is split into. Only `chunk_shape`, `chunk_bytes` and
            `dtype_out` (to `dtype`) are passed on.

        Returns
        -------
        EBSD or LazyEBSD
            Signal with navigation and signal shape equal to the
            rotation instance and detector shape, respectively.

        Notes
        -----
        If the master pattern phase has a non-centrosymmetric point
        group, both the northern and southern hemispheres must be
        provided. For more details regarding the reference frame visit
        the reference frame user guide.
        """
        self._is_suitable_for_projection(raise_if_not=True)

        if len(detector.pc) > 1:
            raise NotImplementedError(
                "Detector must have exactly one projection center")

        # Get suitable chunks when iterating over the rotations. Signal
        # axes are not chunked.
        nav_shape = rotations.shape
        nav_dim = len(nav_shape)
        if nav_dim > 2:
            raise ValueError(
                "`rotations` can only have one or two dimensions, but an instance with "
                f"{nav_dim} dimensions was passed")
        data_shape = nav_shape + detector.shape
        chunks = get_chunking(
            data_shape=data_shape,
            nav_dim=nav_dim,
            sig_dim=len(detector.shape),
            chunk_shape=kwargs.pop("chunk_shape", None),
            chunk_bytes=kwargs.pop("chunk_bytes", None),
            dtype=dtype_out,
        )

        # Whether to rescale pattern intensities after projection
        if dtype_out != self.data.dtype:
            rescale = True
            if isinstance(dtype_out, np.dtype):
                dtype_out = dtype_out.type
            out_min, out_max = dtype_range[dtype_out]
        else:
            rescale = False
            # Cannot be None due to Numba, so they are set to something
            # here. Values aren't used unless `rescale` is True.
            out_min, out_max = 1, 2

        # Get direction cosines for each detector pixel relative to the
        # source point
        direction_cosines = _get_direction_cosines_for_single_pc_from_detector(
            detector)

        # Get dask array from rotations
        rot_da = da.from_array(rotations.data,
                               chunks=chunks[:nav_dim] + (-1, ))

        # Which axes to drop and add when iterating over the rotations
        # dask array to produce the EBSD signal array, i.e. drop the
        # (4,)-shape quaternion axis and add detector shape axes, e.g.
        # (60, 60)
        if nav_dim == 1:
            drop_axis = 1
            new_axis = (1, 2)
        else:  # nav_dim == 2
            drop_axis = 2
            new_axis = (2, 3)

        master_north, master_south = self._get_master_pattern_arrays_from_energy(
            energy)

        # Project simulated patterns onto detector
        npx, npy = self.axes_manager.signal_shape
        scale = (npx - 1) / 2
        # TODO: Use dask.delayed instead?
        simulated = rot_da.map_blocks(
            _project_patterns_from_master_pattern,
            direction_cosines=direction_cosines,
            master_north=master_north,
            master_south=master_south,
            npx=int(npx),
            npy=int(npy),
            scale=float(scale),
            dtype_out=dtype_out,
            rescale=rescale,
            out_min=out_min,
            out_max=out_max,
            drop_axis=drop_axis,
            new_axis=new_axis,
            chunks=chunks,
            dtype=dtype_out,
        )

        # Add crystal map and detector to keyword arguments
        kwargs = dict(
            xmap=CrystalMap(phase_list=PhaseList(self.phase),
                            rotations=rotations),
            detector=detector,
        )

        # Specify navigation and signal axes for signal initialization
        names = ["y", "x", "dy", "dx"]
        scales = np.ones(4)
        ndim = simulated.ndim
        if ndim == 3:
            names = names[1:]
            scales = scales[1:]
        axes = [
            dict(
                size=data_shape[i],
                index_in_array=i,
                name=names[i],
                scale=scales[i],
                offset=0.0,
                units="px",
            ) for i in range(ndim)
        ]

        if compute:
            patterns = np.zeros(shape=simulated.shape, dtype=simulated.dtype)
            with ProgressBar():
                print(
                    f"Creating a dictionary of {nav_shape} simulated patterns:",
                    file=sys.stdout,
                )
                simulated.store(patterns, compute=True)
            out = EBSD(patterns, axes=axes, **kwargs)
        else:
            out = LazyEBSD(simulated, axes=axes, **kwargs)
        gc.collect()

        return out

    # ------ Methods overwritten from hyperspy.signals.Signal2D ------ #

    def deepcopy(self):
        new = super().deepcopy()
        new.phase = self.phase.deepcopy()
        new.projection = copy.deepcopy(self.projection)
        new.hemisphere = copy.deepcopy(self.hemisphere)
        return new

    # ------------------------ Private methods ----------------------- #

    def _get_master_pattern_arrays_from_energy(
            self, energy: Union[int, float]) -> Tuple[np.ndarray, np.ndarray]:
        """Return northern and southern master patterns created with a
        single, given energy.

        Parameters
        ----------
        energy
            Acceleration voltage in kV. If only a single energy is
            present in the signal, this will be returned no matter its
            energy.

        Returns
        -------
        master_north, master_south
            Northern and southern hemispheres of master pattern.
        """
        if "energy" in [i.name for i in self.axes_manager.navigation_axes]:
            master_patterns = self.inav[float(energy)].data
        else:  # Assume a single energy
            master_patterns = self.data
        if self.hemisphere == "both":
            master_north, master_south = master_patterns
        else:
            master_north = master_south = master_patterns
        return master_north, master_south

    def _is_suitable_for_projection(self, raise_if_not: bool = False):
        """Check whether the master pattern is suitable for projection
        onto an EBSD detector and return a bool or raise an error
        message if desired.
        """
        suitable = True
        error = None
        if self.projection != "lambert":
            error = NotImplementedError(
                "Master pattern must be in the square Lambert projection")
            suitable = False
        pg = self.phase.point_group
        if pg is None:
            error = AttributeError(
                "Master pattern `phase` attribute must have a valid point group"
            )
            suitable = False
        elif self.hemisphere != "both" and not pg.contains_inversion:
            error = AttributeError(
                "For point groups without inversion symmetry, like the current "
                f"{pg.name}, both hemispheres must be present in the master pattern "
                "signal")
            suitable = False
        if not suitable and raise_if_not:
            raise error
        else:
            return suitable
Example #26
0
def file_reader(filename, refined=False, **kwargs):
    """Return a :class:`~orix.crystal_map.crystal_map.CrystalMap` object
    from a file in EMsoft's dictionary indexing dot product file format.

    Parameters
    ----------
    filename : str
        Path and file name.
    refined : bool, optional
        Whether to return refined orientations (default is False).
    kwargs
        Keyword arguments passed to :func:`h5py.File`.

    Returns
    -------
    CrystalMap
    """
    mode = kwargs.pop("mode", "r")
    f = File(filename, mode=mode, **kwargs)

    # Get groups for convenience
    ebsd_group = f["Scan 1/EBSD"]
    data_group = ebsd_group["Data"]
    header_group = ebsd_group["Header"]
    phase_group = header_group["Phase/1"]

    # Get map shape and step sizes
    ny = header_group["nRows"][:][0]
    nx = header_group["nColumns"][:][0]
    step_y = header_group["Step Y"][:][0]
    map_size = ny * nx

    # Some of the data needed to create a CrystalMap object
    phase_name, point_group, structure = _get_phase(phase_group)
    data_dict = {
        # Get map coordinates ("Y Position" data set is not correct in EMsoft as of
        # 2020-04, see:
        # https://github.com/EMsoft-org/EMsoft/blob/7762e1961508fe3e71d4702620764ceb98a78b9e/Source/EMsoftHDFLib/EMh5ebsd.f90#L1093)
        "x":
        data_group["X Position"][:],
        # y = data_group["Y Position"][:]
        "y":
        np.sort(np.tile(np.arange(ny) * step_y, nx)),
        # Get phase IDs
        "phase_id":
        data_group["Phase"][:],
        # Get phase name, point group and structure (lattice)
        "phase_list":
        PhaseList(
            Phase(name=phase_name,
                  point_group=point_group,
                  structure=structure)),
        "scan_unit":
        "um",
    }

    # Get rotations
    if refined:
        euler = data_group["RefinedEulerAngles"][:]  # Radians
    else:  # Get n top matches for each pixel
        top_match_idx = data_group["TopMatchIndices"][:][:map_size] - 1
        dictionary_size = data_group["FZcnt"][:][0]
        # Degrees
        dictionary_euler = data_group[
            "DictionaryEulerAngles"][:][:dictionary_size]
        dictionary_euler = np.deg2rad(dictionary_euler)
        euler = dictionary_euler[top_match_idx, :]
    data_dict["rotations"] = Rotation.from_euler(euler)

    # Get number of top matches kept per data point
    n_top_matches = f["NMLparameters/EBSDIndexingNameListType/nnk"][:][0]

    data_dict["prop"] = _get_properties(
        data_group=data_group,
        n_top_matches=n_top_matches,
        map_size=map_size,
    )

    f.close()

    return CrystalMap(**data_dict)
Example #27
0
 def test_allowed(self, space_group, hkl, centering, desired_allowed):
     rlp = ReciprocalLatticePoint(phase=Phase(space_group=space_group), hkl=hkl)
     assert rlp.phase.space_group.short_name[0] == centering
     assert np.allclose(rlp.allowed, desired_allowed)
Example #28
0
import numpy as np
from orix.crystal_map import Phase
import pytest

from diffsims.structure_factor import (
    find_asymmetric_positions,
    get_kinematical_structure_factor,
    get_doyleturner_structure_factor,
    get_refraction_corrected_wavelength,
)

# Debye-Waller factor in Å^-2: Biosequiv = 8 * np.pi ** 2 * Uisoequiv
nickel = Phase(
    space_group=225,
    structure=Structure(
        lattice=Lattice(3.5236, 3.5236, 3.5236, 90, 90, 90),
        atoms=[Atom(xyz=[0, 0, 0], atype="Ni", Uisoequiv=0.006332)],
    ),
)
ferrite = Phase(
    space_group=229,
    structure=Structure(
        lattice=Lattice(2.8665, 2.8665, 2.8665, 90, 90, 90),
        atoms=[
            Atom(xyz=[0, 0, 0], atype="Fe", Uisoequiv=0.006332),
            Atom(xyz=[0.5, 0.5, 0.5], atype="Fe", Uisoequiv=0.006332),
        ],
    ),
)
sic4h = Phase(
    space_group=186,