From 465ddb45521b35c9094865b2c804863d9e0fb612 Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 11 Jun 2024 20:07:16 +0200 Subject: [PATCH 01/19] Support multi-phase vectors --- pyxem/signals/indexation_results.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 5c4c6df6c..446be2d58 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -394,10 +394,10 @@ def to_single_phase_orientations(self, **kwargs) -> Orientation: symmetry=self.simulation.phases.point_group, ) - def to_single_phase_vectors( + def to_vectors( self, n_best_index: int = 0, **kwargs ) -> hs.signals.Signal1D: - """Get the reciprocal lattice vectors for a single-phase simulation. + """Get the reciprocal lattice vectors for each navigation position. Parameters ---------- @@ -408,10 +408,10 @@ def to_single_phase_vectors( """ if self.simulation.has_multiple_phases: - raise ValueError("Multiple phases found in simulation") - - # Use vector data as signal in case of different vectors per navigation position - vectors_signal = hs.signals.Signal1D(self.simulation.coordinates) + # Use vector data as signal in case of different vectors per navigation position + vectors_signal = hs.signals.Signal1D(self.simulation.coordinates) + else: + vectors_signal = hs.signals.Signal1D([sim for sim in self.simulations]) v = self.map( extract_vectors_from_orientation_map, all_vectors=vectors_signal, @@ -580,7 +580,7 @@ def to_markers( navigation_chunks = None all_markers = [] for n in range(n_best): - vectors = self.to_single_phase_vectors( + vectors = self.to_vectors( lazy_output=True, navigation_chunks=navigation_chunks ) color = marker_colors[n % len(marker_colors)] From 384fbd475dc943c8e24d9b1a789afd8f1122c665 Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 11 Jun 2024 20:07:16 +0200 Subject: [PATCH 02/19] Refactor get_ipf_outline --- pyxem/signals/indexation_results.py | 133 ++++++++++++++-------------- 1 file changed, 68 insertions(+), 65 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 446be2d58..c723b3d5f 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -170,6 +170,71 @@ def _get_second_best_phase(z): else: return -1 +def get_ipf_outline( + phase: Phase, include_labels: bool = True, offset: float = 0.85, scale: float = 0.2 +): + """Get the outline of the IPF for the orientation map as a marker in the + upper right hand corner including labels if desired. + + Parameters + ---------- + phase: Phase + The phase to use, defining the symmerty reduced zone + include_labels : bool + If True, the labels for the axes will be included. + offset : float + The offset of the markers from the lower left of the plot (as a fraction of the axis). + scale : float + The scale (as a fraction of the axis) for the markers. + + Returns + ------- + polygon_sector : hs.plot.markers.Polygons + The outline of the IPF as a marker + texts : hs.plot.markers.Texts + The text labels for the IPF axes + maxes : np.ndarray + The maximum values for the axes + mins : np.ndarray + The minimum values for the axes + """ + # Creating Lines around QuadMesh + sector = phase.point_group.fundamental_sector + s = StereographicProjection() + + edges = _closed_edges_in_hemisphere(sector.edges, sector) + ex, ey = s.vector2xy(edges) + original_offset = np.vstack((ex, ey)).T + mins, maxes = original_offset.min(axis=0), original_offset.max(axis=0) + original_offset = ( + (original_offset - ((maxes + mins) / 2)) / (maxes - mins) * scale + ) + original_offset = original_offset + offset + polygon_sector = hs.plot.markers.Polygons( + verts=original_offset[np.newaxis], + transform="axes", + alpha=1, + facecolor="none", + ) + if include_labels: + labels = _get_ipf_axes_labels( + sector.vertices, symmetry=phase.point_group + ) + tx, ty = s.vector2xy(sector.vertices) + texts_offset = np.vstack((tx, ty)).T + texts_offset = ( + (texts_offset - ((maxes + mins) / 2)) / (maxes - mins) * scale + ) + texts_offset = texts_offset + offset + texts = hs.plot.markers.Texts( + texts=labels, + offsets=texts_offset, + sizes=(1,), + offset_transform="axes", + facecolor="k", + ) + return polygon_sector, texts, maxes, mins + def vectors_to_coordinates(vectors): """ @@ -484,8 +549,8 @@ def to_ipf_markers(self, offset: float = 0.85, scale: float = 0.2): ) if self.simulation.has_multiple_phases: raise ValueError("Multiple phases found in simulation") - polygon_sector, texts, maxes, mins = self._get_ipf_outline( - offset=offset, scale=scale + polygon_sector, texts, maxes, mins = get_ipf_outline( + self.simulaiton.phases, offset=offset, scale=scale ) orients = self.to_single_phase_orientations() @@ -724,68 +789,6 @@ def marker_generator(entry): all_markers = compute_markers(all_markers) return all_markers - def _get_ipf_outline( - self, include_labels: bool = True, offset: float = 0.85, scale: float = 0.2 - ): - """Get the outline of the IPF for the orientation map as a marker in the - upper right hand corner including labels if desired. - - Parameters - ---------- - include_labels : bool - If True, the labels for the axes will be included. - offset : float - The offset of the markers from the lower left of the plot (as a fraction of the axis). - scale : float - The scale (as a fraction of the axis) for the markers. - - Returns - ------- - polygon_sector : hs.plot.markers.Polygons - The outline of the IPF as a marker - texts : hs.plot.markers.Texts - The text labels for the IPF axes - maxes : np.ndarray - The maximum values for the axes - mins : np.ndarray - The minimum values for the axes - """ - # Creating Lines around QuadMesh - sector = self.simulation.phases.point_group.fundamental_sector - s = StereographicProjection() - - edges = _closed_edges_in_hemisphere(sector.edges, sector) - ex, ey = s.vector2xy(edges) - original_offset = np.vstack((ex, ey)).T - mins, maxes = original_offset.min(axis=0), original_offset.max(axis=0) - original_offset = ( - (original_offset - ((maxes + mins) / 2)) / (maxes - mins) * scale - ) - original_offset = original_offset + offset - polygon_sector = hs.plot.markers.Polygons( - verts=original_offset[np.newaxis], - transform="axes", - alpha=1, - facecolor="none", - ) - if include_labels: - labels = _get_ipf_axes_labels( - sector.vertices, symmetry=self.simulation.phases.point_group - ) - tx, ty = s.vector2xy(sector.vertices) - texts_offset = np.vstack((tx, ty)).T - texts_offset = ( - (texts_offset - ((maxes + mins) / 2)) / (maxes - mins) * scale - ) - texts_offset = texts_offset + offset - texts = hs.plot.markers.Texts( - texts=labels, - offsets=texts_offset, - sizes=(1,), - offset_transform="axes", - facecolor="k", - ) - return polygon_sector, texts, maxes, mins def get_ipf_annotation_markers(self, offset: float = 0.85, scale: float = 0.2): """Get the outline of the IPF for the orientation map as a marker in the @@ -809,7 +812,7 @@ def get_ipf_annotation_markers(self, offset: float = 0.85, scale: float = 0.2): The color mesh for the IPF (using :class:`matplotlib.collections.QuadMesh`) """ - polygon_sector, texts, _, _ = self._get_ipf_outline(offset=offset, scale=scale) + polygon_sector, texts, _, _ = get_ipf_outline(self.simulation.phases, offset=offset, scale=scale) # Create Color Mesh color_key = DirectionColorKeyTSL(symmetry=self.simulation.phases.point_group) From 3719dfe54360365d00106c6aef3bd7915c590a1b Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 11 Jun 2024 20:07:16 +0200 Subject: [PATCH 03/19] *almost fix ipf markers for multi-phase --- pyxem/signals/indexation_results.py | 41 ++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index c723b3d5f..fe306608a 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -473,10 +473,11 @@ def to_vectors( """ if self.simulation.has_multiple_phases: + # With different phases, we cannot have a signal anymore, as the first dimension is now the phase + vectors_signal = np.array([sim for sim in self.simulation]) + else: # Use vector data as signal in case of different vectors per navigation position vectors_signal = hs.signals.Signal1D(self.simulation.coordinates) - else: - vectors_signal = hs.signals.Signal1D([sim for sim in self.simulations]) v = self.map( extract_vectors_from_orientation_map, all_vectors=vectors_signal, @@ -547,15 +548,30 @@ def to_ipf_markers(self, offset: float = 0.85, scale: float = 0.2): raise ValueError( "Cannot create markers from lazy signal. Please compute the signal first." ) - if self.simulation.has_multiple_phases: - raise ValueError("Multiple phases found in simulation") - polygon_sector, texts, maxes, mins = get_ipf_outline( - self.simulaiton.phases, offset=offset, scale=scale - ) - orients = self.to_single_phase_orientations() - vectors = orients * Vector3d.zvector() - vectors = vectors.in_fundamental_sector(self.simulation.phases.point_group) + rot = self.to_rotation() + vectors = rot * Vector3d.zvector() + phase_idxs = self.to_phase_index() + num_phases = np.max(phase_idxs) + 1 + polygon_sector = [None] * num_phases + texts = [None] * num_phases + maxes = [None] * num_phases + mins = [None] * num_phases + if phase_idxs is None: + vectors = vectors.in_fundamental_sector(self.simulation.phases.point_group) + phase_idxs = np.zeros(vectors.shape, dtype=int) + polygon_sector[0], texts[0], maxes[0], mins[0] = get_ipf_outline( + self.simulation.phases, offset=offset, scale=scale + ) + else: + + for i, phase in enumerate(self.simulation.phases): + vectors[phase_idxs == i] = vectors[phase_idxs == i].in_fundamental_sector(phase.point_group) + polygon_sector[i], texts[i], maxes[i], mins[i] = get_ipf_outline( + self.simulation.phases[i], offset=offset, scale=scale + ) + + s = StereographicProjection() x, y = s.vector2xy(vectors) x = x.reshape(vectors.shape) @@ -566,7 +582,8 @@ def to_ipf_markers(self, offset: float = 0.85, scale: float = 0.2): for i in np.ndindex(offsets.shape): off = np.vstack((x[i], y[i])).T - norm_points = (off - ((maxes + mins) / 2)) / (maxes - mins) * scale + phase_idx = phase_idxs[i][0] + norm_points = (off - ((maxes[phase_idx] + mins[phase_idx]) / 2)) / (maxes[phase_idx] - mins[phase_idx]) * scale norm_points = norm_points + offset offsets[i] = norm_points correlation[i] = cor[i] / np.max(cor[i]) * 0.5 @@ -588,7 +605,7 @@ def to_ipf_markers(self, offset: float = 0.85, scale: float = 0.2): facecolor="green", ) - return square, polygon_sector, best_points, texts + return square, *polygon_sector, best_points, *texts def to_markers( self, From 84f17b12939f2c315fa4632efe654eab094df5a8 Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 11 Jun 2024 20:07:16 +0200 Subject: [PATCH 04/19] Refactor and add multi-phase support for IPF markers --- pyxem/signals/indexation_results.py | 189 +++++++++++++++++----------- 1 file changed, 118 insertions(+), 71 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index fe306608a..9cc126a55 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -170,8 +170,13 @@ def _get_second_best_phase(z): else: return -1 + def get_ipf_outline( - phase: Phase, include_labels: bool = True, offset: float = 0.85, scale: float = 0.2 + phase: Phase, + include_labels: bool = True, + offset_x: float = 0.85, + offset_y: float = 0.85, + scale: float = 0.2, ): """Get the outline of the IPF for the orientation map as a marker in the upper right hand corner including labels if desired. @@ -198,6 +203,7 @@ def get_ipf_outline( mins : np.ndarray The minimum values for the axes """ + offset = np.array([offset_x, offset_y]) # Creating Lines around QuadMesh sector = phase.point_group.fundamental_sector s = StereographicProjection() @@ -207,7 +213,7 @@ def get_ipf_outline( original_offset = np.vstack((ex, ey)).T mins, maxes = original_offset.min(axis=0), original_offset.max(axis=0) original_offset = ( - (original_offset - ((maxes + mins) / 2)) / (maxes - mins) * scale + (original_offset - ((maxes + mins) / 2)) / (maxes.max() - mins.min()) * scale ) original_offset = original_offset + offset polygon_sector = hs.plot.markers.Polygons( @@ -217,13 +223,11 @@ def get_ipf_outline( facecolor="none", ) if include_labels: - labels = _get_ipf_axes_labels( - sector.vertices, symmetry=phase.point_group - ) + labels = _get_ipf_axes_labels(sector.vertices, symmetry=phase.point_group) tx, ty = s.vector2xy(sector.vertices) texts_offset = np.vstack((tx, ty)).T texts_offset = ( - (texts_offset - ((maxes + mins) / 2)) / (maxes - mins) * scale + (texts_offset - ((maxes + mins) / 2)) / (maxes.max() - mins.min()) * scale ) texts_offset = texts_offset + offset texts = hs.plot.markers.Texts( @@ -236,6 +240,76 @@ def get_ipf_outline( return polygon_sector, texts, maxes, mins +def vectors_to_single_phase_ipf_markers( + vectors: Vector3d, + phase: Phase, + correlation: np.ndarray, + offset_x: float = 0.85, + offset_y: float = 0.85, + scale: float = 0.2, +): + """Convert the orientation map to a set of inverse pole figure + markers which visualizes the best matching orientations in the + reduced S2 space. + + Parameters + ---------- + offset : float + The offset of the markers from the center of the plot + scale : float + The scale (as a fraction of the axis) for the markers. + """ + offset = np.array([offset_x, offset_y]) + + markers = [] + + vectors = vectors.in_fundamental_sector(phase.point_group) + polygon_sector, texts, maxes, mins = get_ipf_outline( + phase, offset_x=offset_x, offset_y=offset_y, scale=scale + ) + markers.append(polygon_sector) + markers.append(texts) + + s = StereographicProjection() + x, y = s.vector2xy(vectors) + x = x.reshape(vectors.shape) + y = y.reshape(vectors.shape) + offsets = np.empty(shape=vectors.shape[:-1], dtype=object) + alpha = np.empty(shape=vectors.shape[:-1], dtype=object) + + for i in np.ndindex(offsets.shape): + off = np.vstack((x[i], y[i])).T + norm_points = (off - ((maxes + mins) / 2)) / (maxes.max() - mins.min()) * scale + norm_points = norm_points + offset + offsets[i] = norm_points + max_cor = np.max(correlation[i]) + if max_cor == 0: + max_cor = 1 + alpha[i] = correlation[i] / max_cor * 0.5 + + square = hs.plot.markers.Squares( + offsets=[offset], + widths=(scale + scale / 2,), + units="width", + offset_transform="axes", + facecolor="white", + edgecolor="black", + ) + # Square needs to be on the bottom + markers.insert(0, square) + + best_points = hs.plot.markers.Points( + offsets=offsets.T, + sizes=(4,), + offset_transform="axes", + alpha=alpha.T, + facecolor="green", + ) + markers.append(best_points) + + return (*markers,) + + def vectors_to_coordinates(vectors): """ Convert a set of diffraction vectors to coordinates. For use with the map @@ -459,9 +533,7 @@ def to_single_phase_orientations(self, **kwargs) -> Orientation: symmetry=self.simulation.phases.point_group, ) - def to_vectors( - self, n_best_index: int = 0, **kwargs - ) -> hs.signals.Signal1D: + def to_vectors(self, n_best_index: int = 0, **kwargs) -> hs.signals.Signal1D: """Get the reciprocal lattice vectors for each navigation position. Parameters @@ -532,7 +604,12 @@ def to_crystal_map(self) -> CrystalMap: phase_list=phases, ) - def to_ipf_markers(self, offset: float = 0.85, scale: float = 0.2): + def to_ipf_markers( + self, + offset_x: float = 0.85, + offset_y: float = 0.85, + scale: float = 0.2, + ): """Convert the orientation map to a set of inverse pole figure markers which visualizes the best matching orientations in the reduced S2 space. @@ -544,68 +621,37 @@ def to_ipf_markers(self, offset: float = 0.85, scale: float = 0.2): scale : float The scale (as a fraction of the axis) for the markers. """ - if self._lazy: - raise ValueError( - "Cannot create markers from lazy signal. Please compute the signal first." + rots = self.to_rotation() + vecs = rots * Vector3d.zvector() + cors = self.data[..., 1] + if not self.simulation.has_multiple_phases: + vecs = vecs.in_fundamental_sector(self.simulation.phases) + return self._to_single_phase_ipf_markers( + vecs, + self.simulation.phases, + cors, + offset_x=offset_x, + offset_y=offset_y, + scale=scale, ) - - rot = self.to_rotation() - vectors = rot * Vector3d.zvector() + # Multiple phases, we make a few different IPFs + markers = [] phase_idxs = self.to_phase_index() - num_phases = np.max(phase_idxs) + 1 - polygon_sector = [None] * num_phases - texts = [None] * num_phases - maxes = [None] * num_phases - mins = [None] * num_phases - if phase_idxs is None: - vectors = vectors.in_fundamental_sector(self.simulation.phases.point_group) - phase_idxs = np.zeros(vectors.shape, dtype=int) - polygon_sector[0], texts[0], maxes[0], mins[0] = get_ipf_outline( - self.simulation.phases, offset=offset, scale=scale - ) - else: - - for i, phase in enumerate(self.simulation.phases): - vectors[phase_idxs == i] = vectors[phase_idxs == i].in_fundamental_sector(phase.point_group) - polygon_sector[i], texts[i], maxes[i], mins[i] = get_ipf_outline( - self.simulation.phases[i], offset=offset, scale=scale + for phase_idx, phase in enumerate(self.simulation.phases): + phase_vecs = vecs.in_fundamental_sector(phase.point_group) + # As a workaround, set alpha to 0 for the wrong phase + phase_cors = cors * (phase_idxs == phase_idx) + markers += list( + self._to_single_phase_ipf_markers( + phase_vecs, + phase, + phase_cors, + offset_x=offset_x, + offset_y=offset_y - 1.5 * scale * phase_idx, + scale=scale, ) - - - s = StereographicProjection() - x, y = s.vector2xy(vectors) - x = x.reshape(vectors.shape) - y = y.reshape(vectors.shape) - cor = self.data[..., 1] - offsets = np.empty(shape=vectors.shape[:-1], dtype=object) - correlation = np.empty(shape=vectors.shape[:-1], dtype=object) - - for i in np.ndindex(offsets.shape): - off = np.vstack((x[i], y[i])).T - phase_idx = phase_idxs[i][0] - norm_points = (off - ((maxes[phase_idx] + mins[phase_idx]) / 2)) / (maxes[phase_idx] - mins[phase_idx]) * scale - norm_points = norm_points + offset - offsets[i] = norm_points - correlation[i] = cor[i] / np.max(cor[i]) * 0.5 - - square = hs.plot.markers.Squares( - offsets=[[offset, offset]], - widths=(scale + scale / 2,), - units="width", - offset_transform="axes", - facecolor="white", - edgecolor="black", - ) - - best_points = hs.plot.markers.Points( - offsets=offsets.T, - sizes=(4,), - offset_transform="axes", - alpha=correlation.T, - facecolor="green", - ) - - return square, *polygon_sector, best_points, *texts + ) + return markers def to_markers( self, @@ -806,7 +852,6 @@ def marker_generator(entry): all_markers = compute_markers(all_markers) return all_markers - def get_ipf_annotation_markers(self, offset: float = 0.85, scale: float = 0.2): """Get the outline of the IPF for the orientation map as a marker in the upper right hand corner including labels if desired. As well as the color @@ -829,7 +874,9 @@ def get_ipf_annotation_markers(self, offset: float = 0.85, scale: float = 0.2): The color mesh for the IPF (using :class:`matplotlib.collections.QuadMesh`) """ - polygon_sector, texts, _, _ = get_ipf_outline(self.simulation.phases, offset=offset, scale=scale) + polygon_sector, texts, _, _ = get_ipf_outline( + self.simulation.phases, offset=offset, scale=scale + ) # Create Color Mesh color_key = DirectionColorKeyTSL(symmetry=self.simulation.phases.point_group) From f2da734b1857321252148faabdbc00bc5e0f6f3a Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 11 Jun 2024 20:07:16 +0200 Subject: [PATCH 05/19] Implement `to_phase_map` --- pyxem/signals/indexation_results.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 9cc126a55..829148905 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -947,6 +947,29 @@ def to_ipf_colormap( plot_on_signal=False, ) return s + + def to_phase_map(self): + """Create a colored navigator which can be passed as the + navigator argument to the `plot` method of some signal. + + Returns + ------- + hs.signals.BaseSignal + """ + if not self.simulation.has_multiple_phases: + raise ValueError("Only a single phase present in simulation") + + phase_idxs = self.to_phase_index() + colors = [p.color_rgb for p in self.simulation.phases] + + float_rgb = np.take(colors, phase_idxs[..., 0], axis=0) + print(float_rgb.shape) + int_rgb = (float_rgb * 255).astype(np.uint8) + + s = hs.signals.Signal1D(int_rgb) + s.change_dtype("rgb8") + s = s.T + return s def plot_over_signal( self, @@ -975,7 +998,11 @@ def plot_over_signal( Additional keyword arguments to pass to the :meth:`hyperspy.api.signals.Signal2D.plot` method """ - nav = self.to_ipf_colormap(add_markers=False) + if self.simulation.has_multiple_phases: + nav = self.to_phase_map() + add_ipf_colorkey = False + else: + nav = self.to_ipf_colormap(add_markers=False) if vector_kwargs is None: vector_kwargs = dict() signal.plot(navigator=nav, **kwargs) From 6ac9e2eb4e942a0715a6150f24274dbf6f8379b6 Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 11 Jun 2024 20:46:51 +0200 Subject: [PATCH 06/19] Verify plot_over_signal works --- pyxem/signals/indexation_results.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 829148905..8c810cecf 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -366,7 +366,7 @@ def rotation_from_orientation_map(result, rots): return ori -def extract_vectors_from_orientation_map(result, all_vectors, n_best_index=0): +def vectors_from_orientation_map(result, all_vectors, n_best_index=0): index, _, rotation, mirror = result[n_best_index, :].T index = index.astype(int) if all_vectors.ndim == 0: @@ -383,6 +383,9 @@ def extract_vectors_from_orientation_map(result, all_vectors, n_best_index=0): rotation = Rotation.from_euler( (mirror * rotation, 0, 0), degrees=True, direction="crystal2lab" ) + assert len(vectors.shape) == 1 + assert vectors.size > 1 + assert rotation.data.size == 4 vectors = ~rotation * vectors.to_miller() vectors = DiffractingVector( vectors.phase, xyz=vectors.data.copy(), intensity=intensity @@ -551,7 +554,7 @@ def to_vectors(self, n_best_index: int = 0, **kwargs) -> hs.signals.Signal1D: # Use vector data as signal in case of different vectors per navigation position vectors_signal = hs.signals.Signal1D(self.simulation.coordinates) v = self.map( - extract_vectors_from_orientation_map, + vectors_from_orientation_map, all_vectors=vectors_signal, inplace=False, output_signal_size=(), @@ -626,7 +629,7 @@ def to_ipf_markers( cors = self.data[..., 1] if not self.simulation.has_multiple_phases: vecs = vecs.in_fundamental_sector(self.simulation.phases) - return self._to_single_phase_ipf_markers( + return vectors_to_single_phase_ipf_markers( vecs, self.simulation.phases, cors, @@ -642,7 +645,7 @@ def to_ipf_markers( # As a workaround, set alpha to 0 for the wrong phase phase_cors = cors * (phase_idxs == phase_idx) markers += list( - self._to_single_phase_ipf_markers( + vectors_to_single_phase_ipf_markers( phase_vecs, phase, phase_cors, @@ -963,7 +966,6 @@ def to_phase_map(self): colors = [p.color_rgb for p in self.simulation.phases] float_rgb = np.take(colors, phase_idxs[..., 0], axis=0) - print(float_rgb.shape) int_rgb = (float_rgb * 255).astype(np.uint8) s = hs.signals.Signal1D(int_rgb) From 2c60a1ef9e17f9a6e5b02508ff66731d15c62a9d Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 11 Jun 2024 21:23:08 +0200 Subject: [PATCH 07/19] Refactor ipf colormap marker --- pyxem/signals/indexation_results.py | 125 ++++++++++++++-------------- 1 file changed, 64 insertions(+), 61 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 8c810cecf..27697bd1e 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -240,6 +240,63 @@ def get_ipf_outline( return polygon_sector, texts, maxes, mins +def get_ipf_annotation_markers(phase: Phase, offset: float = 0.85, scale: float = 0.2): + """Get the outline of the IPF for the orientation map as a marker in the + upper right hand corner including labels if desired. As well as the color + mesh for the IPF. + + Parameters + ---------- + offset : float + The offset of the markers from the lower left of the plot (as a fraction of the axis). + scale : float + The scale (as a fraction of the axis) for the markers. + + Returns + ------- + polygon_sector : hs.plot.markers.Polygons + The outline of the IPF as a marker + texts : hs.plot.markers.Texts + The text labels for the IPF axes + mesh : hs.plot.markers.Markers + The color mesh for the IPF (using :class:`matplotlib.collections.QuadMesh`) + """ + + polygon_sector, texts, _, _ = get_ipf_outline( + phase, offset_x=offset, offset_y=offset, scale=scale + ) + + # Create Color Mesh + color_key = DirectionColorKeyTSL(symmetry=phase.point_group) + g, ext = color_key._create_rgba_grid(return_extent=True) + + max_x = np.max(ext[1]) + min_x = np.min(ext[1]) + + max_y = np.max(ext[0]) + min_y = np.min(ext[0]) + + # center extent: + y = np.linspace(ext[0][0], ext[0][1], g.shape[1] + 1) - ((max_y + min_y) / 2) + + y = y / (max_y - min_y) * scale + offset + + x = np.linspace(ext[1][1], ext[1][0], g.shape[0] + 1) - ((max_x + min_x) / 2) + + x = x / (max_x - min_x) * scale + offset + xx, yy = np.meshgrid(y, x) + + mesh = hs.plot.markers.Markers( + collection=QuadMesh, + coordinates=np.stack((xx, yy), axis=-1), + array=g, + transform="axes", + offset_transform="display", + offsets=[[0, 0]], + ) + return polygon_sector, mesh, texts + + def vectors_to_single_phase_ipf_markers( vectors: Vector3d, phase: Phase, @@ -628,7 +685,7 @@ def to_ipf_markers( vecs = rots * Vector3d.zvector() cors = self.data[..., 1] if not self.simulation.has_multiple_phases: - vecs = vecs.in_fundamental_sector(self.simulation.phases) + vecs = vecs.in_fundamental_sector(self.simulation.phases.point_group) return vectors_to_single_phase_ipf_markers( vecs, self.simulation.phases, @@ -855,62 +912,6 @@ def marker_generator(entry): all_markers = compute_markers(all_markers) return all_markers - def get_ipf_annotation_markers(self, offset: float = 0.85, scale: float = 0.2): - """Get the outline of the IPF for the orientation map as a marker in the - upper right hand corner including labels if desired. As well as the color - mesh for the IPF. - - Parameters - ---------- - offset : float - The offset of the markers from the lower left of the plot (as a fraction of the axis). - scale : float - The scale (as a fraction of the axis) for the markers. - - Returns - ------- - polygon_sector : hs.plot.markers.Polygons - The outline of the IPF as a marker - texts : hs.plot.markers.Texts - The text labels for the IPF axes - mesh : hs.plot.markers.Markers - The color mesh for the IPF (using :class:`matplotlib.collections.QuadMesh`) - """ - - polygon_sector, texts, _, _ = get_ipf_outline( - self.simulation.phases, offset=offset, scale=scale - ) - - # Create Color Mesh - color_key = DirectionColorKeyTSL(symmetry=self.simulation.phases.point_group) - g, ext = color_key._create_rgba_grid(return_extent=True) - - max_x = np.max(ext[1]) - min_x = np.min(ext[1]) - - max_y = np.max(ext[0]) - min_y = np.min(ext[0]) - - # center extent: - y = np.linspace(ext[0][0], ext[0][1], g.shape[1] + 1) - ((max_y + min_y) / 2) - - y = y / (max_y - min_y) * scale + offset - - x = np.linspace(ext[1][1], ext[1][0], g.shape[0] + 1) - ((max_x + min_x) / 2) - - x = x / (max_x - min_x) * scale + offset - xx, yy = np.meshgrid(y, x) - - mesh = hs.plot.markers.Markers( - collection=QuadMesh, - coordinates=np.stack((xx, yy), axis=-1), - array=g, - transform="axes", - offset_transform="display", - offsets=[[0, 0]], - ) - return polygon_sector, mesh, texts - def to_ipf_colormap( self, direction: Vector3d = Vector3d.zvector(), @@ -941,7 +942,7 @@ def to_ipf_colormap( s = s.T if add_markers: - annotations = self.get_ipf_annotation_markers() + annotations = get_ipf_annotation_markers(self.simulation.phases) s.add_marker( annotations, permanent=True, @@ -950,7 +951,7 @@ def to_ipf_colormap( plot_on_signal=False, ) return s - + def to_phase_map(self): """Create a colored navigator which can be passed as the navigator argument to the `plot` method of some signal. @@ -961,7 +962,7 @@ def to_phase_map(self): """ if not self.simulation.has_multiple_phases: raise ValueError("Only a single phase present in simulation") - + phase_idxs = self.to_phase_index() colors = [p.color_rgb for p in self.simulation.phases] @@ -1014,7 +1015,9 @@ def plot_over_signal( ipf_markers = self.to_ipf_markers() signal.add_marker(ipf_markers) if add_ipf_colorkey: - signal.add_marker(self.get_ipf_annotation_markers(), plot_on_signal=False) + signal.add_marker( + get_ipf_annotation_markers(self.simulation.phases), plot_on_signal=False + ) class GenericMatchingResults: From abbaf3f26e05ea2c829071886c6a059b783671bd Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 11 Jun 2024 21:23:14 +0200 Subject: [PATCH 08/19] Fix tests --- .../tests/signals/test_indexation_results.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/pyxem/tests/signals/test_indexation_results.py b/pyxem/tests/signals/test_indexation_results.py index ca0b8c23a..9c2192e49 100644 --- a/pyxem/tests/signals/test_indexation_results.py +++ b/pyxem/tests/signals/test_indexation_results.py @@ -336,12 +336,6 @@ def test_to_ipf_markers(self, simple_multi_rot_orientation_result): markers = orientations.to_ipf_markers() assert isinstance(markers[0], hs.plot.markers.Markers) - def test_to_ipf_annotation(self, simple_multi_rot_orientation_result): - orientations, rotations, s = simple_multi_rot_orientation_result - annotations = orientations.get_ipf_annotation_markers() - for a in annotations: - assert isinstance(a, hs.plot.markers.Markers) - @pytest.mark.parametrize("add_markers", [True, False]) def test_to_ipf_map(self, simple_multi_rot_orientation_result, add_markers): orientations, rotations, s = simple_multi_rot_orientation_result @@ -350,15 +344,15 @@ def test_to_ipf_map(self, simple_multi_rot_orientation_result, add_markers): if add_markers: assert len(navigator.metadata.Markers) == 3 + def test_to_phasemap(self, multi_phase_orientation_result): + navigator = multi_phase_orientation_result.to_phase_map() + assert isinstance(navigator, hs.signals.BaseSignal) + def test_multi_phase_errors(self, multi_phase_orientation_result): with pytest.raises(ValueError): multi_phase_orientation_result.to_ipf_colormap() - with pytest.raises(ValueError): - multi_phase_orientation_result.to_single_phase_vectors() with pytest.raises(ValueError): multi_phase_orientation_result.to_single_phase_orientations() - with pytest.raises(ValueError): - multi_phase_orientation_result.to_ipf_markers() def test_lazy_error(self, simple_multi_rot_orientation_result): orientations, rotations, s = simple_multi_rot_orientation_result @@ -377,3 +371,12 @@ def test_to_crystal_map_error(self, simple_multi_rot_orientation_result): def test_plot_over_signal(self, simple_multi_rot_orientation_result): orientations, rotations, s = simple_multi_rot_orientation_result orientations.plot_over_signal(s) + + def test_plot_over_signal_multi_phase(self, multi_phase_orientation_result): + # Mock signal + s = hs.signals.Signal2D( + np.zeros(multi_phase_orientation_result.data.shape[:-2])[ + ..., np.newaxis, np.newaxis + ] + ) + multi_phase_orientation_result.plot_over_signal(s) From aa617c9a192cd5ed0a9b510db6fe01e1a457555a Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 11 Jun 2024 21:32:15 +0200 Subject: [PATCH 09/19] Remove leftover asserts from development --- pyxem/signals/indexation_results.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 27697bd1e..38ff819f7 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -440,9 +440,7 @@ def vectors_from_orientation_map(result, all_vectors, n_best_index=0): rotation = Rotation.from_euler( (mirror * rotation, 0, 0), degrees=True, direction="crystal2lab" ) - assert len(vectors.shape) == 1 - assert vectors.size > 1 - assert rotation.data.size == 4 + vectors = ~rotation * vectors.to_miller() vectors = DiffractingVector( vectors.phase, xyz=vectors.data.copy(), intensity=intensity From d4900c117d7a0db5b187abc3e0bb6b291b99e95a Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Wed, 12 Jun 2024 11:43:04 +0200 Subject: [PATCH 10/19] Add support for color map for correlation scores --- pyxem/signals/indexation_results.py | 50 +++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 38ff819f7..a914d60a3 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -43,6 +43,7 @@ import hyperspy.api as hs import numpy as np from matplotlib.collections import QuadMesh +from matplotlib.colors import Normalize import numpy as np import hyperspy.api as hs @@ -300,10 +301,12 @@ def get_ipf_annotation_markers(phase: Phase, offset: float = 0.85, scale: float def vectors_to_single_phase_ipf_markers( vectors: Vector3d, phase: Phase, - correlation: np.ndarray, + normalized_correlation: np.ndarray, offset_x: float = 0.85, offset_y: float = 0.85, scale: float = 0.2, + color: str = "green", + cmap: str = None, ): """Convert the orientation map to a set of inverse pole figure markers which visualizes the best matching orientations in the @@ -311,6 +314,13 @@ def vectors_to_single_phase_ipf_markers( Parameters ---------- + vectors : Vector3d + The vectors to display. These will be projected into the fundamental sector + phase : Phase + The phase for the fundamental sector + normalized_correrlation : np.ndarray + Correlation scores, normalized to lie between 0 and 1. + Used for alpha for single-color plotting, and for the color map offset : float The offset of the markers from the center of the plot scale : float @@ -332,17 +342,16 @@ def vectors_to_single_phase_ipf_markers( x = x.reshape(vectors.shape) y = y.reshape(vectors.shape) offsets = np.empty(shape=vectors.shape[:-1], dtype=object) - alpha = np.empty(shape=vectors.shape[:-1], dtype=object) + alpha = np.ones(shape=vectors.shape[:-1], dtype=object) + # For each set of n_best, we reverse the order (using [::-1]) + # to ensure the best spot is displayed on top for i in np.ndindex(offsets.shape): off = np.vstack((x[i], y[i])).T norm_points = (off - ((maxes + mins) / 2)) / (maxes.max() - mins.min()) * scale norm_points = norm_points + offset - offsets[i] = norm_points - max_cor = np.max(correlation[i]) - if max_cor == 0: - max_cor = 1 - alpha[i] = correlation[i] / max_cor * 0.5 + offsets[i] = norm_points[::-1] + alpha[i] = normalized_correlation[i][::-1] square = hs.plot.markers.Squares( offsets=[offset], @@ -360,7 +369,10 @@ def vectors_to_single_phase_ipf_markers( sizes=(4,), offset_transform="axes", alpha=alpha.T, - facecolor="green", + facecolor=color, + cmap=cmap, + array=None if cmap is None else alpha.T, + norm=Normalize(0, 1), ) markers.append(best_points) @@ -667,6 +679,8 @@ def to_ipf_markers( offset_x: float = 0.85, offset_y: float = 0.85, scale: float = 0.2, + color: str = "green", + cmap: str = None, ): """Convert the orientation map to a set of inverse pole figure markers which visualizes the best matching orientations in the @@ -678,12 +692,17 @@ def to_ipf_markers( The offset of the markers from the center of the plot scale : float The scale (as a fraction of the axis) for the markers. + color : str + The color of the markers. Overridden by cmap, if not None + cmap : str + Use a color map to show correlation scores. + Takes priority over the `color` parameter """ rots = self.to_rotation() vecs = rots * Vector3d.zvector() - cors = self.data[..., 1] + # Normalize the correlation scores + cors = self.data[..., 1] / np.max(self.data[..., 1], axis=-1)[..., np.newaxis] if not self.simulation.has_multiple_phases: - vecs = vecs.in_fundamental_sector(self.simulation.phases.point_group) return vectors_to_single_phase_ipf_markers( vecs, self.simulation.phases, @@ -691,22 +710,25 @@ def to_ipf_markers( offset_x=offset_x, offset_y=offset_y, scale=scale, + color=color, + cmap=cmap, ) # Multiple phases, we make a few different IPFs markers = [] phase_idxs = self.to_phase_index() for phase_idx, phase in enumerate(self.simulation.phases): - phase_vecs = vecs.in_fundamental_sector(phase.point_group) # As a workaround, set alpha to 0 for the wrong phase phase_cors = cors * (phase_idxs == phase_idx) markers += list( vectors_to_single_phase_ipf_markers( - phase_vecs, + vecs, phase, phase_cors, offset_x=offset_x, offset_y=offset_y - 1.5 * scale * phase_idx, scale=scale, + color=color, + cmap=cmap, ) ) return markers @@ -979,6 +1001,7 @@ def plot_over_signal( add_ipf_markers=True, add_ipf_colorkey=True, vector_kwargs=None, + ipf_marker_show_correlation=False, **kwargs, ): """Convenience method to plot the orientation map and the n-best matches over the signal. @@ -1010,7 +1033,8 @@ def plot_over_signal( if add_vector_markers: signal.add_marker(self.to_markers(1, **vector_kwargs)) if add_ipf_markers: - ipf_markers = self.to_ipf_markers() + cmap = "magma" if ipf_marker_show_correlation else None + ipf_markers = self.to_ipf_markers(cmap=cmap) signal.add_marker(ipf_markers) if add_ipf_colorkey: signal.add_marker( From 8805aedf415075056c922f8025805cb10fb9d56d Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Thu, 13 Jun 2024 12:40:58 +0200 Subject: [PATCH 11/19] Add correlation heatmap marker --- pyxem/signals/indexation_results.py | 136 ++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index a914d60a3..170c4f682 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -44,6 +44,7 @@ import numpy as np from matplotlib.collections import QuadMesh from matplotlib.colors import Normalize +from scipy.spatial import Delaunay import numpy as np import hyperspy.api as hs @@ -733,6 +734,141 @@ def to_ipf_markers( ) return markers + def get_ipf_correlation_heatmap( + self, + offset_x: float = 0.85, + offset_y: float = 0.85, + scale: float = 0.2, + cmap: str = "inferno", + ): + """Get the outline of the IPF for the orientation map as a marker in the + upper right hand corner including labels if desired. As well as the color + mesh for the IPF. + + Parameters + ---------- + offset : float + The offset of the markers from the lower left of the plot (as a fraction of the axis). + scale : float + The scale (as a fraction of the axis) for the markers. + + Returns + ------- + polygon_sector : hs.plot.markers.Polygons + The outline of the IPF as a marker + texts : hs.plot.markers.Texts + The text labels for the IPF axes + mesh : hs.plot.markers.Markers + The color mesh for the IPF (using :class:`matplotlib.collections.QuadMesh`) + + Notes + ----- + This will not look good if the rotations used in the simulation(s) consists of + multiple different regions in the IPF. + """ + phase_idx_signal = hs.signals.Signal1D(self.to_phase_index()) + phases = self.simulation.phases + rotations = self.simulation.rotations + if not self.simulation.has_multiple_phases: + phases = [phases] + rotations = [rotations] + phase_idx_signal = 0 + + s = StereographicProjection() + rotation_sizes = np.cumsum([0] + [r.size for r in rotations]) + + markers = [] + + for phase_idx, phase in enumerate(phases): + offset = np.array([offset_x, offset_y]) + polygon_sector, texts, maxs, mins = get_ipf_outline( + phase, offset_x=offset_x, offset_y=offset_y, scale=scale + ) + # We make a mesh of the correlation scores, + # But we mask out the nj", temp[:, :-1, :], delta) + weights = np.hstack((bary, 1 - bary.sum(axis=1, keepdims=True))) + + def interpolate(values): + return np.einsum("nj,nj->n", np.take(values, vertices), weights) + + def interpolate_and_mask_correlations(result, phase_idxs): + indices, correlation, _, _ = result[phase_idxs == phase_idx].T + max_cor = result[:, 1].max() + indices = indices.astype(int) - rotation_sizes[phase_idx] - 1 + all_correlations = np.full(x.shape, np.nan) + all_correlations[indices] = correlation / max_cor + out = interpolate(all_correlations) + out[outside] = np.nan + return out + + g = self.map( + interpolate_and_mask_correlations, + phase_idxs=phase_idx_signal, + inplace=False, + ragged=True, + ).data + + # Scale the coordinates + out_xy = (out_xy - ((maxs + mins) / 2)) / (maxs.max() - mins.min()) * scale + out_xy += offset + + mesh = hs.plot.markers.Markers( + collection=QuadMesh, + coordinates=out_xy, + array=g.T, + transform="axes", + offset_transform="display", + offsets=[[0, 0]], + norm=Normalize(0, 1), + cmap=cmap, + ) + square = hs.plot.markers.Squares( + offsets=[offset], + widths=(scale + scale / 2,), + units="width", + offset_transform="axes", + facecolor="white", + edgecolor="black", + ) + + markers.append(square) + markers.append(polygon_sector) + markers.append(mesh) + markers.append(texts) + + offset_y -= 1.5 * scale + return (*markers,) + def to_markers( self, n_best: int = 1, From 135394aa159ba70092e30fb902f0dd8d5f3ae669 Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Thu, 13 Jun 2024 12:47:34 +0200 Subject: [PATCH 12/19] Update function name, and add note in `to_ipf_markers` --- pyxem/signals/indexation_results.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 170c4f682..06491c700 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -697,7 +697,12 @@ def to_ipf_markers( The color of the markers. Overridden by cmap, if not None cmap : str Use a color map to show correlation scores. - Takes priority over the `color` parameter + Takes priority over the `color` parameter. + + Notes + ----- + This function can be slow with a large n_keep. + Use `to_ipf_correlation_heatmap_markers` instead. """ rots = self.to_rotation() vecs = rots * Vector3d.zvector() @@ -734,7 +739,7 @@ def to_ipf_markers( ) return markers - def get_ipf_correlation_heatmap( + def to_ipf_correlation_heatmap_markers( self, offset_x: float = 0.85, offset_y: float = 0.85, @@ -760,7 +765,7 @@ def get_ipf_correlation_heatmap( The text labels for the IPF axes mesh : hs.plot.markers.Markers The color mesh for the IPF (using :class:`matplotlib.collections.QuadMesh`) - + Notes ----- This will not look good if the rotations used in the simulation(s) consists of From f192d7fdabd810110047378448a770bcd0b3af07 Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Thu, 13 Jun 2024 12:47:43 +0200 Subject: [PATCH 13/19] Add tests --- pyxem/tests/signals/test_indexation_results.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pyxem/tests/signals/test_indexation_results.py b/pyxem/tests/signals/test_indexation_results.py index 9c2192e49..97b443ae2 100644 --- a/pyxem/tests/signals/test_indexation_results.py +++ b/pyxem/tests/signals/test_indexation_results.py @@ -380,3 +380,16 @@ def test_plot_over_signal_multi_phase(self, multi_phase_orientation_result): ] ) multi_phase_orientation_result.plot_over_signal(s) + + def test_to_ipf_correlation_heatmap_markers_single_phase( + self, simple_multi_rot_orientation_result + ): + orientations, rotations, s = simple_multi_rot_orientation_result + markers = orientations.to_ipf_correlation_heatmap_markers() + assert all(isinstance(m, hs.plot.markers.Markers) for m in markers) + + def test_to_ipf_correlation_heatmap_markers_multi_phase( + self, multi_phase_orientation_result + ): + markers = multi_phase_orientation_result.to_ipf_correlation_heatmap_markers() + assert all(isinstance(m, hs.plot.markers.Markers) for m in markers) From ea63284737f0781041a53a46ff3d4b9da8c14ed3 Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Thu, 13 Jun 2024 13:53:26 +0200 Subject: [PATCH 14/19] Add option for heatmap in plot_over_signal --- pyxem/signals/indexation_results.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 06491c700..b854b70fe 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -768,8 +768,11 @@ def to_ipf_correlation_heatmap_markers( Notes ----- + It is recommended to have a large `n_keep` for this to look nice, e.g. 75% of the simulation bank. + This will not look good if the rotations used in the simulation(s) consists of multiple different regions in the IPF. + """ phase_idx_signal = hs.signals.Signal1D(self.to_phase_index()) phases = self.simulation.phases @@ -1140,9 +1143,9 @@ def plot_over_signal( signal, add_vector_markers=True, add_ipf_markers=True, + add_ipf_correlation_heatmap=False, add_ipf_colorkey=True, vector_kwargs=None, - ipf_marker_show_correlation=False, **kwargs, ): """Convenience method to plot the orientation map and the n-best matches over the signal. @@ -1155,6 +1158,9 @@ def plot_over_signal( If True, the vector markers will be added to the signal. add_ipf_markers : bool If True, the IPF best fit will be added to the signal in an overlay + add_ipf_correlation_heatmap : bool + If True, a correlation score heatmap as an IPF will be added to the signal in an overlay. + This overrides the `add_ipf_markers` parameter. add_ipf_colorkey : bool If True, the IPF colorkey will be added to the signal vector_kwargs : dict @@ -1173,10 +1179,12 @@ def plot_over_signal( signal.plot(navigator=nav, **kwargs) if add_vector_markers: signal.add_marker(self.to_markers(1, **vector_kwargs)) - if add_ipf_markers: - cmap = "magma" if ipf_marker_show_correlation else None - ipf_markers = self.to_ipf_markers(cmap=cmap) + if add_ipf_markers and not add_ipf_correlation_heatmap: + ipf_markers = self.to_ipf_markers() signal.add_marker(ipf_markers) + if add_ipf_correlation_heatmap: + heatmap_markers = self.to_ipf_correlation_heatmap_markers() + signal.add_marker(heatmap_markers) if add_ipf_colorkey: signal.add_marker( get_ipf_annotation_markers(self.simulation.phases), plot_on_signal=False From 852c9d077da8f1869d10ab693d4aada208c9e99f Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Thu, 13 Jun 2024 14:18:34 +0200 Subject: [PATCH 15/19] Increase test coverage --- pyxem/signals/indexation_results.py | 2 +- .../tests/signals/test_indexation_results.py | 52 +++++++++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index b854b70fe..3bfb917e3 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -772,7 +772,7 @@ def to_ipf_correlation_heatmap_markers( This will not look good if the rotations used in the simulation(s) consists of multiple different regions in the IPF. - + """ phase_idx_signal = hs.signals.Signal1D(self.to_phase_index()) phases = self.simulation.phases diff --git a/pyxem/tests/signals/test_indexation_results.py b/pyxem/tests/signals/test_indexation_results.py index 97b443ae2..3aae69683 100644 --- a/pyxem/tests/signals/test_indexation_results.py +++ b/pyxem/tests/signals/test_indexation_results.py @@ -368,18 +368,62 @@ def test_to_crystal_map_error(self, simple_multi_rot_orientation_result): with pytest.raises(ValueError): rotations = orientations.to_crystal_map() - def test_plot_over_signal(self, simple_multi_rot_orientation_result): + @pytest.mark.parametrize("add_vector_markers", [False, True]) + @pytest.mark.parametrize("add_ipf_markers", [False, True]) + @pytest.mark.parametrize("add_ipf_correlation_heatmap", [False, True]) + @pytest.mark.parametrize("add_ipf_colorkey", [False, True]) + @pytest.mark.parametrize( + "vector_kwargs", [None, {"annotate": False}, {"annotate": True}] + ) + def test_plot_over_single_phase_signal( + self, + simple_multi_rot_orientation_result, + add_vector_markers, + add_ipf_markers, + add_ipf_correlation_heatmap, + add_ipf_colorkey, + vector_kwargs, + ): orientations, rotations, s = simple_multi_rot_orientation_result - orientations.plot_over_signal(s) + orientations.plot_over_signal( + s, + add_vector_markers=add_vector_markers, + add_ipf_markers=add_ipf_markers, + add_ipf_correlation_heatmap=add_ipf_correlation_heatmap, + add_ipf_colorkey=add_ipf_colorkey, + vector_kwargs=vector_kwargs, + ) - def test_plot_over_signal_multi_phase(self, multi_phase_orientation_result): + @pytest.mark.parametrize("add_vector_markers", [False, True]) + @pytest.mark.parametrize("add_ipf_markers", [False, True]) + @pytest.mark.parametrize("add_ipf_correlation_heatmap", [False, True]) + @pytest.mark.parametrize("add_ipf_colorkey", [False, True]) + @pytest.mark.parametrize( + "vector_kwargs", [None, {"annotate": False}, {"annotate": True}] + ) + def test_plot_over_multi_phase_signal( + self, + multi_phase_orientation_result, + add_vector_markers, + add_ipf_markers, + add_ipf_correlation_heatmap, + add_ipf_colorkey, + vector_kwargs, + ): # Mock signal s = hs.signals.Signal2D( np.zeros(multi_phase_orientation_result.data.shape[:-2])[ ..., np.newaxis, np.newaxis ] ) - multi_phase_orientation_result.plot_over_signal(s) + multi_phase_orientation_result.plot_over_signal( + s, + add_vector_markers=add_vector_markers, + add_ipf_markers=add_ipf_markers, + add_ipf_correlation_heatmap=add_ipf_correlation_heatmap, + add_ipf_colorkey=add_ipf_colorkey, + vector_kwargs=vector_kwargs, + ) def test_to_ipf_correlation_heatmap_markers_single_phase( self, simple_multi_rot_orientation_result From 284fbc31f949df4b91db19930eec43bcb1c746c2 Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Thu, 13 Jun 2024 15:20:07 +0200 Subject: [PATCH 16/19] Fix phase map with `n_best = 1` --- pyxem/signals/indexation_results.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 3bfb917e3..9a65f6dbd 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -1128,6 +1128,8 @@ def to_phase_map(self): raise ValueError("Only a single phase present in simulation") phase_idxs = self.to_phase_index() + # in case n_best = 1 + phase_idxs = phase_idxs.reshape(*self.axes_manager._navigation_shape_in_array, -1) colors = [p.color_rgb for p in self.simulation.phases] float_rgb = np.take(colors, phase_idxs[..., 0], axis=0) From 18b405b5ce8bb7a5190db42e2dc7b3e4fb5af925 Mon Sep 17 00:00:00 2001 From: Viljar Femoen Date: Tue, 13 Aug 2024 09:22:31 +0200 Subject: [PATCH 17/19] Fix formatting --- pyxem/signals/indexation_results.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index 9a65f6dbd..fe491bcb3 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -1129,7 +1129,9 @@ def to_phase_map(self): phase_idxs = self.to_phase_index() # in case n_best = 1 - phase_idxs = phase_idxs.reshape(*self.axes_manager._navigation_shape_in_array, -1) + phase_idxs = phase_idxs.reshape( + *self.axes_manager._navigation_shape_in_array, -1 + ) colors = [p.color_rgb for p in self.simulation.phases] float_rgb = np.take(colors, phase_idxs[..., 0], axis=0) From 575f6427c70fb2e84405296d75ead9c351e1f21c Mon Sep 17 00:00:00 2001 From: Viljar Femoen <57151700+viljarjf@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:18:25 +0200 Subject: [PATCH 18/19] Add deprecations --- pyxem/signals/indexation_results.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index fe491bcb3..dde00105d 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -436,6 +436,15 @@ def rotation_from_orientation_map(result, rots): return ori +@deprecated( + since="0.20", + removal="1.0.0", + alternative="pyxem.signals.indexation_results.vectors_from_orientation_map", +) +def extract_vectors_from_orientation_map(result, all_vectors, n_best_index=0): + return vectors_from_orientation_map(result, all_vectors, n_best_index=n_best_index) + + def vectors_from_orientation_map(result, all_vectors, n_best_index=0): index, _, rotation, mirror = result[n_best_index, :].T index = index.astype(int) @@ -604,6 +613,16 @@ def to_single_phase_orientations(self, **kwargs) -> Orientation: symmetry=self.simulation.phases.point_group, ) + @deprecated( + since="0.20", + removal="1.0.0", + alternative="pyxem.signals.OrientationMap.to_vectors", + ) + def to_single_phase_vectors( + self, n_best_index: int = 0, **kwargs + ) -> hs.signals.Signal1D: + return self.to_vectors(n_best_index=n_best_index, **kwargs) + def to_vectors(self, n_best_index: int = 0, **kwargs) -> hs.signals.Signal1D: """Get the reciprocal lattice vectors for each navigation position. From 7a49616751161ea8fb440195d7e4ed28aac123d4 Mon Sep 17 00:00:00 2001 From: Viljar Femoen <57151700+viljarjf@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:41:25 +0200 Subject: [PATCH 19/19] Import deprecation decorator --- pyxem/signals/indexation_results.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyxem/signals/indexation_results.py b/pyxem/signals/indexation_results.py index dde00105d..bbd0d1719 100644 --- a/pyxem/signals/indexation_results.py +++ b/pyxem/signals/indexation_results.py @@ -53,6 +53,7 @@ from pyxem.signals.diffraction_vectors2d import DiffractionVectors2D from pyxem.utils._signals import _transfer_navigation_axes from pyxem.utils.signal import compute_markers +from pyxem.utils._deprecated import deprecated def crystal_from_vector_matching(z_matches):