Exemple #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",
            )
Exemple #2
0
def compute_refine_orientation_projection_center_results(
    results: list,
    detector,
    xmap: CrystalMap,
    master_pattern,
):
    """Compute the results from
    :meth:`~kikuchipy.signals.EBSD.refine_orientation_projection_center`
    and return the :class:`~orix.crystal_map.CrystalMap` and
    :class:`~kikuchipy.detectors.EBSDDetector`.

    Parameters
    ----------
    results
        Results returned from `refine_orientation_projection_center()`,
        which is a list of :class:`~dask.delayed.Delayed`.
    detector : ~kikuchipy.detectors.EBSDDetector
        Detector passed to `refine_orientation_projection_center()` to
        obtain `results`.
    xmap
        Crystal map passed to `refine_orientation_projection_center()`
        to obtain `results`.
    master_pattern : ~kikuchipy.signals.EBSDMasterPattern
        Master pattern passed to
        `refine_orientation_projection_center()` to obtain `results`.

    Returns
    -------
    xmap_refined : :class:`~orix.crystal_map.CrystalMap`
        Crystal map with refined orientations and scores.
    new_detector : :class:`~kikuchipy.detectors.EBSDDetector`
        EBSD detector with refined projection center parameters.
    """
    n_patterns = len(results)
    nav_shape = xmap.shape
    with ProgressBar():
        print(
            f"Refining {n_patterns} orientation(s) and projection center(s):",
            file=sys.stdout,
        )
        computed_results = dask.compute(*results)
        computed_results = np.array(computed_results)
        # (n, score, phi1, Phi, phi2, PCx, PCy, PCz)
        xmap_refined = CrystalMap(
            rotations=Rotation.from_euler(computed_results[:, 1:4]),
            phase_id=np.zeros(n_patterns),
            x=xmap.x,
            y=xmap.y,
            phase_list=PhaseList(phases=master_pattern.phase),
            prop=dict(scores=computed_results[:, 0]),
            scan_unit=xmap.scan_unit,
        )
        new_detector = detector.deepcopy()
        new_detector.pc = computed_results[:, 4:].reshape(nav_shape + (3, ))
    return xmap_refined, new_detector
Exemple #3
0
def dict2phaselist(dictionary):
    """Get a :class:`~orix.crystal_map.phase_list.PhaseList` object from a
    dictionary.

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

    Returns
    -------
    PhaseList
    """
    dictionary = copy.deepcopy(dictionary)
    return PhaseList(phases={int(k): dict2phase(v) for k, v in dictionary.items()})
Exemple #4
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)
Exemple #5
0
def compute_refine_orientation_results(results: list, xmap: CrystalMap,
                                       master_pattern):
    """Compute the results from
    :meth:`~kikuchipy.signals.EBSD.refine_orientation` and return the
    :class:`~orix.crystal_map.CrystalMap`.

    Parameters
    ----------
    results
        Results returned from `refine_orientation()`, which is a list of
        :class:`~dask.delayed.Delayed`.
    xmap
        Crystal map passed to `refine_orientation()` to obtain
        `results`.
    master_pattern : ~kikuchipy.signals.EBSDMasterPattern
        Master pattern passed to `refine_orientation()` to obtain
        `results`.

    Returns
    -------
    refined_xmap : :class:`~orix.crystal_map.CrystalMap`
        Crystal map with refined orientations and scores.
    """
    n_patterns = len(results)
    with ProgressBar():
        print(f"Refining {n_patterns} orientation(s):", file=sys.stdout)
        computed_results = dask.compute(*results)
        # (n, score, phi1, Phi, phi2)
        computed_results = np.array(computed_results)
        xmap_refined = CrystalMap(
            rotations=Rotation.from_euler(computed_results[:, 1:]),
            phase_id=np.zeros(n_patterns),
            x=xmap.x,
            y=xmap.y,
            phase_list=PhaseList(phases=master_pattern.phase),
            prop=dict(scores=computed_results[:, 0]),
            scan_unit=xmap.scan_unit,
        )
    return xmap_refined
    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
