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]
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)
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)
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)
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
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]
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