Exemple #1
0
    def test_project_single_pattern_from_master_pattern(
            self, dtype_out, intensity_range):
        """Make sure the Numba function is covered."""
        dc = _get_direction_cosines_for_single_pc_from_detector(self.detector)
        npx = npy = 101
        mpn = mps = np.random.random(npy * npx).reshape((npy, npx))

        pattern = _project_single_pattern_from_master_pattern.py_func(
            rotation=np.array([1, 1, 0, 0], dtype=float),
            direction_cosines=dc.reshape((-1, 3)),
            master_north=mpn,
            master_south=mps,
            npx=npx,
            npy=npy,
            scale=1,
            n_pixels=self.detector.size,
            rescale=True,
            out_min=intensity_range[0],
            out_max=intensity_range[1],
            dtype_out=dtype_out,
        )
        assert pattern.shape == (self.detector.size, )
        assert pattern.dtype == dtype_out
        assert np.min(pattern) == intensity_range[0]
        assert np.max(pattern) == intensity_range[1]
Exemple #2
0
    def test_get_direction_cosines(self):
        detector = self.detector
        dc = _get_direction_cosines_for_single_pc_from_detector(detector)
        assert dc.shape == detector.shape + (3, )
        assert np.max(dc) <= 1

        dc2 = _get_direction_cosines_for_single_pc.py_func(
            pcx=detector.pcx,
            pcy=detector.pcy,
            pcz=detector.pcz,
            nrows=detector.nrows,
            ncols=detector.ncols,
            tilt=detector.tilt,
            azimuthal=detector.azimuthal,
            sample_tilt=detector.sample_tilt,
        )
        assert np.allclose(dc, dc2)
Exemple #3
0
    def test_get_lambert_interpolation_parameters(self):
        """Make sure the Numba function is covered."""
        dc = _get_direction_cosines_for_single_pc_from_detector(self.detector)
        npx = npy = 101
        scale = (npx - 1) // 2
        nii, nij, niip, nijp = _get_lambert_interpolation_parameters.py_func(
            v=dc.reshape((-1, 3)), npx=npx, npy=npy, scale=scale)[:4]

        assert np.all(nii <= niip)
        assert np.all(nij <= nijp)
        assert np.all(nii < npx)
        assert np.all(nij < npy)
        assert np.all(niip < npx)
        assert np.all(nijp < npx)
        assert np.all(nii >= 0)
        assert np.all(nij >= 0)
        assert np.all(niip >= 0)
        assert np.all(nijp >= 0)
Exemple #4
0
    def test_get_direction_cosines_for_multiple_pcs(self):
        """Make sure the Numba function is covered."""
        detector = self.detector
        dc0 = _get_direction_cosines_for_single_pc_from_detector(detector)
        nav_shape = (2, 3)
        detector.pc = np.full(nav_shape + (3, ), detector.pc)
        nrows, ncols = detector.shape
        dc = _get_direction_cosines_for_multiple_pcs.py_func(
            pcx=detector.pcx.ravel(),
            pcy=detector.pcy.ravel(),
            pcz=detector.pcz.ravel(),
            nrows=nrows,
            ncols=ncols,
            tilt=detector.tilt,
            azimuthal=detector.azimuthal,
            sample_tilt=detector.sample_tilt,
        )

        assert np.allclose(dc0, dc[0])
        assert dc.shape == (np.prod(nav_shape), nrows, ncols, 3)
Exemple #5
0
 def test_get_pixel_from_master_pattern(self):
     """Make sure the Numba function is covered."""
     dc = _get_direction_cosines_for_single_pc_from_detector(self.detector)
     npx = npy = 101
     scale = (npx - 1) // 2
     (
         nii,
         nij,
         niip,
         nijp,
         di,
         dj,
         dim,
         djm,
     ) = _get_lambert_interpolation_parameters(v=dc.reshape((-1, 3)),
                                               npx=npx,
                                               npy=npy,
                                               scale=scale)
     mp = np.ones((npy, npx), dtype=float)
     value = _get_pixel_from_master_pattern.py_func(mp, nii[0], nij[0],
                                                    niip[0], nijp[0], di[0],
                                                    dj[0], dim[0], djm[0])
     assert value == 1.0
