def test_merging_refined_maps(self): ny, nx = (3, 3) nav_size = ny * nx r = Rotation.from_euler(np.ones((nav_size, 3))) x = np.tile(np.arange(ny), nx) y = np.repeat(np.arange(nx), ny) # Simulation indices n_sim_indices = 10 sim_indices1 = np.random.randint(low=0, high=1000, size=n_sim_indices * nav_size).reshape( (nav_size, n_sim_indices)) sim_indices2 = np.random.randint(low=0, high=1000, size=n_sim_indices * nav_size).reshape( (nav_size, n_sim_indices)) # Scores scores1 = np.ones(nav_size) scores1[0] = 3 scores2 = 2 * np.ones(nav_size) xmap1 = CrystalMap( rotations=r, phase_id=np.ones(nav_size) * 0, phase_list=PhaseList(Phase(name="a")), x=x, y=y, prop={ "simulation_indices": sim_indices1, "scores": scores1 }, ) xmap2 = CrystalMap( rotations=r, phase_id=np.ones(nav_size), phase_list=PhaseList(Phase(name="b")), x=x, y=y, prop={ "simulation_indices": sim_indices2, "scores": scores2 }, ) xmap_merged = merge_crystal_maps(crystal_maps=[xmap1, xmap2]) assert "simulation_indices" not in xmap_merged.prop.keys() assert "merged_simulation_indices" not in xmap_merged.prop.keys() with pytest.raises(ValueError, match="Cannot merge maps with more"): _ = merge_crystal_maps( crystal_maps=[xmap1, xmap2], simulation_indices_prop="simulation_indices", )
def test_mean_n_best( self, get_single_phase_xmap, nav_shape, rot_per_point, mean_n_best, desired_merged_scores, desired_merged_sim_idx, ): """Ensure that the mergesorted scores and simulation index properties in the merged map has the correct values and shape. """ n_phases = np.shape(desired_merged_scores)[-1] // rot_per_point xmaps = [] for i in range(n_phases): xmap = get_single_phase_xmap(nav_shape, rot_per_point, name=str(i)) xmap[i].scores += i xmaps.append(xmap) # The simulation indices should be the same in all maps all_sim_idx = np.dstack([xmap.simulation_indices for xmap in xmaps]) assert np.sum(np.diff(all_sim_idx)) == 0 merged_xmap = merge_crystal_maps( crystal_maps=xmaps, mean_n_best=mean_n_best, ) assert merged_xmap.phases.size == n_phases assert np.allclose(merged_xmap.merged_scores, desired_merged_scores) assert np.allclose(merged_xmap.merged_simulation_indices, desired_merged_sim_idx)
def test_warning_merge_maps_with_same_phase( self, get_single_phase_xmap, phase_names, desired_phase_names, ): n_phases = len(phase_names) scores_prop = "scores" sim_idx_prop = "simulated_indices" map_shape = (5, 6) rot_per_point = 5 xmaps = [] xmap_args = (map_shape, rot_per_point, [scores_prop, sim_idx_prop]) phase_ids = np.arange(n_phases) for i in range(n_phases): xmap = get_single_phase_xmap(*xmap_args, phase_names[i], phase_ids[i]) # All maps have at least one point with the best score xmap[i, i].scores += i + 1 xmaps.append(xmap) with pytest.warns( UserWarning, match=f"There are duplicates of phase {phase_names[0]}"): merged_xmap = merge_crystal_maps( crystal_maps=xmaps, scores_prop=scores_prop, simulation_indices_prop=sim_idx_prop, ) assert all( [name in merged_xmap.phases.names for name in desired_phase_names])
def test_negative_metric(self, get_single_phase_xmap): def negative_sad(p, t): # pragma: no cover return -np.sum(np.abs(p - t), axis=(2, 3)) metric = make_similarity_metric(negative_sad, greater_is_better=False) map_shape = (5, 6) rot_per_point = 5 scores_prop = "scores" sim_idx_prop = "simulation_indices" xmap1 = get_single_phase_xmap(map_shape, rot_per_point, [scores_prop, sim_idx_prop], "a", 0) xmap2 = get_single_phase_xmap(map_shape, rot_per_point, [scores_prop, sim_idx_prop], "b", 1) xmap2[0, 3].prop[scores_prop] = 0 desired_phase_id = np.zeros(np.prod(map_shape)) desired_phase_id[3] = 1 merged_xmap = merge_crystal_maps( crystal_maps=[xmap1, xmap2], metric=metric, ) assert np.allclose(merged_xmap.phase_id, desired_phase_id)
def test_property_names(self, get_single_phase_xmap, scores_prop, sim_idx_prop): """Passing scores and simulation indices property names returns expected properties in merged map. """ map_shape = (5, 6) rot_per_point = 50 xmap1 = get_single_phase_xmap(map_shape, rot_per_point, [scores_prop, sim_idx_prop], "a", 0) xmap2 = get_single_phase_xmap(map_shape, rot_per_point, [scores_prop, sim_idx_prop], "b", 1) xmap2[3, 3].prop[scores_prop] = 2 merged_xmap = merge_crystal_maps( crystal_maps=[xmap1, xmap2], scores_prop=scores_prop, simulation_indices_prop=sim_idx_prop, ) assert scores_prop in merged_xmap.prop.keys() assert sim_idx_prop in merged_xmap.prop.keys() desired_merged_shapes = (np.prod(map_shape), rot_per_point * 2) assert (merged_xmap.prop[f"merged_{scores_prop}"].shape == desired_merged_shapes) assert (merged_xmap.prop[f"merged_{sim_idx_prop}"].shape == desired_merged_shapes)
def test_merging_maps_different_number_of_scores_raises( self, get_single_phase_xmap): nav_shape = (2, 3) xmap1 = get_single_phase_xmap(nav_shape, 3, name="a") xmap2 = get_single_phase_xmap(nav_shape, 4, name="b") xmap2[0, 1].scores = 2.0 # Both maps in both merged maps crystal_maps = [xmap1, xmap2] with pytest.raises(ValueError, match="All crystal maps must have the"): _ = merge_crystal_maps(crystal_maps)
def test_mean_n_best_varying_scores(self, get_single_phase_xmap): """Ensure various combinations of scores per point and how many of these are evaulated to find the best match return expected results. """ nav_shape = (2, 3) rot_per_point = 3 xmap1 = get_single_phase_xmap(nav_shape, rot_per_point, name="a") xmap2 = get_single_phase_xmap(nav_shape, rot_per_point, name="b") idx = (0, 0) xmap1[idx].scores = [1, 2, 2.1] xmap2[idx].scores = [1, 1.9, 3] xmap2[0, 1].scores = 2.0 # Both maps in both merged maps crystal_maps = [xmap1, xmap2] merged_xmap1 = merge_crystal_maps(crystal_maps, mean_n_best=2) merged_xmap2 = merge_crystal_maps(crystal_maps, mean_n_best=3) assert np.allclose(merged_xmap1.phase_id, [0, 1, 0, 0, 0, 0]) assert np.allclose(merged_xmap2.phase_id, [1, 1, 0, 0, 0, 0])
def __call__( self, signal, metric: Union[str, SimilarityMetric] = "ncc", keep_n: int = 50, n_slices: int = 1, return_merged_crystal_map: bool = False, get_orientation_similarity_map: bool = False, ) -> Union[CrystalMap, List[CrystalMap]]: """Match each experimental pattern to all simulated patterns, of known crystal orientations in pre-computed dictionaries :cite:`chen2015dictionary,jackson2019dictionary`, to determine their phase and orientation. A suitable similarity metric, the normalized cross-correlation (:func:`~kikuchipy.indexing.similarity_metrics.ncc`), is used by default, but a valid user-defined similarity metric may be used instead. :class:`~orix.crystal_map.crystal_map.CrystalMap`'s for each dictionary with "scores" and "simulation_indices" as properties are returned. Parameters ---------- signal : EBSD EBSD signal with experimental patterns. metric : str or SimilarityMetric, optional Similarity metric, by default "ncc" (normalized cross-correlation). keep_n : int, optional Number of best matches to keep, by default 50 or the number of simulated patterns if fewer than 50 are available. n_slices : int, optional Number of simulation slices to process sequentially, by default 1 (no slicing). return_merged_crystal_map : bool, optional Whether to return a merged crystal map, the best matches determined from the similarity scores, in addition to the single phase maps. By default False. get_orientation_similarity_map : bool, optional Add orientation similarity maps to the returned crystal maps' properties named "osm". By default False. Returns ------- xmaps : :class:`~orix.crystal_map.crystal_map.CrystalMap` or \ list of \ :class:`~orix.crystal_map.crystal_map.CrystalMap` A crystal map for each dictionary loaded and one merged map if `return_merged_crystal_map = True`. Notes ----- Merging of crystal maps and calculations of orientation similarity maps can be done afterwards with :func:`~kikuchipy.indexing.merge_crystal_maps` and :func:`~kikuchipy.indexing.orientation_similarity_map`, respectively. See Also -------- ~kikuchipy.indexing.similarity_metrics.make_similarity_metric ~kikuchipy.indexing.similarity_metrics.ndp """ # This needs a rework before sent to cluster and possibly more # automatic slicing with dask n_simulations = max( [d.axes_manager.navigation_size for d in self.dictionaries] ) good_number = 13500 if (n_simulations // n_slices) > good_number: answer = input( "You should probably increase n_slices depending on your " f"available memory, try above {n_simulations // good_number}." " Do you want to proceed? [y/n]" ) if answer != "y": return # Get metric from optimized metrics if it is available, or # return the metric if it is not metric = _SIMILARITY_METRICS.get(metric, metric) axes_manager = signal.axes_manager spatial_arrays = _get_spatial_arrays( shape=axes_manager.navigation_shape, extent=axes_manager.navigation_extent, ) n_nav_dims = axes_manager.navigation_dimension if n_nav_dims == 0: xmap_kwargs = dict() elif n_nav_dims == 1: scan_unit = axes_manager.navigation_axes[0].units xmap_kwargs = dict(x=spatial_arrays, scan_unit=scan_unit) else: # 2d scan_unit = axes_manager.navigation_axes[0].units xmap_kwargs = dict( x=spatial_arrays[0], y=spatial_arrays[1], scan_unit=scan_unit, ) keep_n = min([keep_n] + [d.xmap.size for d in self.dictionaries]) # Naively let dask compute them seperately, should try in the # future combined compute for better performance xmaps = [] patterns = signal.data for dictionary in self.dictionaries: simulation_indices, scores = _pattern_match( patterns, dictionary.data, metric=metric, keep_n=keep_n, n_slices=n_slices, phase_name=dictionary.xmap.phases_in_data.names[0], ) new_xmap = CrystalMap( rotations=dictionary.xmap.rotations[simulation_indices], phase_list=dictionary.xmap.phases_in_data, prop={ "scores": scores, "simulation_indices": simulation_indices, }, **xmap_kwargs, ) xmaps.append(new_xmap) # Create a merged CrystalMap using best metric result across all # dictionaries if return_merged_crystal_map and len(self.dictionaries) > 1: xmap_merged = merge_crystal_maps(xmaps, metric=metric) xmaps.append(xmap_merged) # Compute orientation similarity map if get_orientation_similarity_map: for xmap in xmaps: osm = orientation_similarity_map(xmap, n_best=keep_n) xmap.prop["osm"] = osm.flatten() if len(xmaps) == 1: xmaps = xmaps[0] return xmaps
def test_merging_maps_different_shapes_raises(self, get_single_phase_xmap): xmap1 = get_single_phase_xmap((4, 3)) xmap2 = get_single_phase_xmap((3, 4)) with pytest.raises(ValueError, match="All crystal maps must have the"): _ = merge_crystal_maps([xmap1, xmap2])
def test_merge_crystal_maps_1d(self, get_single_phase_xmap, map_shape, rot_per_point, phase_names): """Crystal maps with a 1D navigation shape can be merged successfully and yields an expected output. """ n_phases = len(phase_names) scores_prop, sim_idx_prop = "scores", "sim_idx" map_size = np.sum(map_shape) data_shape = (map_size, ) if rot_per_point > 1: data_shape += (rot_per_point, ) desired_phase_ids = np.zeros(map_size) desired_scores = np.ones(data_shape) desired_idx = np.arange(np.prod(data_shape)).reshape(data_shape) xmaps = [] xmap_args = (map_shape, rot_per_point, [scores_prop, sim_idx_prop]) phase_ids = np.arange(n_phases) for i in range(n_phases): xmap = get_single_phase_xmap(*xmap_args, phase_names[i], phase_ids[i]) # All maps have at least one point with the best score xmap[i].prop[scores_prop] += i + 1 xmaps.append(xmap) desired_phase_ids[i] = i desired_scores[i] = xmap[i].prop[scores_prop] desired_idx[i] = xmap[i].prop[sim_idx_prop] if i == 0: desired_rot = xmap.rotations.data else: desired_rot[i] = xmap[i].rotations.data merged_xmap = merge_crystal_maps( crystal_maps=xmaps, scores_prop=scores_prop, simulation_indices_prop=sim_idx_prop, ) assert merged_xmap.shape == xmaps[0].shape assert merged_xmap.size == xmaps[0].size for v1, v2 in zip(merged_xmap._coordinates.values(), xmaps[0]._coordinates.values()): if v1 is None: assert v1 is v2 else: np.allclose(v1, v2) assert np.allclose(merged_xmap.phase_id, desired_phase_ids) assert np.allclose(merged_xmap.prop[scores_prop], desired_scores) assert np.allclose(merged_xmap.prop[sim_idx_prop], desired_idx) assert np.allclose(merged_xmap.rotations.data, desired_rot) desired_merged_shapes = (map_size, rot_per_point * n_phases) assert (merged_xmap.prop[f"merged_{scores_prop}"].shape == desired_merged_shapes) assert (merged_xmap.prop[f"merged_{sim_idx_prop}"].shape == desired_merged_shapes)