From 6dde8aa881ad1462d615f1d615b5c4374834e135 Mon Sep 17 00:00:00 2001 From: Patrick Avery Date: Mon, 25 Apr 2022 21:29:32 -0500 Subject: [PATCH 1/3] Save/load ome maps select method Signed-off-by: Patrick Avery --- hexrd/ui/indexing/ome_maps_select_dialog.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hexrd/ui/indexing/ome_maps_select_dialog.py b/hexrd/ui/indexing/ome_maps_select_dialog.py index a11d047d9..9de91197d 100644 --- a/hexrd/ui/indexing/ome_maps_select_dialog.py +++ b/hexrd/ui/indexing/ome_maps_select_dialog.py @@ -150,6 +150,7 @@ def update_config(self): # Set the new config options on the internal config indexing_config = HexrdConfig().indexing_config maps_config = indexing_config['find_orientations']['orientation_maps'] + maps_config['_select_method'] = self.method_name maps_config['file'] = self.file_name maps_config['threshold'] = self.threshold maps_config['bin_frames'] = self.bin_frames @@ -162,6 +163,8 @@ def update_gui(self): indexing_config = HexrdConfig().indexing_config maps_config = indexing_config['find_orientations']['orientation_maps'] + self.method_name = maps_config.get('_select_method', 'load') + file_name = maps_config['file'] if maps_config['file'] else '' self.ui.file_name.setText(file_name) From 5e9200006c91395e5d5efb06e3b92eccddcb75e7 Mon Sep 17 00:00:00 2001 From: Patrick Avery Date: Mon, 25 Apr 2022 21:24:59 -0500 Subject: [PATCH 2/3] Add hand-picked indexing method This allows the user to hand pick the grains by first selecting the hand-picked quaternion method in the eta omega maps viewer, then right-clicking on a spot. This will create a list of fibers from 0 to 360 degrees that intersect with the spot. A slider can then be moved to determine the angle at which the fiber intersects with other spots as well. This fiber can then be added to the list of quaternions. If the user utilizes this method, the workflow then proceeds directly to fit grains. Signed-off-by: Patrick Avery --- hexrd/ui/indexing/fiber_pick_utils.py | 157 +++++++ hexrd/ui/indexing/ome_maps_viewer_dialog.py | 389 ++++++++++++++++-- hexrd/ui/indexing/run.py | 13 +- .../ui/resources/ui/ome_maps_viewer_dialog.ui | 219 +++++++++- 4 files changed, 745 insertions(+), 33 deletions(-) create mode 100644 hexrd/ui/indexing/fiber_pick_utils.py diff --git a/hexrd/ui/indexing/fiber_pick_utils.py b/hexrd/ui/indexing/fiber_pick_utils.py new file mode 100644 index 000000000..cd8e32ba4 --- /dev/null +++ b/hexrd/ui/indexing/fiber_pick_utils.py @@ -0,0 +1,157 @@ +import numpy as np + +from hexrd import constants as cnst +from hexrd.rotations import discreteFiber +from hexrd.transforms import xfcapi + + +def _pick_to_fiber(pick_coords, eta_ome_maps, map_index, step=0.5, + beam_vec=None, chi=0., as_expmap=True): + """ + Returns the orientations for the specified fiber parameters. + + Parameters + ---------- + pick_coords : array_like + The (2, ) list/vector containing the pick coordinates as (eta, omega) + in DEGREES. This corresponds to the (column, row) or (x, y) dimensions + on the map. + eta_ome_maps : hexrd.xrdutil.utils.EtaOmeMaps + The eta-omega maps. + map_index : int + The index of the current map. + step : scalar, optional + The step size along the fiber in DEGREES. The default is 0.5. + chi : scalar, optional + The chi angle from the associated instrument (specifically, + `instr.chi`). The default is 0. + beam_vec : array_like, optional + The beam vector of the associated instrument (specifically, + `instr.beam_vector`). The default is None (giving [0, 0, -1]). + as_expmap : bool, optional + Flag for converting the output from qauternions to exponential map. + The default is True. + + Returns + ------- + qfib : numpy.ndarray + The array containing the fiber points as quaternions or exponential + map parameters, according to the `as_expmap` kwarg. + + """ + pick_coords = np.atleast_1d(pick_coords).flatten() + + if beam_vec is None: + beam_vec = cnst.beam_vec + + ndiv = int(np.round(360./float(step))) + + # grab the planeData instance from the maps + # !!! this should have a copy of planeData that has hkls consistent with + # the map data. + pd = eta_ome_maps.planeData + bmat = pd.latVecOps['B'] + + # the crystal direction (plane normal) + crys_dir = pd.hkls[:, map_index].reshape(3, 1) + + # the sample direction + tth = pd.getTTh()[map_index] # !!! in radians + angs = np.atleast_2d(np.hstack([tth, np.radians(pick_coords)])) + samp_dir = xfcapi.anglesToGVec( + angs, bHat_l=beam_vec, chi=chi + ).reshape(3, 1) + + # make the fiber + qfib = discreteFiber( + crys_dir, samp_dir, + B=bmat, ndiv=ndiv, + invert=False, + csym=pd.getQSym(), ssym=None + )[0] + + if as_expmap: + phis = 2.*np.arccos(qfib[0, :]) + ns = xfcapi.unitRowVector(qfib[1:, :].T) + expmaps = phis*ns.T + return expmaps.T # (3, ndiv) + else: + return qfib.T # (4, ndiv) + + +def _angles_from_orientation(instr, eta_ome_maps, orientation): + """ + Return the (eta, omega) angles for a specified orientation consistent with + input EtaOmeMaps. + + Parameters + ---------- + instr : hexrd.instrument.HEDMInstrument + The instrument instance used to generate the EtaOmeMaps. + eta_ome_maps : hexrd.xrdutil.utils.EtaOmeMaps + The eta-omega maps. + orientation : array_like + Either a (3, ) or (4, ) element vector specifying an orientation. + + Raises + ------ + RuntimeError + If orientation has more than 4 elements. + + Returns + ------- + simulated_angles : list + A list with length = len(eta_ome_maps.dataStore) containing the angular + coordinates of all valid reflections for each map. If no valid points + exist for a particular map, the entry contains `None`. Otherwise, + the entry is a (2, p) array of the (eta, omega) coordinates in DEGREES + for the p valid reflections. + + """ + plane_data = eta_ome_maps.planeData + + # angle ranges from maps + eta_range = (eta_ome_maps.etaEdges[0], eta_ome_maps.etaEdges[-1]) + ome_range = (eta_ome_maps.omeEdges[0], eta_ome_maps.omeEdges[-1]) + ome_period = eta_ome_maps.omeEdges[0] + np.r_[0., 2*np.pi] + + # need the hklids + hklids = [i['hklID'] for i in plane_data.hklDataList] + + expmap = np.atleast_1d(orientation).flatten() + if len(expmap) == 4: + # have a quat; convert here + phi = 2.*np.arccos(expmap[0]) + n = xfcapi.unitRowVector(expmap[1:]) + expmap = phi*n + elif len(expmap) > 4: + raise RuntimeError( + "orientation must be a single exponential map or quaternion" + ) + + grain_param_list = [np.hstack([expmap, cnst.zeros_3, cnst.identity_6x1]), ] + sim_dict = instr.simulate_rotation_series( + plane_data, grain_param_list, + eta_ranges=[eta_range, ], ome_ranges=[ome_range, ], + ome_period=ome_period, wavelength=None + ) + + rids = [] + angs = [] + for sim in sim_dict.values(): + rids.append(sim[0][0]) + angs.append(sim[2][0]) + rids = np.hstack(rids) + angs = np.vstack(angs) + + simulated_angles = [] + for rid in hklids: + this_idx = rids == rid + if np.any(this_idx): + simulated_angles.append( + np.degrees(np.atleast_2d(angs[this_idx, 1:])) + ) + else: + simulated_angles.append(None) + + return simulated_angles diff --git a/hexrd/ui/indexing/ome_maps_viewer_dialog.py b/hexrd/ui/indexing/ome_maps_viewer_dialog.py index d8baea7f7..8ebd3d9fa 100644 --- a/hexrd/ui/indexing/ome_maps_viewer_dialog.py +++ b/hexrd/ui/indexing/ome_maps_viewer_dialog.py @@ -6,10 +6,12 @@ import numpy as np import yaml -from PySide2.QtCore import Signal, QObject, QSignalBlocker, QTimer, Qt +from PySide2.QtCore import ( + Signal, QItemSelectionModel, QObject, QSignalBlocker, QTimer, Qt +) from PySide2.QtWidgets import ( QCheckBox, QComboBox, QDoubleSpinBox, QFileDialog, QMessageBox, - QSizePolicy, QSpinBox + QSizePolicy, QSpinBox, QTableWidgetItem ) from hexrd.constants import sigma_to_fwhm @@ -17,14 +19,20 @@ clean_map, filter_maps_if_requested, filter_stdev_DFLT ) from hexrd.imageutil import find_peaks_2d +from hexrd.rotations import quatOfExpMap from hexrd.ui import resource_loader from hexrd.ui.color_map_editor import ColorMapEditor +from hexrd.ui.create_hedm_instrument import create_hedm_instrument from hexrd.ui.hexrd_config import HexrdConfig +from hexrd.ui.indexing.fiber_pick_utils import ( + _angles_from_orientation, _pick_to_fiber +) from hexrd.ui.navigation_toolbar import NavigationToolbar from hexrd.ui.select_items_widget import SelectItemsWidget from hexrd.ui.ui_loader import UiLoader +from hexrd.ui.utils import block_signals import hexrd.ui.constants import hexrd.ui.resources.indexing @@ -32,6 +40,8 @@ DEFAULT_FWHM = filter_stdev_DFLT * sigma_to_fwhm +NUM_HAND_PICKED_FIBERS = 720 + class OmeMapsViewerDialog(QObject): @@ -51,10 +61,17 @@ def __init__(self, data, parent=None): self.spots = None self.reset_internal_config() + self.cached_hand_picked_spots = {} + self.generated_fibers = np.empty((0,)) + self.current_fiber_spots = np.empty((0,)) + self.hand_picked_fibers = np.empty((0, 3)) + self.setup_widget_paths() self.setup_combo_box_item_data() + self.ui.current_fiber_slider.setRange(0, NUM_HAND_PICKED_FIBERS - 1) + # Hide these tab bars. The user selects them via combo boxes. self.ui.quaternion_method_tab_widget.tabBar().hide() self.ui.seed_search_method_tab_widget.tabBar().hide() @@ -80,20 +97,11 @@ def setup_connections(self): self.ui.rejected.connect(self.on_rejected) self.ui.quaternion_method.currentIndexChanged.connect( - self.update_quaternion_method_tab) - self.ui.quaternion_method.currentIndexChanged.connect( - self.update_seed_search_visibilities) - self.ui.quaternion_method.currentIndexChanged.connect( - self.update_config) - self.ui.quaternion_method.currentIndexChanged.connect( - self.update_spots) + self.quaternion_method_changed) self.ui.seed_search_method.currentIndexChanged.connect( - self.update_seed_search_method_tab) - self.ui.seed_search_method.currentIndexChanged.connect( - self.update_config) - self.ui.seed_search_method.currentIndexChanged.connect( - self.update_spots) + self.seed_search_method_changed) + self.color_map_editor.ui.minimum.valueChanged.connect( self.update_config) @@ -123,11 +131,26 @@ def changed_signal(w): self.ui.select_quaternion_grid_file.pressed.connect( self.select_quaternion_grid_file) + self.ui.current_fiber_slider.valueChanged.connect( + self.current_fiber_slider_value_changed) + + self.ui.current_fiber_angle.valueChanged.connect( + self.current_fiber_angle_value_changed) + + self.ui.add_fiber_button.clicked.connect(self.add_current_fiber) + + self.ui.picked_fibers_table.selectionModel().selectionChanged.connect( + self.picked_fibers_table_selection_changed) + + self.ui.picked_fibers_delete_selected.clicked.connect( + self.delete_selected_fiber_rows) + def setup_combo_box_item_data(self): # Set the item data for the combo boxes to be the names we want item_data = [ 'seed_search', 'grid_search', + 'hand_picked', ] for i, data in enumerate(item_data): self.ui.quaternion_method.setItemData(i, data) @@ -205,7 +228,11 @@ def on_rejected(self): self.rejected.emit() def validate(self): - if self.quaternion_method_name == 'grid_search': + if self.quaternion_method_name == 'hand_picked': + if self.hand_picked_fibers.size == 0: + msg = 'At least one fiber must be picked' + raise ValidationException(msg) + elif self.quaternion_method_name == 'grid_search': # Make sure the file exists. q_file = self.config['find_orientations']['use_quaternion_grid'] if not q_file or not Path(q_file).exists(): @@ -225,11 +252,14 @@ def validate(self): f'(4, n), but instead has a shape of "{quats.shape}".' ) raise ValidationException(msg) - else: + elif self.quaternion_method_name == 'seed_search': # Seed search. Make sure hkls were chosen. hkls = self.config['find_orientations']['seed_search']['hkl_seeds'] if not hkls: raise ValidationException('No hkls selected') + else: + msg = f'Unhandled quaternion method: {self.quaternion_method_name}' + raise ValidationException(msg) def setup_widget_paths(self): text = resource_loader.load_resource(hexrd.ui.resources.indexing, @@ -268,12 +298,16 @@ def quaternion_method_name(self, v): for i in range(w.count()): if v == w.itemData(i): w.setCurrentIndex(i) - self.update_seed_search_visibilities() + self.update_visibilities() self.update_quaternion_method_tab() return raise Exception(f'Unable to set quaternion_method: {v}') + @property + def quaternions_hand_picked(self): + return self.quaternion_method_name == 'hand_picked' + @property def quaternion_grid_file(self): return self.ui.quaternion_grid_file.text() @@ -282,16 +316,41 @@ def quaternion_grid_file(self): def quaternion_grid_file(self, v): self.ui.quaternion_grid_file.setText(v) - def update_seed_search_visibilities(self): - visible = self.quaternion_method_name == 'seed_search' + def update_visibilities(self): + self.update_seed_search_visibilities() + self.update_indexing_visibilities() + self.update_clustering_visibilities() + + @staticmethod + def set_widgets_visible(widgets, visible): + for w in widgets: + w.setVisible(visible) + def update_seed_search_visibilities(self): widgets = [ self.ui.select_hkls_group, self.ui.label_spots, ] + visible = self.quaternion_method_name == 'seed_search' - for w in widgets: - w.setVisible(visible) + self.set_widgets_visible(widgets, visible) + + def update_indexing_visibilities(self): + widgets = [ + self.ui.omega_group_box, + self.ui.eta_group_box, + ] + visible = self.quaternion_method_name in ('seed_search', 'grid_search') + + self.set_widgets_visible(widgets, visible) + + def update_clustering_visibilities(self): + widgets = [ + self.ui.clustering_group_box, + ] + visible = self.quaternion_method_name in ('seed_search', 'grid_search') + + self.set_widgets_visible(widgets, visible) @property def seed_search_method_name(self): @@ -348,6 +407,21 @@ def filter_maps(self, v): self.ui.filtering_apply_gaussian_laplace.setChecked(apply_gl) self.ui.filtering_fwhm.setValue(fwhm) + def quaternion_method_changed(self): + self.clear_generated_fibers() + self.clear_selected_fibers_artists() + self.select_fiber_rows([]) + + self.update_quaternion_method_tab() + self.update_visibilities() + self.update_config() + self.update_spots() + + def seed_search_method_changed(self): + self.update_seed_search_method_tab() + self.update_config() + self.update_spots() + def update_quaternion_method_tab(self): # Take advantage of the naming scheme... method_tab = getattr(self.ui, self.quaternion_method_name + '_tab') @@ -366,9 +440,9 @@ def hkls(self): def update_hkl_options(self): # This won't trigger a re-draw. Can change in the future if needed. - blocker = QSignalBlocker(self.ui.active_hkl) # noqa: F841 - self.ui.active_hkl.clear() - self.ui.active_hkl.addItems(self.hkls) + with block_signals(self.ui.active_hkl): + self.ui.active_hkl.clear() + self.ui.active_hkl.addItems(self.hkls) def setup_plot(self): # Create the figure and axes to use @@ -391,6 +465,8 @@ def setup_plot(self): # Center the toolbar self.ui.canvas_layout.setAlignment(self.toolbar, Qt.AlignCenter) + canvas.mpl_connect('button_press_event', self.plot_clicked) + self.fig = fig self.ax = ax self.canvas = canvas @@ -520,6 +596,8 @@ def update_plot(self): im.set_norm(self.norm) self.update_spots() + self.update_current_fiber_plot() + self.draw_selected_fibers() im.set_extent(self.extent) @@ -527,6 +605,9 @@ def update_plot(self): ax.autoscale_view() ax.axis('auto') + # Now disable autoscaling + ax.autoscale(False) + self.draw() def draw(self): @@ -661,7 +742,9 @@ def set_val(w, path): find_orientations = config['find_orientations'] - if find_orientations['use_quaternion_grid']: + if find_orientations.get('_hand_picked_quaternions', False): + self.quaternion_method_name = 'hand_picked' + elif find_orientations['use_quaternion_grid']: self.quaternion_method_name = 'grid_search' else: self.quaternion_method_name = 'seed_search' @@ -687,10 +770,13 @@ def update_config(self): config = self.config find_orientations = config['find_orientations'] - if self.quaternion_method_name == 'seed_search': - quat_file = None - else: + key = '_hand_picked_quaternions' + find_orientations[key] = self.quaternions_hand_picked + + if self.quaternion_method_name == 'grid_search': quat_file = self.quaternion_grid_file + else: + quat_file = None find_orientations['use_quaternion_grid'] = quat_file find_orientations['_quat_file'] = self.quaternion_grid_file @@ -763,6 +849,253 @@ class Cfg: # Perform the filtering filter_maps_if_requested(self.data, cfg) + def clear_generated_fibers(self): + self.generated_fibers = np.empty((0,)) + self.ui.current_fiber_slider.setValue(0) + # In case the value didn't change. This shouldn't be expensive, + # so it's okay to run it twice. + self.update_current_fiber() + + def plot_clicked(self, event): + if not self.quaternions_hand_picked: + # If we are not hand picking quaternions, just return + return + + if not event.button == 3: + # Hand-picking quaternions is right-click only + return + + instr = create_hedm_instrument() + + eta = event.xdata + ome = event.ydata + + kwargs = { + 'pick_coords': (eta, ome), + 'eta_ome_maps': self.data, + 'map_index': self.current_hkl_index, + 'step': 360 / NUM_HAND_PICKED_FIBERS, + 'beam_vec': instr.beam_vector, + 'chi': instr.chi, + 'as_expmap': True, + } + self.generated_fibers = _pick_to_fiber(**kwargs) + + self.ui.current_fiber_slider.setValue(0) + # In case the value didn't change. This shouldn't be expensive, + # so it's okay to run it twice. + self.update_current_fiber() + + def update_current_fiber(self): + enable = len(self.generated_fibers) > 0 + + enable_list = [ + self.ui.current_fiber_slider, + self.ui.current_fiber_angle, + self.ui.selected_fiber_orientation_0, + self.ui.selected_fiber_orientation_1, + self.ui.selected_fiber_orientation_2, + self.ui.add_fiber_button, + ] + for w in enable_list: + w.setEnabled(enable) + + for i, v in enumerate(self.current_fiber_orientation): + w = getattr(self.ui, f'selected_fiber_orientation_{i}') + w.setValue(v) + + angle = self.current_fiber_index / NUM_HAND_PICKED_FIBERS * 360 + self.ui.current_fiber_angle.setValue(angle) + + self.generate_current_fiber_spots() + self.update_current_fiber_plot() + + def generate_current_fiber_spots(self): + if self.current_fiber_index >= len(self.generated_fibers): + fibers = [] + else: + fibers = self.generated_fibers[self.current_fiber_index] + + self.current_fiber_spots = self.generate_fiber_spots(fibers) + + def generate_fiber_spots(self, fibers): + if len(fibers) == 0: + return np.empty((0,)) + + kwargs = { + 'instr': create_hedm_instrument(), + 'eta_ome_maps': self.data, + 'orientation': fibers, + } + return _angles_from_orientation(**kwargs) + + def clear_current_fiber_plot(self): + if hasattr(self, '_current_fiber_lines'): + self._current_fiber_lines.remove() + del self._current_fiber_lines + + def update_current_fiber_plot(self): + self.clear_current_fiber_plot() + hkl_idx = self.current_hkl_index + if len(self.current_fiber_spots) <= hkl_idx: + self.draw() + return + + current = self.current_fiber_spots[hkl_idx] + if current.size: + kwargs = { + 'x': current[:, 0], + 'y': current[:, 1], + 's': 36, + 'c': 'm', + 'marker': '+', + } + self._current_fiber_lines = self.ax.scatter(**kwargs) + + self.draw() + + @property + def current_fiber_orientation(self): + if len(self.generated_fibers) == 0: + return np.array([0, 0, 0]) + + return self.generated_fibers[self.current_fiber_index] + + @property + def current_fiber_index(self): + return self.ui.current_fiber_slider.value() + + def current_fiber_slider_value_changed(self): + self.update_current_fiber() + + def current_fiber_angle_value_changed(self, v): + new_slider_index = round(v / 360 * NUM_HAND_PICKED_FIBERS) + self.ui.current_fiber_slider.setValue(new_slider_index) + + def add_current_fiber(self): + to_stack = (self.hand_picked_fibers, self.current_fiber_orientation) + self.hand_picked_fibers = np.vstack(to_stack) + self.update_picked_fibers_table() + + self.clear_generated_fibers() + + table = self.ui.picked_fibers_table + last_row = table.rowCount() - 1 + self.select_fiber_rows([last_row]) + + def update_picked_fibers_table(self): + table = self.ui.picked_fibers_table + table.clearContents() + table.setColumnCount(3) + table.setRowCount(len(self.hand_picked_fibers)) + for i, orientation in enumerate(self.hand_picked_fibers): + for j in range(3): + item = QTableWidgetItem(f'{orientation[j]:.4f}') + item.setTextAlignment(Qt.AlignCenter) + item.setFlags(item.flags() & ~Qt.ItemIsEditable) + table.setItem(i, j, item) + + @property + def hand_picked_quaternions(self): + # We store these as 3D exp maps. Convert and return as quaternions. + quats = quatOfExpMap(self.hand_picked_fibers.T) + if quats.ndim == 1: + # quatOfExpMap() squeezes the output. We must reshape it. + quats = np.atleast_2d(quats).T + + return quats + + @property + def hand_picked_fibers(self): + return self._hand_picked_fibers + + @hand_picked_fibers.setter + def hand_picked_fibers(self, v): + self._hand_picked_fibers = v + # Clear the cache for hand picked spots + self.cached_hand_picked_spots.clear() + + def clear_selected_fibers_artists(self): + lines = getattr(self, '_selected_fibers_artists', []) + while lines: + lines.pop(0).remove() + + @property + def selected_fibers_rows(self): + selected = self.ui.picked_fibers_table.selectionModel().selectedRows() + selected = [] if None else selected + return [x.row() for x in selected] + + def picked_fibers_table_selection_changed(self): + self.draw_selected_fibers() + + enable_delete = len(self.selected_fibers_rows) > 0 + self.ui.picked_fibers_delete_selected.setEnabled(enable_delete) + + def spots_for_hand_picked_quaternion(self, i): + if i >= len(self.hand_picked_fibers): + return None + + cache = self.cached_hand_picked_spots + + # Check the cache first. If not present, add to the cache. + if i not in cache: + fiber = self.hand_picked_fibers[i] + if not fiber.size: + return None + + cache[i] = self.generate_fiber_spots(fiber) + + return cache[i][self.current_hkl_index] + + def draw_selected_fibers(self): + self.clear_selected_fibers_artists() + + artists = [] + for i in self.selected_fibers_rows: + spots = self.spots_for_hand_picked_quaternion(i) + if spots is None: + continue + + kwargs = { + 'x': spots[:, 0], + 'y': spots[:, 1], + 's': 36, + 'c': 'g', + 'marker': 'o', + } + artists.append(self.ax.scatter(**kwargs)) + + self._selected_fibers_artists = artists + self.draw() + + def select_fiber_rows(self, rows): + table = self.ui.picked_fibers_table + selection_model = table.selectionModel() + + with block_signals(selection_model): + selection_model.clearSelection() + command = QItemSelectionModel.Select | QItemSelectionModel.Rows + + for i in rows: + if i is None or i >= table.rowCount(): + # Out of range. Don't do anything. + continue + + # Select the row + model_index = selection_model.model().index(i, 0) + selection_model.select(model_index, command) + + self.picked_fibers_table_selection_changed() + + def delete_selected_fiber_rows(self): + selected = self.selected_fibers_rows + self.hand_picked_fibers = np.delete(self.hand_picked_fibers, + selected, 0) + # There should be no selection now + self.select_fiber_rows([]) + self.update_picked_fibers_table() + class ValidationException(Exception): pass diff --git a/hexrd/ui/indexing/run.py b/hexrd/ui/indexing/run.py index 11a7ba22f..cbb831a77 100644 --- a/hexrd/ui/indexing/run.py +++ b/hexrd/ui/indexing/run.py @@ -135,6 +135,15 @@ def view_ome_maps(self): self.ome_maps_viewer_dialog = dialog def ome_maps_viewed(self): + # If we did the hand-picked method, go ahead and skip now to + # generating the grains table and running fit grains + dialog = self.ome_maps_viewer_dialog + if dialog.quaternions_hand_picked: + self.qbar = dialog.hand_picked_quaternions + self.generate_grains_table() + self.start_fit_grains_runner() + return + # The dialog should have automatically updated our internal config # Let's go ahead and run the indexing! @@ -298,7 +307,9 @@ def generate_grains_table(self): print('No grains found') return - msg = f'{num_grains} grains found' + plural = 's' if num_grains != 1 else '' + + msg = f'{num_grains} grain{plural} found' self.update_progress_text(msg) print(msg) diff --git a/hexrd/ui/resources/ui/ome_maps_viewer_dialog.ui b/hexrd/ui/resources/ui/ome_maps_viewer_dialog.ui index f41b5b6c4..cf313d6cb 100644 --- a/hexrd/ui/resources/ui/ome_maps_viewer_dialog.ui +++ b/hexrd/ui/resources/ui/ome_maps_viewer_dialog.ui @@ -7,7 +7,7 @@ 0 0 1900 - 1108 + 1149 @@ -886,6 +886,200 @@ + + + Hand Picked + + + + + + + 0 + 0 + + + + Picked Fibers + + + + + + QAbstractItemView::SelectRows + + + false + + + true + + + + + + + false + + + Delete Selected + + + + + + + + + + + 0 + 0 + + + + Current Fiber + + + + + + + + false + + + Qt::AlignCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 6 + + + -10.000000000000000 + + + 10.000000000000000 + + + + + + + false + + + Qt::AlignCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 6 + + + -10.000000000000000 + + + 10.000000000000000 + + + + + + + false + + + Qt::AlignCenter + + + true + + + QAbstractSpinBox::NoButtons + + + false + + + 6 + + + -10.000000000000000 + + + 10.000000000000000 + + + + + + + + + false + + + Add Fiber + + + + + + + Right-click the canvas to generate fibers for a position + + + + + + + false + + + Qt::Horizontal + + + + + + + false + + + false + + + ° + + + 8 + + + 360.000000000000000 + + + + + + + + @@ -900,6 +1094,11 @@ Grid Search + + + Hand Picked + + @@ -915,9 +1114,6 @@ - apply_filtering - filtering_apply_gaussian_laplace - filtering_fwhm quaternion_method quaternion_method_tab_widget seed_search_method @@ -935,6 +1131,16 @@ bl_threshold bl_overlap fiber_step + quaternion_grid_file + select_quaternion_grid_file + current_fiber_slider + current_fiber_angle + selected_fiber_orientation_0 + selected_fiber_orientation_1 + selected_fiber_orientation_2 + add_fiber_button + picked_fibers_table + picked_fibers_delete_selected omega_tolerance eta_tolerance eta_mask @@ -943,7 +1149,12 @@ clustering_algorithm write_scored_orientations select_working_dir + apply_filtering + filtering_apply_gaussian_laplace + filtering_fwhm + active_hkl label_spots + export_button From 90acdec50f535b53cddc74fd7507ef58b7b89f34 Mon Sep 17 00:00:00 2001 From: Patrick Avery Date: Tue, 26 Apr 2022 15:43:51 -0500 Subject: [PATCH 3/3] Add fiber step option to hand picking This adds a fiber step option to the hand picking indexing, which is always synchronized with the fiber step option in the seed search quaternion method. If the user modifies the fiber step, the fibers get regenerated with the new step. The current angle will also be rounded to the nearest valid angle. Signed-off-by: Patrick Avery --- hexrd/ui/indexing/ome_maps_viewer_dialog.py | 82 ++++++++++++++++--- .../ui/resources/ui/ome_maps_viewer_dialog.ui | 37 ++++++++- 2 files changed, 106 insertions(+), 13 deletions(-) diff --git a/hexrd/ui/indexing/ome_maps_viewer_dialog.py b/hexrd/ui/indexing/ome_maps_viewer_dialog.py index 8ebd3d9fa..fc7e24fcd 100644 --- a/hexrd/ui/indexing/ome_maps_viewer_dialog.py +++ b/hexrd/ui/indexing/ome_maps_viewer_dialog.py @@ -40,8 +40,6 @@ DEFAULT_FWHM = filter_stdev_DFLT * sigma_to_fwhm -NUM_HAND_PICKED_FIBERS = 720 - class OmeMapsViewerDialog(QObject): @@ -65,13 +63,13 @@ def __init__(self, data, parent=None): self.generated_fibers = np.empty((0,)) self.current_fiber_spots = np.empty((0,)) self.hand_picked_fibers = np.empty((0, 3)) + self.latest_picked_eta = None + self.latest_picked_ome = None self.setup_widget_paths() self.setup_combo_box_item_data() - self.ui.current_fiber_slider.setRange(0, NUM_HAND_PICKED_FIBERS - 1) - # Hide these tab bars. The user selects them via combo boxes. self.ui.quaternion_method_tab_widget.tabBar().hide() self.ui.seed_search_method_tab_widget.tabBar().hide() @@ -145,6 +143,13 @@ def changed_signal(w): self.ui.picked_fibers_delete_selected.clicked.connect( self.delete_selected_fiber_rows) + self.ui.fiber_step.valueChanged.connect( + self.synchronize_fiber_step_boxes) + self.ui.hand_picked_fiber_step.valueChanged.connect( + self.synchronize_fiber_step_boxes) + + self.ui.fiber_step.valueChanged.connect(self.fiber_step_value_changed) + def setup_combo_box_item_data(self): # Set the item data for the combo boxes to be the names we want item_data = [ @@ -765,6 +770,11 @@ def set_val(w, path): self.working_dir = config.get('working_dir', HexrdConfig().working_dir) + self.synchronize_fiber_step_boxes(self.fiber_step) + + self.ui.current_fiber_slider.setRange( + 0, self.num_hand_picked_fibers - 1) + def update_config(self): # Update all of the config with their settings from the widgets config = self.config @@ -850,6 +860,10 @@ class Cfg: filter_maps_if_requested(self.data, cfg) def clear_generated_fibers(self): + # Reset the latest picks to None + self.latest_picked_eta = None + self.latest_picked_ome = None + self.generated_fibers = np.empty((0,)) self.ui.current_fiber_slider.setValue(0) # In case the value didn't change. This shouldn't be expensive, @@ -865,16 +879,24 @@ def plot_clicked(self, event): # Hand-picking quaternions is right-click only return - instr = create_hedm_instrument() + self.latest_picked_eta = event.xdata + self.latest_picked_ome = event.ydata + + self.recreate_generated_fibers() - eta = event.xdata - ome = event.ydata + def recreate_generated_fibers(self): + pick_coords = (self.latest_picked_eta, self.latest_picked_ome) + if any(x is None for x in pick_coords): + # No picked coords. Just return. + return + + instr = create_hedm_instrument() kwargs = { - 'pick_coords': (eta, ome), + 'pick_coords': pick_coords, 'eta_ome_maps': self.data, 'map_index': self.current_hkl_index, - 'step': 360 / NUM_HAND_PICKED_FIBERS, + 'step': self.fiber_step, 'beam_vec': instr.beam_vector, 'chi': instr.chi, 'as_expmap': True, @@ -904,7 +926,7 @@ def update_current_fiber(self): w = getattr(self.ui, f'selected_fiber_orientation_{i}') w.setValue(v) - angle = self.current_fiber_index / NUM_HAND_PICKED_FIBERS * 360 + angle = self.current_fiber_index * self.fiber_step self.ui.current_fiber_angle.setValue(angle) self.generate_current_fiber_spots() @@ -969,9 +991,14 @@ def current_fiber_slider_value_changed(self): self.update_current_fiber() def current_fiber_angle_value_changed(self, v): - new_slider_index = round(v / 360 * NUM_HAND_PICKED_FIBERS) + new_slider_index = round(v / self.fiber_step) self.ui.current_fiber_slider.setValue(new_slider_index) + # This usually already happens, but make sure the angle gets + # updated to its new value (it may need to round to the nearest). + angle = self.current_fiber_index * self.fiber_step + self.ui.current_fiber_angle.setValue(angle) + def add_current_fiber(self): to_stack = (self.hand_picked_fibers, self.current_fiber_orientation) self.hand_picked_fibers = np.vstack(to_stack) @@ -1061,8 +1088,10 @@ def draw_selected_fibers(self): 'x': spots[:, 0], 'y': spots[:, 1], 's': 36, - 'c': 'g', 'marker': 'o', + 'facecolors': 'none', + 'edgecolors': 'c', + 'linewidths': 1, } artists.append(self.ax.scatter(**kwargs)) @@ -1096,6 +1125,35 @@ def delete_selected_fiber_rows(self): self.select_fiber_rows([]) self.update_picked_fibers_table() + def synchronize_fiber_step_boxes(self, v): + self.ui.fiber_step.setValue(v) + self.ui.hand_picked_fiber_step.setValue(v) + + def fiber_step_value_changed(self, v): + prev_angle = self.ui.current_fiber_angle.value() + + self.ui.current_fiber_slider.setRange( + 0, self.num_hand_picked_fibers - 1) + self.ui.current_fiber_angle.setSingleStep(self.fiber_step) + + if self.quaternions_hand_picked: + # Re-create the generated fibers + # Restore the closest value to the previous angle + self.recreate_generated_fibers() + self.ui.current_fiber_angle.setValue(prev_angle) + + @property + def fiber_step(self): + return self.ui.fiber_step.value() + + @fiber_step.setter + def fiber_step(self, v): + self.ui.fiber_step.setValue(v) + + @property + def num_hand_picked_fibers(self): + return round(360 / self.fiber_step) + class ValidationException(Exception): pass diff --git a/hexrd/ui/resources/ui/ome_maps_viewer_dialog.ui b/hexrd/ui/resources/ui/ome_maps_viewer_dialog.ui index cf313d6cb..b10c6b60c 100644 --- a/hexrd/ui/resources/ui/ome_maps_viewer_dialog.ui +++ b/hexrd/ui/resources/ui/ome_maps_viewer_dialog.ui @@ -404,7 +404,7 @@ - 0 + 2 @@ -929,6 +929,13 @@ + + + + Fiber Step: + + + @@ -1051,6 +1058,9 @@ false + + 719 + Qt::Horizontal @@ -1078,6 +1088,31 @@ + + + + false + + + ° + + + 8 + + + 0.000000000000000 + + + 360.000000000000000 + + + 0.100000000000000 + + + 0.500000000000000 + + +