def test_signal_varying_dimensions(self, dummy_signal, slices, desired_xmap_shape): s = dummy_signal.inav[slices] sig_shape = dummy_signal.axes_manager.signal_shape s_dict1 = EBSD(dummy_signal.data.reshape((-1, ) + sig_shape)) n_sim = s_dict1.axes_manager.navigation_size s_dict1._xmap = CrystalMap(Rotation(np.zeros((n_sim, 4)))) sd = StaticPatternMatching(s_dict1) res = sd(s) assert res.shape == desired_xmap_shape
def test_keep_n(self, n_rot_in, n_rot_out, keep_n): s = nickel_ebsd_small() s_dict = EBSD(np.random.random((n_rot_in, 60, 60)).astype(np.float32)) s_dict._xmap = CrystalMap(Rotation(np.zeros((n_rot_in, 4)))) sd = StaticPatternMatching(s_dict) xmap = sd(s) assert xmap.rotations_per_point == n_rot_out xmap2 = sd(s, keep_n=keep_n) assert xmap2.rotations_per_point == keep_n
def test_bands_as_markers_1d_nav( self, nickel_ebsd_simulation_generator, nickel_rlp ): """1D nav shape band markers work.""" simgen = nickel_ebsd_simulation_generator[0] assert simgen.navigation_shape == (1,) sim = simgen.geometrical_simulation(nickel_rlp.symmetrise()) assert sim.bands.navigation_shape == (1,) se = EBSD(np.ones((60, 60), dtype=np.uint8)) se.add_marker(marker=sim.bands_as_markers(), permanent=False, plot_marker=True) plt.close("all")
def test_get_rgb_1d(self): s = EBSD(np.random.random(9 * 3600).reshape((9, 60, 60))) vbse_gen = VirtualBSEGenerator(s) with pytest.raises(ValueError, match="The signal dimension cannot be "): _ = vbse_gen.get_rgb_image(r=(0, 0), g=(0, 1), b=(0, 2))
def test_n_slices_input(self, dummy_signal): sig_shape = dummy_signal.axes_manager.signal_shape n_px = np.prod(sig_shape) n_sim = 13500 + 1 rand_data = (np.random.randint( 0, 255, n_sim * n_px).reshape((n_sim, ) + sig_shape).astype(np.uint8)) s_dict1 = EBSD(rand_data) s_dict1._xmap = CrystalMap(Rotation(np.zeros((n_sim, 4)))) sd = StaticPatternMatching(s_dict1) with replace_stdin(io.StringIO("y")): res = sd(dummy_signal, n_slices=1) assert isinstance(res, CrystalMap) with replace_stdin(io.StringIO("n")): res = sd(dummy_signal, n_slices=1) assert res is None
def dummy_signal(): """Dummy signal of shape <(3, 3)|(3, 3)>. If this is changed, all tests using this signal will fail since they compare the output from methods using this signal (as input) to hard-coded outputs. """ # fmt: off dummy_array = np.array([ 5, 6, 5, 7, 6, 5, 6, 1, 0, 9, 7, 8, 7, 0, 8, 8, 7, 6, 0, 3, 3, 5, 2, 9, 3, 3, 9, 8, 1, 7, 6, 4, 8, 8, 2, 2, 4, 0, 9, 0, 1, 0, 2, 2, 5, 8, 6, 0, 4, 7, 7, 7, 6, 0, 4, 1, 6, 3, 4, 0, 1, 1, 0, 5, 9, 8, 4, 6, 0, 2, 9, 2, 9, 4, 3, 6, 5, 6, 2, 5, 9 ], dtype=np.uint8).reshape((3, 3, 3, 3)) # fmt: on return EBSD(dummy_array)
def test_return_merged_crystal_map(self, return_merged_xmap, desired_n_xmaps_out): s = nickel_ebsd_small() s_dict1 = EBSD(s.data.reshape(-1, 60, 60)) s_dict2 = s_dict1.deepcopy() n_patterns = s_dict1.axes_manager.navigation_size s_dict1._xmap = CrystalMap(Rotation(np.zeros((n_patterns, 4)))) s_dict2._xmap = s_dict1.xmap.deepcopy() s_dict1.xmap.phases[0].name = "a" s_dict2.xmap.phases[0].name = "b" sd = StaticPatternMatching([s_dict1, s_dict2]) res1 = sd(s, return_merged_crystal_map=return_merged_xmap) assert len(res1) == desired_n_xmaps_out sd.dictionaries.pop(-1) res2 = sd(s, return_merged_crystal_map=True) assert isinstance(res2, CrystalMap) res3 = sd(s) assert isinstance(res3, CrystalMap)
def test_bands_as_markers( self, nickel_ebsd_simulation_generator, nickel_rlp, nav_shape ): """Line markers work.""" simgen = nickel_ebsd_simulation_generator simgen.navigation_shape = nav_shape sim = simgen.geometrical_simulation(nickel_rlp.symmetrise()) se = EBSD(np.ones(nav_shape + (60, 60), dtype=np.uint8)) se.add_marker(marker=sim.bands_as_markers(), permanent=True, plot_marker=False) assert isinstance(se.metadata.Markers.line_segment, line_segment) se.plot() plt.close("all")
def test_pc_as_markers( self, nickel_ebsd_simulation_generator, nickel_rlp, nav_shape ): """Projection center markers work.""" simgen = nickel_ebsd_simulation_generator simgen.navigation_shape = nav_shape sim = simgen.geometrical_simulation(nickel_rlp.symmetrise()) se = EBSD(np.ones(nav_shape + (60, 60), dtype=np.uint8)) se.add_marker(marker=sim.pc_as_markers(), permanent=True, plot_marker=False) assert isinstance(se.metadata.Markers.point, point) assert se.metadata.Markers.point.marker_properties["marker"] == "*" se.plot() plt.close("all")
def test_as_markers(self, nickel_ebsd_simulation_generator, nickel_rlp): """All markers work.""" simgen = nickel_ebsd_simulation_generator sim = simgen.geometrical_simulation(nickel_rlp.symmetrise()) se = EBSD(np.ones(simgen.navigation_shape + (60, 60), dtype=np.uint8)) se.add_marker( marker=sim.as_markers( bands=True, zone_axes=True, zone_axes_labels=True, pc=True ), permanent=True, plot_marker=False, ) se.plot() plt.close("all")
def test_get_orientation_similarity_map(self): s = nickel_ebsd_small() s_dict1 = EBSD(s.data.reshape(-1, 60, 60)) s_dict2 = EBSD(s.data.reshape(-1, 60, 60)) n_patterns = s_dict1.axes_manager.navigation_size s_dict1._xmap = CrystalMap(Rotation(np.zeros((n_patterns, 4)))) s_dict2._xmap = CrystalMap(Rotation(np.zeros((n_patterns, 4)))) s_dict1.xmap.phases[0].name = "a" s_dict2.xmap.phases[0].name = "b" sd = StaticPatternMatching([s_dict1, s_dict2]) res = sd(s, keep_n=1, get_orientation_similarity_map=True) xmap1, _ = res assert np.allclose(xmap1.scores, 1) assert np.all(["osm" in xmap.prop for xmap in res])
def test_plot_zone_axes_labels_warns(self, nickel_ebsd_simulation_generator, nickel_rlp): """Matplotlib warns when plotting text with NaN coordinates.""" simgen = nickel_ebsd_simulation_generator sim = simgen.geometrical_simulation(nickel_rlp.symmetrise()) se = EBSD(np.ones(simgen.navigation_shape + (60, 60), dtype=np.uint8)) with pytest.warns(UserWarning, match="Matplotlib will print"): se.add_marker( marker=sim.zone_axes_labels_as_markers(), permanent=False, plot_marker=True, ) se.plot() se.axes_manager[0].index = 1 plt.close("all")
def test_bands_as_markers_family_colors( self, nickel_ebsd_simulation_generator, nickel_rlp ): """Line markers work when specifying colors.""" simgen = nickel_ebsd_simulation_generator sim = simgen.geometrical_simulation(nickel_rlp[:2]) se = EBSD(np.ones(simgen.navigation_shape + (60, 60), dtype=np.uint8)) colors = ["lime", "tab:blue"] se.add_marker( marker=sim.bands_as_markers(family_colors=colors), permanent=True, plot_marker=False, ) assert se.metadata.Markers.line_segment.marker_properties["color"] == colors[0] assert se.metadata.Markers.line_segment1.marker_properties["color"] == colors[1] se.plot() plt.close("all")
def test_zone_axes_labels_as_markers(self, nickel_ebsd_simulation_generator, nickel_rlp, nav_shape): """Text markers work.""" simgen = nickel_ebsd_simulation_generator simgen.navigation_shape = nav_shape sim = simgen.geometrical_simulation(nickel_rlp.symmetrise()) se = EBSD(np.ones(nav_shape + (60, 60), dtype=np.uint8)) matplotlib.set_loglevel("error") se.add_marker( marker=sim.zone_axes_labels_as_markers(), permanent=True, plot_marker=False, ) assert isinstance(se.metadata.Markers.text, text) se.plot() plt.close("all") matplotlib.set_loglevel("warning")
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