Example #1
0
 def test_get_chunking_no_signal(self, shape, nav_dim, sig_dim, dtype,
                                 desired_chunks):
     chunks = get_chunking(data_shape=shape,
                           nav_dim=nav_dim,
                           sig_dim=sig_dim,
                           dtype=dtype)
     assert chunks == desired_chunks
Example #2
0
    def get_patterns(self, lazy: bool) -> Union[np.ndarray, da.Array]:
        """Return the EBSD patterns in the file.

        The patterns are read from the memory map. They are sorted into
        their correct navigation (map) position if necessary.

        Parameters
        ----------
        lazy
            Whether to return a :class:`numpy.ndarray` or
            :class:`dask.array.Array`.

        Returns
        -------
        data
            EBSD patterns of shape (n navigation rows, n navigation
            columns, n signal rows, n signal columns).
        """
        data = self.memmap["pattern"]
        if lazy:
            data = da.from_array(data)

        is_sorted = np.allclose(np.diff(self.pattern_order), 1)
        if not is_sorted and self.all_patterns_present:
            data = data[self.pattern_order]

        if data.shape != self.data_shape:
            data = data.reshape(self.data_shape)

        if lazy:
            chunks = get_chunking(
                data_shape=self.data_shape,
                nav_dim=len(self.navigation_shape),
                sig_dim=2,
                dtype=self.dtype,
            )
            data = data.rechunk(chunks)

        return data
    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
    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
Example #5
0
 def test_get_chunking_dtype(self):
     s = LazyEBSD(da.zeros((32, 32, 256, 256), dtype=np.uint8))
     chunks0 = get_chunking(s, dtype=np.float32)
     chunks1 = get_chunking(s)
     assert chunks0 == ((8, 8, 8, 8), (8, 8, 8, 8), (256, ), (256, ))
     assert chunks1 == ((16, 16), (16, 16), (256, ), (256, ))
Example #6
0
 def test_chunk_bytes(self):
     s = LazyEBSD(da.zeros((32, 32, 256, 256), dtype=np.uint16))
     chunks = get_chunking(s, chunk_bytes=15e6)
     assert chunks == ((8, 8, 8, 8), (8, 8, 8, 8), (256, ), (256, ))
Example #7
0
 def test_chunk_shape(self):
     s = LazyEBSD(da.zeros((32, 32, 256, 256), dtype=np.uint16))
     chunks = get_chunking(s, chunk_shape=16)
     assert chunks == ((16, 16), (16, 16), (256, ), (256, ))
Example #8
0
 def test_get_chunking_no_parameters(self):
     s = LazyEBSD(da.zeros((32, 32, 256, 256), dtype=np.uint16))
     chunks = get_chunking(s)
     assert len(chunks) == 4