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