Exemple #7
0
def file_reader(
    filename: str,
    scan_size: Union[None, int, Tuple[int, ...]] = None,
    lazy: bool = False,
    **kwargs,
) -> List[dict]:
    """Read dynamically simulated electron backscatter diffraction
    patterns from EMsoft's format produced by their EMEBSD.f90 program.

    Parameters
    ----------
    filename
        Full file path of the HDF file.
    scan_size
        Scan size in number of patterns in width and height.
    lazy
        Open the data lazily without actually reading the data from disk
        until requested. Allows opening datasets larger than available
        memory. Default is False.
    kwargs :
        Keyword arguments passed to h5py.File.

    Returns
    -------
    signal_dict_list: list of dicts
        Data, axes, metadata and original metadata.
    """
    mode = kwargs.pop("mode", "r")
    f = File(filename, mode=mode, **kwargs)

    _check_file_format(f)

    # Read original metadata
    omd = hdf5group2dict(f["/"],
                         data_dset_names=["EBSDPatterns"],
                         recursive=True)

    # Set metadata and original metadata dictionaries
    md = _get_metadata(omd)
    md.update({
        "Signal": {
            "signal_type": "EBSD",
            "record_by": "image"
        },
        "General": {
            "title": f.filename.split("/")[-1].split(".")[0],
            "original_filename": f.filename.split("/")[-1],
        },
    })
    scan = {"metadata": md, "original_metadata": omd}

    # Read patterns
    dataset = f["EMData/EBSD/EBSDPatterns"]
    if lazy:
        chunks = "auto" if dataset.chunks is None else dataset.chunks
        patterns = da.from_array(dataset, chunks=chunks)
    else:
        patterns = np.asanyarray(dataset)

    # Reshape data if desired
    sy = omd["NMLparameters"]["EBSDNameList"]["numsy"]
    sx = omd["NMLparameters"]["EBSDNameList"]["numsx"]
    if scan_size is not None:
        if isinstance(scan_size, int):
            new_shape = (scan_size, sy, sx)
        else:
            new_shape = scan_size + (sy, sx)
        patterns = patterns.reshape(new_shape)

    scan["data"] = patterns

    # Set navigation and signal axes
    pixel_size = omd["NMLparameters"]["EBSDNameList"]["delta"]
    ndim = patterns.ndim
    units = ["px", "um", "um"]
    names = ["x", "dy", "dx"]
    scales = np.array([1, pixel_size, pixel_size])
    if ndim == 4:
        units = ["px"] + units
        names = ["y"] + names
        scales = np.append([1], scales)
    scan["axes"] = [{
        "size": patterns.shape[i],
        "index_in_array": i,
        "name": names[i],
        "scale": scales[i],
        "offset": 0,
        "units": units[i],
    } for i in range(patterns.ndim)]

    # Get crystal map
    phase = _crystaldata2phase(hdf5group2dict(f["CrystalData"]))
    xtal_fname = f["EMData/EBSD/xtalname"][()][0].decode().split("/")[-1]
    phase.name, _ = os.path.splitext(xtal_fname)
    scan["xmap"] = CrystalMap(
        rotations=Rotation.from_euler(f["EMData/EBSD/EulerAngles"][()]),
        phase_list=PhaseList(phase),
    )

    if not lazy:
        f.close()

    return [scan]