Exemple #6
0
    def test_project_patterns_from_master_pattern(self):
        """Make sure the Numba function is covered."""
        r = Rotation.from_euler(((0, 0, 0), (1, 1, 1), (2, 2, 2)))
        dc = _get_direction_cosines_for_single_pc_from_detector(self.detector)

        npx = npy = 101
        mpn = mps = np.zeros((npy, npx))
        patterns = _project_patterns_from_master_pattern.py_func(
            rotations=r.data,
            direction_cosines=dc,
            master_north=mpn,
            master_south=mps,
            npx=npx,
            npy=npy,
            scale=float((npx - 1) / 2),
            dtype_out=mpn.dtype,
            rescale=False,
            # Aren't used
            out_min=1,
            out_max=2,
        )

        assert patterns.shape == r.shape + dc.shape[:-1]
Exemple #7
0
def _refine_orientation(
    xmap: CrystalMap,
    detector,
    master_pattern,
    energy: Union[int, float],
    patterns: Union[np.ndarray, da.Array],
    mask: Optional[np.ndarray] = None,
    method: Optional[str] = None,
    method_kwargs: Optional[dict] = None,
    trust_region: Union[None, np.ndarray, list] = None,
    compute: bool = True,
) -> CrystalMap:
    """See the docstring of
    :meth:`kikuchipy.signals.EBSD.refine_orientation`.
    """
    (
        method,
        method_kwargs,
        nav_shape,
        n_patterns,
        sig_shape,
        nrows,
        ncols,
        n_pixels,
        rotations,
        solver_kwargs,
        fixed_parameters,
    ) = _refine_setup(
        xmap=xmap,
        detector=detector,
        master_pattern=master_pattern,
        energy=energy,
        patterns_dtype=patterns.dtype,
        mask=mask,
        method=method,
        method_kwargs=method_kwargs,
    )

    # Get rotations in the correct shape
    euler = rotations.to_euler()
    euler = euler.reshape(nav_shape + (3, ))

    if trust_region is None:
        trust_region = DEFAULT_TRUST_REGION_EULER_DEG
        solver_kwargs["trust_region_passed"] = False
    else:
        solver_kwargs["trust_region_passed"] = True
    solver_kwargs["trust_region"] = dask.delayed(np.deg2rad(trust_region))

    # Prepare parameters for the objective function which are constant
    # during optimization
    solver_kwargs["fixed_parameters"] = dask.delayed(fixed_parameters)

    # Determine whether a new PC is used for every pattern
    new_pc = np.prod(detector.navigation_shape) != 1 and n_patterns > 1

    # Delay data once
    patterns = dask.delayed(patterns)
    euler = dask.delayed(euler)

    refined_parameters = []
    if new_pc:
        # Patterns have been indexed with varying PCs, so we
        # re-compute the direction cosines for every pattern during
        # refinement
        pcx = detector.pcx.astype(float).reshape(nav_shape)
        pcy = detector.pcy.astype(float).reshape(nav_shape)
        pcz = detector.pcz.astype(float).reshape(nav_shape)
        pcx = dask.delayed(pcx)
        pcy = dask.delayed(pcy)
        pcz = dask.delayed(pcz)
        for idx in np.ndindex(*nav_shape):
            delayed_solution = dask.delayed(_refine_orientation_solver)(
                pattern=patterns[idx],
                rotation=euler[idx],
                pcx=pcx[idx],
                pcy=pcy[idx],
                pcz=pcz[idx],
                nrows=nrows,
                ncols=ncols,
                tilt=detector.tilt,
                azimuthal=detector.azimuthal,
                sample_tilt=detector.sample_tilt,
                **solver_kwargs,
            )
            refined_parameters.append(delayed_solution)
    else:
        # Patterns have been indexed with the same PC, so we use the
        # same direction cosines during refinement of all patterns
        dc = _get_direction_cosines_for_single_pc_from_detector(
            detector).reshape((n_pixels, 3))
        for idx in np.ndindex(*nav_shape):
            delayed_solution = dask.delayed(_refine_orientation_solver)(
                pattern=patterns[idx],
                rotation=euler[idx],
                direction_cosines=dc,
                **solver_kwargs,
            )
            refined_parameters.append(delayed_solution)

    print(
        _refinement_info_message(
            method=method,
            method_kwargs=method_kwargs,
            trust_region=list(trust_region),
            trust_region_passed=solver_kwargs["trust_region_passed"],
        ))
    if compute:
        output = compute_refine_orientation_results(
            results=refined_parameters,
            xmap=xmap,
            master_pattern=master_pattern)
    else:
        output = refined_parameters
    gc.collect()

    return output
    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