Exemple #8
0
def file_reader(filename):
    """Return a :class:`~orix.crystal_map.crystal_map.CrystalMap` object
    from a file in EDAX TLS's .ang format. The map in the input is
    assumed to be 2D.

    Many vendors produce an .ang file. Supported vendors are
    * EDAX TSL

    * NanoMegas ASTAR Index

    * EMsoft (from program `EMdpmerge`)

    * orix

    All points satisfying the following criteria are classified as not
    indexed:

    * EDAX TSL: confidence index == -1

    Parameters
    ----------
    filename : str
        Path and file name.

    Returns
    -------
    CrystalMap
    """
    # Get file header
    with open(filename) as f:
        header = _get_header(f)

    # Get phase names and crystal symmetries from header (potentially empty)
    phase_ids, phase_names, symmetries, lattice_constants = _get_phases_from_header(
        header)
    structures = []
    for name, abcABG in zip(phase_names, lattice_constants):
        structures.append(Structure(title=name, lattice=Lattice(*abcABG)))

    # Read all file data
    file_data = np.loadtxt(filename)

    # Get vendor and column names
    n_rows, n_cols = file_data.shape
    vendor, column_names = _get_vendor_columns(header, n_cols)

    # Data needed to create a CrystalMap object
    data_dict = {
        "euler1": None,
        "euler2": None,
        "euler3": None,
        "x": None,
        "y": None,
        "phase_id": None,
        "prop": {},
    }
    for column, name in enumerate(column_names):
        if name in data_dict.keys():
            data_dict[name] = file_data[:, column]
        else:
            data_dict["prop"][name] = file_data[:, column]

    # Add phase list to dictionary
    data_dict["phase_list"] = PhaseList(
        names=phase_names,
        point_groups=symmetries,
        structures=structures,
        ids=phase_ids,
    )

    # Set which data points are not indexed
    if vendor in ["orix", "tsl"]:
        data_dict["phase_id"][np.where(data_dict["prop"]["ci"] == -1)] = -1
    # TODO: Add not-indexed convention for INDEX ASTAR

    # Set scan unit
    if vendor in ["tsl", "emsoft"]:
        scan_unit = "um"
    else:  # NanoMegas
        scan_unit = "nm"
    data_dict["scan_unit"] = scan_unit

    # Create rotations
    data_dict["rotations"] = Rotation.from_euler(
        np.column_stack((data_dict.pop("euler1"), data_dict.pop("euler2"),
                         data_dict.pop("euler3"))), )

    return CrystalMap(**data_dict)
def merge_crystal_maps(
    crystal_maps: List[CrystalMap],
    mean_n_best: int = 1,
    metric: Union[str, SimilarityMetric] = None,
    scores_prop: str = "scores",
    simulation_indices_prop: Optional[str] = None,
):
    """Merge a list of at least two single phase
    :class:`~orix.crystal_map.crystal_map.CrystalMap` with a 1D or 2D
    navigation shape into one multi phase map.

    It is required that all maps have the same number of rotations and
    scores (and simulation indices if applicable) per point.

    Parameters
    ----------
    crystal_maps : list of\
            :class:`~orix.crystal_map.crystal_map.CrystalMap`
        A list of crystal maps with simulated indices and scores among
        their properties.
    mean_n_best : int, optional
        Number of best metric results to take the mean of before
        comparing. Default is 1.
    metric : str or SimilarityMetric, optional
        Similarity metric, default is None.
    scores_prop : str, optional
        Name of scores array in the crystal maps' properties. Default
        is "scores".
    simulation_indices_prop : str, optional
        Name of simulated indices array in the crystal maps' properties.
        If None (default), the merged crystal map will not contain
        an array of merged simulation indices from the input crystal
        maps' properties. If a string, there must be as many simulation
        indices per point as there are scores.

    Returns
    -------
    merged_xmap : ~orix.crystal_map.crystal_map.CrystalMap
        A crystal map where the rotation of the phase with the best
        matching score(s) is assigned to each point. The best matching
        scores, merge sorted, are added to its properties with a name
        equal to whatever passed to `scores_prop` with "merged" as a
        suffix. If `simulation_indices_prop` is passed, the best
        matching simulation indices are added in the same way as the
        scores.

    Notes
    -----
    `mean_n_best` can be given with a negative sign if `metric` is not
    given, in order to choose the lowest valued metric results.
    """
    map_shapes = [xmap.shape for xmap in crystal_maps]
    if not np.sum(abs(np.diff(map_shapes, axis=0))) == 0:
        raise ValueError(
            "All crystal maps must have the same navigation shape")

    rot_per_point_per_map = [xmap.rotations_per_point for xmap in crystal_maps]
    if not all(np.diff(rot_per_point_per_map) == 0):
        raise ValueError(
            "All crystal maps must have the same number of rotations and scores"
            " per point.")

    if metric is None:
        sign = copysign(1, mean_n_best)
        mean_n_best = abs(mean_n_best)
    else:
        sign = _SIMILARITY_METRICS.get(metric, metric).sign

    # Notation used in the comments below:
    # - M: number of map points
    # - N: number of scores per point
    # - I: number of simulation indices per point
    # - K: number of maps to merge

    # Shape of the combined (unsorted) scores array, and the total
    # number of scores per point. Shape: (M, N, K) or (M, K) if only one
    # score is available (e.g. refined dot products from EMsoft)
    (comb_shape, n_scores_per_point) = _get_combined_scores_shape(
        crystal_maps=crystal_maps, scores_prop=scores_prop)

    # Combined (unsorted) scores array of shape (M, N, K) or (M, K)
    combined_scores = np.dstack(
        [xmap.prop[scores_prop] for xmap in crystal_maps])
    combined_scores = combined_scores.reshape(comb_shape)

    # Best score in each map point
    if n_scores_per_point > 1:  # (M, N, K)
        best_scores = np.mean(combined_scores[:, :mean_n_best], axis=1)
    else:  # (M, K)
        best_scores = combined_scores

    # Phase of best score in each map point
    phase_id = np.argmax(sign * best_scores, axis=1)

    # Get the new CrystalMap's rotations, scores and indices, restricted
    # to one phase per point (uncombined)
    new_rotations = Rotation(np.zeros_like(crystal_maps[0].rotations.data))
    new_scores = np.zeros_like(crystal_maps[0].prop[scores_prop])
    if simulation_indices_prop is not None:
        new_indices = np.zeros_like(
            crystal_maps[0].prop[simulation_indices_prop])
    phase_list = PhaseList()
    for i, xmap in enumerate(crystal_maps):
        mask = phase_id == i
        new_rotations[mask] = xmap.rotations[mask]
        new_scores[mask] = xmap.prop[scores_prop][mask]
        if simulation_indices_prop is not None:
            new_indices[mask] = xmap.prop[simulation_indices_prop][mask]
        if np.sum(mask) != 0:
            current_id = xmap.phases_in_data.ids[0]
            phase = xmap.phases_in_data[current_id].deepcopy()
            try:
                phase_list.add(phase)
            except ValueError:
                name = phase.name
                warnings.warn(
                    f"There are duplicates of phase {name}, will therefore "
                    f"rename this phase's name to {name + str(i)} in the merged"
                    " PhaseList", )
                phase.name = name + str(i)
                phase_list.add(phase)

    # To get the combined, best, sorted scores and simulation indices
    # from all maps (phases), we collapse the second and (potentially)
    # third axis to get (M, N * K) or (M, K)
    mergesort_shape = (comb_shape[0], np.prod(comb_shape[1:]))
    comb_scores_reshaped = combined_scores.reshape(mergesort_shape)
    best_sorted_idx = np.argsort(sign * -comb_scores_reshaped,
                                 kind="mergesort",
                                 axis=1)

    # Best, sorted scores in all maps (for all phases) per point
    merged_best_scores = np.take_along_axis(comb_scores_reshaped,
                                            best_sorted_idx,
                                            axis=-1)

    # Set up merged map's properties
    props = {
        scores_prop: new_scores,
        f"merged_{scores_prop}": merged_best_scores,
    }

    if simulation_indices_prop is not None:
        # Combined (unsorted) simulation indices array of shape
        # (M, N, K) or (M, K), accounting for the case where there are
        # more simulation indices per point than scores (e.g. refined
        # dot products from EMsoft)
        comb_sim_idx = np.dstack(
            [xmap.prop[simulation_indices_prop] for xmap in crystal_maps])

        if comb_sim_idx.size != np.prod(mergesort_shape):
            raise ValueError(
                "Cannot merge maps with more simulation indices than scores per"
                " point.")

        # To enable calculation of an orientation similarity map from
        # the combined, sorted simulation indices array, we must make
        # the indices unique across all maps
        for i in range(1, comb_sim_idx.shape[-1]):
            increment = (abs(comb_sim_idx[..., i - 1].max() -
                             comb_sim_idx[..., i].min()) + 1)
            comb_sim_idx[..., i] += increment

        # Collapse axes as for the combined scores array above
        comb_sim_idx = comb_sim_idx.reshape(mergesort_shape)

        # Best, sorted simulation indices in all maps (for all phases)
        # per point
        merged_simulated_indices = np.take_along_axis(comb_sim_idx,
                                                      best_sorted_idx,
                                                      axis=-1)

        # Finally, add to properties
        props[simulation_indices_prop] = new_indices
        props[f"merged_{simulation_indices_prop}"] = merged_simulated_indices

    return CrystalMap(
        rotations=new_rotations,
        phase_id=phase_id,
        phase_list=phase_list,
        x=crystal_maps[0].x,
        y=crystal_maps[0].y,
        z=crystal_maps[0].z,
        prop=props,
        scan_unit=crystal_maps[0].scan_unit,
    )
Exemple #10
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)
    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