From 5dda08f352faf579c04c4ed97d88d98351df4994 Mon Sep 17 00:00:00 2001 From: Patrick Avery Date: Sat, 2 Nov 2024 12:55:01 -0500 Subject: [PATCH] Refactor HEDM calibration for short-term changes We eventually want to use features from our main calibration workflows, such as the "undo" button, and the relative constraints. However, we need to make short-term updates to the HEDM calibration workflow. This makes several changes, including allowing users to see how the refinement options get changed with different refinement settings, allowing users to select grains to be used, and allowing users to see and set all parameters used as input for `pull_spots()`. Fixes: #1741 Signed-off-by: Patrick Avery --- .../hedm/calibration_options_dialog.py | 486 ++++++++++++++++-- .../calibration/hedm/calibration_runner.py | 69 +-- hexrdgui/hexrd_config.py | 6 + hexrdgui/image_canvas.py | 2 +- hexrdgui/main_window.py | 2 + hexrdgui/overlay_manager.py | 1 + hexrdgui/overlays/rotation_series_overlay.py | 7 +- hexrdgui/refinements_editor.py | 25 +- .../indexing/default_indexing_config.yml | 6 +- .../ui/hedm_calibration_options_dialog.ui | 297 ++++++++--- hexrdgui/resources/ui/refinements_editor.ui | 3 + .../ui/rotation_series_overlay_editor.ui | 5 +- hexrdgui/select_grains_dialog.py | 17 +- .../tree_views/multi_column_dict_tree_view.py | 11 +- 14 files changed, 770 insertions(+), 167 deletions(-) diff --git a/hexrdgui/calibration/hedm/calibration_options_dialog.py b/hexrdgui/calibration/hedm/calibration_options_dialog.py index d1a94dfde..ae3e57948 100644 --- a/hexrdgui/calibration/hedm/calibration_options_dialog.py +++ b/hexrdgui/calibration/hedm/calibration_options_dialog.py @@ -1,9 +1,20 @@ -from PySide6.QtCore import QObject, Signal +import numpy as np +from PySide6.QtCore import QObject, Qt, Signal +from PySide6.QtWidgets import QMessageBox, QTableWidget, QTableWidgetItem + +from hexrd import constants as cnst + +from hexrdgui.constants import OverlayType from hexrdgui.hexrd_config import HexrdConfig -from hexrdgui.grains_viewer_dialog import GrainsViewerDialog +from hexrdgui.indexing.create_config import ( + create_indexing_config, OmegasNotFoundError +) +from hexrdgui.refinements_editor import RefinementsEditor from hexrdgui.reflections_table import ReflectionsTable +from hexrdgui.select_grains_dialog import SelectGrainsDialog from hexrdgui.ui_loader import UiLoader +from hexrdgui.utils import block_signals class HEDMCalibrationOptionsDialog(QObject): @@ -11,26 +22,44 @@ class HEDMCalibrationOptionsDialog(QObject): accepted = Signal() rejected = Signal() - def __init__(self, material, grains_table, parent=None): + def __init__(self, material, parent=None): super().__init__(parent) loader = UiLoader() self.ui = loader.load_file('hedm_calibration_options_dialog.ui', parent) + self.refinements_editor = RefinementsEditor(self.ui) + self.refinements_editor.hide_bottom_buttons = True self.material = material - self.grains_table = grains_table self.parent = parent + self.setup_refinement_options() + self.setup_table() + self.update_materials() self.update_gui() + self.apply_refinement_selections() self.setup_connections() def setup_connections(self): - self.ui.view_grains_table.clicked.connect(self.show_grains_table) + self.ui.view_grains_table.clicked.connect(self.edit_grains_table) + self.ui.view_refinements.clicked.connect(self.view_refinements) + + self.ui.material.currentIndexChanged.connect(self.material_changed) self.ui.choose_hkls.pressed.connect(self.choose_hkls) HexrdConfig().overlay_config_changed.connect(self.update_num_hkls) + self.ui.tolerances_selected_grain.currentIndexChanged.connect( + self.update_tolerances_table) + self.ui.tolerances_table.itemChanged.connect( + self.on_tolerances_changed) + self.refinements_editor.tree_view.dict_modified.connect( + self.on_refinements_editor_modified) + self.ui.fix_strain.toggled.connect(self.apply_refinement_selections) + self.ui.refinement_choice.currentIndexChanged.connect( + self.apply_refinement_selections) + self.ui.accepted.connect(self.on_accepted) self.ui.rejected.connect(self.on_rejected) @@ -43,11 +72,14 @@ def update_gui(self): calibration_config = indexing_config['_hedm_calibration'] self.do_refit = calibration_config['do_refit'] - self.clobber_strain = calibration_config['clobber_strain'] - self.clobber_centroid = calibration_config['clobber_centroid'] - self.clobber_grain_Y = calibration_config['clobber_grain_Y'] + indexing_config = HexrdConfig().indexing_config + self.npdiv = indexing_config['fit_grains']['npdiv'] + self.threshold = indexing_config['fit_grains']['threshold'] + + self.update_tolerances_grain_options() self.update_num_hkls() + self.update_num_grains_selected() def update_config(self): config = HexrdConfig().indexing_config['fit_grains'] @@ -57,20 +89,226 @@ def update_config(self): indexing_config = HexrdConfig().indexing_config calibration_config = indexing_config['_hedm_calibration'] calibration_config['do_refit'] = self.do_refit - calibration_config['clobber_strain'] = self.clobber_strain - calibration_config['clobber_centroid'] = self.clobber_centroid - calibration_config['clobber_grain_Y'] = self.clobber_grain_Y + + indexing_config = HexrdConfig().indexing_config + indexing_config['fit_grains']['npdiv'] = self.npdiv + indexing_config['fit_grains']['threshold'] = self.threshold + + def setup_refinement_options(self): + w = self.ui.refinement_choice + w.clear() + + for key, label in REFINEMENT_OPTIONS.items(): + w.addItem(label, key) def show(self): self.ui.show() def on_accepted(self): + self.apply_refinement_selections() + + try: + self.validate() + except Exception as e: + QMessageBox.critical(self.parent, 'HEXRD', f'Error: {e}') + self.show() + return + + self.refinements_editor.update_config() + self.refinements_editor.ui.accept() + self.update_config() self.accepted.emit() def on_rejected(self): self.rejected.emit() + def validate(self): + # Validation to perform before we do anything else + if not self.active_overlays: + msg = 'At least one grain must be selected' + raise Exception(msg) + + ome_periods = [] + for overlay in self.active_overlays: + if not overlay.has_widths: + msg = ( + 'All visible rotation series overlays must have widths ' + 'enabled' + ) + raise Exception(msg) + + ome_periods.append(overlay.ome_period) + + for i in range(1, len(ome_periods)): + if not np.allclose(ome_periods[0], ome_periods[i]): + msg = ( + 'All visible rotation series overlays must have ' + 'identical omega periods' + ) + raise Exception(msg) + + materials = [overlay.material_name for overlay in self.active_overlays] + if not all(x == materials[0] for x in materials): + msg = ( + 'All visible rotation series overlays must have the same ' + 'material' + ) + raise Exception(msg) + + # Make sure the material is updated in the indexing config + self.synchronize_material() + + # Ensure we have omega metadata + try: + create_indexing_config() + except OmegasNotFoundError: + msg = ( + 'No omega metadata found. Be sure to import the image ' + 'series using the "Simple Image Series" import tool.' + ) + raise Exception(msg) + + def synchronize_material(self): + # This material is used for creating the indexing config. + # Make sure it matches the material we are using. + cfg = HexrdConfig().indexing_config + cfg['_selected_material'] = self.material.name + + def update_materials(self): + prev = self.selected_material + material_names = list(HexrdConfig().materials) + + self.ui.material.clear() + self.ui.material.addItems(material_names) + + if prev in material_names: + self.ui.material.setCurrentText(prev) + else: + self.ui.material.setCurrentText(self.material.name) + + def material_changed(self): + # First, update the material on self.material + self.material = HexrdConfig().material(self.selected_material) + + # Deselect all grains + self.deselect_all_grains() + + def deselect_all_grains(self): + for overlay in self.overlays: + overlay.visible = False + + self.update_tolerances_grain_options() + self.update_num_grains_selected() + HexrdConfig().overlay_config_changed.emit() + HexrdConfig().update_overlay_manager.emit() + self.update_refinements_editor() + + def update_tolerances_grain_options(self): + w = self.ui.tolerances_selected_grain + if w.count() > 0: + prev = int(w.currentText()) + else: + prev = None + + with block_signals(w): + w.clear() + items = [str(i) for i in range(len(self.active_overlays))] + w.addItems(items) + if prev is not None and prev < len(items): + w.setCurrentIndex(prev) + + self.update_tolerances_table() + + def setup_table(self): + w = self.ui.tolerances_table + for i in range(3): + item = QTableWidgetItem() + item.setTextAlignment(Qt.AlignCenter) + w.setItem(0, i, item) + + content_height = calc_table_height(w) + w.setMaximumHeight(content_height) + + def update_tolerances_table(self): + w = self.ui.tolerances_table + if len(self.active_overlays) == 0: + # Cannot update + with block_signals(w): + for i in range(3): + w.item(0, i).setText('') + return + + grain_w = self.ui.tolerances_selected_grain + idx = int(grain_w.currentText()) + + overlay = self.active_overlays[idx] + + values = [ + overlay.tth_width, + overlay.eta_width, + overlay.ome_width, + ] + for col, val in enumerate(values): + item = w.item(0, col) + item.setText(f'{np.round(np.degrees(val), 10)}') + + def on_tolerances_changed(self): + if len(self.active_overlays) == 0: + # Can't do anything. Just return + return + + grain_w = self.ui.tolerances_selected_grain + idx = int(grain_w.currentText()) + + overlay = self.active_overlays[idx] + + w = self.ui.tolerances_table + columns = [ + 'tth_width', + 'eta_width', + 'ome_width', + ] + for col, attr in enumerate(columns): + item = w.item(0, col) + try: + val = float(item.text()) + except ValueError: + # Invalid. Continue. + val = getattr(overlay, attr) + item.setText(f'{np.round(np.degrees(val), 10)}') + continue + + setattr(overlay, attr, np.radians(val)) + + overlay.update_needed = True + + HexrdConfig().overlay_config_changed.emit() + HexrdConfig().update_overlay_editor.emit() + + def on_refinements_editor_modified(self): + # Set it to "custom" + idx = list(REFINEMENT_OPTIONS).index('custom') + + w = self.ui.refinement_choice + fix_strain_w = self.ui.fix_strain + with block_signals(w, fix_strain_w): + w.setCurrentIndex(idx) + + # Also disable fixing the strain + fix_strain_w.setChecked(False) + + # Trigger an update to the config + self.refinements_editor.update_config() + + @property + def selected_material(self) -> str: + return self.ui.material.currentText() + + @selected_material.setter + def selected_material(self, v: str): + self.ui.material.setCurrentText(v) + @property def do_refit(self): return self.ui.do_refit.isChecked() @@ -96,28 +334,20 @@ def refit_ome_step_scale(self, v): self.ui.refit_ome_step_scale.setValue(v) @property - def clobber_strain(self): - return self.ui.clobber_strain.isChecked() - - @clobber_strain.setter - def clobber_strain(self, b): - self.ui.clobber_strain.setChecked(b) - - @property - def clobber_centroid(self): - return self.ui.clobber_centroid.isChecked() + def npdiv(self): + return self.ui.npdiv.value() - @clobber_centroid.setter - def clobber_centroid(self, b): - self.ui.clobber_centroid.setChecked(b) + @npdiv.setter + def npdiv(self, v): + self.ui.npdiv.setValue(v) @property - def clobber_grain_Y(self): - return self.ui.clobber_grain_Y.isChecked() + def threshold(self): + return self.ui.threshold.value() - @clobber_grain_Y.setter - def clobber_grain_Y(self, b): - self.ui.clobber_grain_Y.setChecked(b) + @threshold.setter + def threshold(self, v): + self.ui.threshold.setValue(v) def choose_hkls(self): kwargs = { @@ -137,8 +367,198 @@ def update_num_hkls(self): text = f'Number of hkls selected: {num_hkls}' self.ui.num_hkls_selected.setText(text) - def show_grains_table(self): - if not hasattr(self, '_grains_viewer_dialog'): - self._grains_viewer_dialog = GrainsViewerDialog(self.grains_table) + def update_num_grains_selected(self): + num_grains = len(self.active_overlays) + text = f'Number of grains selected: {num_grains}' + self.ui.num_grains_selected.setText(text) + + def edit_grains_table(self): + dialog = SelectGrainsDialog(None, self.ui) + if not dialog.exec(): + return + + selected_grains = dialog.selected_grains + + # Hide any grains that were not selected. + # Show any grains that were selected. + # And add any new grains that don't have overlays. + new_overlays_needed = list(range(len(selected_grains))) + for overlay in self.overlays: + if not overlay.is_rotation_series: + overlay.visible = False + continue + + match_idx = -1 + for i in new_overlays_needed: + grain_params = selected_grains[i][3:15] + if np.allclose(overlay.crystal_params, grain_params): + match_idx = i + break + + overlay.visible = match_idx != -1 + if match_idx != -1: + new_overlays_needed.remove(match_idx) + + # Now create new overlays for any missing selected grains + for i in new_overlays_needed: + HexrdConfig().append_overlay( + self.material.name, + OverlayType.rotation_series, + ) + + # Grab that overlay we just made, and set the grain params + overlay = HexrdConfig().overlays[-1] + overlay.crystal_params = selected_grains[i][3:15] + overlay.update_needed = True + overlay.visible = True + + HexrdConfig().overlay_config_changed.emit() + + self.update_tolerances_grain_options() + self.update_num_grains_selected() + self.apply_refinement_selections() + self.update_refinements_editor() + HexrdConfig().update_overlay_manager.emit() + + @property + def fix_strain(self): + return self.ui.fix_strain.isChecked() + + @fix_strain.setter + def fix_strain(self, b): + self.ui.fix_strain.setChecked(b) + + @property + def fix_det_y(self): + return self.ui.refinement_choice.currentData() == 'fix_det_y' + + @property + def fix_grain_centroid(self): + return self.ui.refinement_choice.currentData() == 'fix_grain_centroid' + + @property + def fix_grain_y(self): + return self.ui.refinement_choice.currentData() == 'fix_grain_y' + + @property + def custom_refinements(self): + return self.ui.refinement_choice.currentData() == 'custom' + + def apply_refinement_selections(self): + def perform_updates(): + self.update_refinements_editor() + HexrdConfig().overlay_config_changed.emit() + HexrdConfig().update_overlay_editor.emit() + HexrdConfig().update_instrument_toolbox.emit() + + # First, apply strain settings + for overlay in self.active_overlays: + refinements = overlay.refinements + crystal_params = overlay.crystal_params + if self.fix_strain: + crystal_params[6:] = cnst.identity_6x1 + for i in range(6, len(refinements)): + refinements[i] = False + elif not self.custom_refinements: + # Make all strain parameters refinable, but only + # if we are not doing custom refinements. + for i in range(6, len(refinements)): + refinements[i] = True + + # If we are doing custom refinements, don't make any more changes + if self.custom_refinements: + perform_updates() + return + + # Set all rotation series orientation/position refinement params + for idx, overlay in enumerate(self.active_overlays): + refinements = overlay.refinements + crystal_params = overlay.crystal_params + + # The position and orientation will be refinable by default + for i in range(6): + refinements[i] = True + + if idx == 0: + # First grain may be affected by refinement choices + if self.fix_grain_centroid: + crystal_params[3:6] = cnst.zeros_3 + for i in range(3, 6): + refinements[i] = False + elif self.fix_grain_y: + crystal_params[4] = 0 + refinements[4] = False + + def recursive_set_refinable(cur, b): + if 'status' not in cur: + for key, value in cur.items(): + recursive_set_refinable(value, b) + return + + if isinstance(cur['status'], list): + for i in range(len(cur['status'])): + cur['status'][i] = b + else: + cur['status'] = b + + # Now make all detector parameters refinable by default. + iconfig = HexrdConfig().config['instrument'] + + # Mark everything under "beam" and "oscillation stage" as not refinable + recursive_set_refinable(iconfig['beam'], False) + recursive_set_refinable(iconfig['oscillation_stage'], False) + + # Mark everything under detectors as refinable + recursive_set_refinable(iconfig['detectors'], True) + + if self.fix_det_y: + # Fix the detector y translation values + for det_key, conf in iconfig['detectors'].items(): + conf['transform']['translation']['status'][1] = False + + # Now trigger updates everywhere + perform_updates() + + def view_refinements(self): + self.update_refinements_editor() + self.refinements_editor.ui.show() + + def update_refinements_editor(self): + self.refinements_editor.reset_dict() + + @property + def overlays(self): + return HexrdConfig().overlays - self._grains_viewer_dialog.show() + @property + def visible_overlays(self): + return [x for x in self.overlays if x.visible] + + @property + def visible_rotation_series_overlays(self): + return [x for x in self.visible_overlays if x.is_rotation_series] + + @property + def active_overlays(self): + return self.visible_rotation_series_overlays + + +def calc_table_height(table: QTableWidget) -> int: + """Calculate table height.""" + res = 0 + for i in range(table.verticalHeader().count()): + if not table.verticalHeader().isSectionHidden(i): + res += table.verticalHeader().sectionSize(i) + if table.horizontalScrollBar().isHidden(): + res += table.horizontalScrollBar().height() + if not table.horizontalHeader().isHidden(): + res += table.horizontalHeader().height() + return res + + +REFINEMENT_OPTIONS = { + 'fix_det_y': 'Fix origin based on current sample/detector position', + 'fix_grain_centroid': 'Reset origin to grain centroid position', + 'fix_grain_y': 'Reset Y axis origin to grain\'s Y position', + 'custom': 'Custom refinement parameters', +} diff --git a/hexrdgui/calibration/hedm/calibration_runner.py b/hexrdgui/calibration/hedm/calibration_runner.py index db158de3c..87a13daef 100644 --- a/hexrdgui/calibration/hedm/calibration_runner.py +++ b/hexrdgui/calibration/hedm/calibration_runner.py @@ -47,19 +47,15 @@ def clear(self): def run(self): self.clear() self.pre_validate() - self.synchronize_omega_period() # Create the grains table - shape = (self.num_active_overlays, 21) - self.grains_table = np.empty(shape, dtype=np.float64) - - gw = instrument.GrainDataWriter(array=self.grains_table) - for i, overlay in enumerate(self.active_overlays): - gw.dump_grain(i, 1, 0, overlay.crystal_params) + if self.num_active_overlays > 0: + material = self.material + else: + material = HexrdConfig().active_material kwargs = { - 'material': self.material, - 'grains_table': self.grains_table, + 'material': material, 'parent': self.parent, } dialog = HEDMCalibrationOptionsDialog(**kwargs) @@ -70,13 +66,16 @@ def run(self): def on_options_dialog_accepted(self): dialog = self.options_dialog + shape = (self.num_active_overlays, 21) + self.grains_table = np.empty(shape, dtype=np.float64) + gw = instrument.GrainDataWriter(array=self.grains_table) + for i, overlay in enumerate(self.active_overlays): + gw.dump_grain(i, 1, 0, overlay.crystal_params) + + self.synchronize_omega_period() + # Grab some selections from the dialog self.do_refit = dialog.do_refit - self.clobber_strain = dialog.clobber_strain - self.clobber_centroid = dialog.clobber_centroid - self.clobber_grain_Y = dialog.clobber_grain_Y - - self.clobber_refinements() self.run_calibration() def run_calibration(self): @@ -99,9 +98,6 @@ def on_pull_spots_finished(self, spots_data_dict): # User selected these from the dialog do_refit = self.do_refit - clobber_strain = self.clobber_strain - clobber_centroid = self.clobber_centroid - clobber_grain_Y = self.clobber_grain_Y # Our grains table only contains the grains that the user # selected. @@ -118,15 +114,6 @@ def on_pull_spots_finished(self, spots_data_dict): ome_period = self.ome_period grain_parameters = grain_parameters.copy() - if clobber_strain: - for grain in grain_parameters: - grain[6:] = cnst.identity_6x1 - if clobber_centroid: - for grain in grain_parameters: - grain[3:6] = cnst.zeros_3 - if clobber_grain_Y: - for grain in grain_parameters: - grain[4] = 0. ngrains = len(grain_parameters) # The styles we will use for plotting points @@ -472,28 +459,6 @@ def material(self): def active_overlay_refinements(self): return [x.refinements for x in self.active_overlays] - def clobber_refinements(self): - any_clobbering = ( - self.clobber_strain or - self.clobber_centroid or - self.clobber_grain_Y - ) - if not any_clobbering: - return - - for overlay in self.active_overlays: - refinements = overlay.refinements - if self.clobber_strain: - for i in range(6, len(refinements)): - refinements[i] = False - if self.clobber_centroid: - for i in range(3, 6): - refinements[i] = False - if self.clobber_grain_Y: - refinements[4] = False - - HexrdConfig().update_overlay_editor.emit() - def synchronize_material(self): # This material is used for creating the indexing config. # Make sure it matches the material we are using. @@ -509,8 +474,8 @@ def synchronize_omega_period(self): def pre_validate(self): # Validation to perform before we do anything else if not self.active_overlays: - msg = 'There must be at least one visible rotation series overlay' - raise Exception(msg) + # No more validation needed. + return ome_periods = [] for overlay in self.active_overlays: @@ -539,10 +504,6 @@ def pre_validate(self): ) raise Exception(msg) - if not np.any(self.all_flags): - msg = 'There are no refinable parameters' - raise Exception(msg) - # Make sure the material is updated in the indexing config self.synchronize_material() diff --git a/hexrdgui/hexrd_config.py b/hexrdgui/hexrd_config.py index e46313e99..8f8afe5b4 100644 --- a/hexrdgui/hexrd_config.py +++ b/hexrdgui/hexrd_config.py @@ -194,9 +194,15 @@ class HexrdConfig(QObject, metaclass=QSingleton): """Indicate that the state was loaded...""" state_loaded = Signal() + """Indicate that the overlay manager should update its table""" + update_overlay_manager = Signal() + """Indicate that the overlay editor should update its GUI""" update_overlay_editor = Signal() + """Indicate that the main window should update it's instrument toolbox""" + update_instrument_toolbox = Signal() + """Indicate that the beam marker has been modified""" beam_marker_modified = Signal() diff --git a/hexrdgui/image_canvas.py b/hexrdgui/image_canvas.py index 2b5c5cb78..2c5e9bcb8 100644 --- a/hexrdgui/image_canvas.py +++ b/hexrdgui/image_canvas.py @@ -894,7 +894,7 @@ def beam_vector_changed(self): # Right now, hexrd doesn't want this to be inf. # Maybe that will change in the future... self.iviewer.instr.source_distance = ( - beam_config['source_distance']['value'], + beam_config['source_distance']['value'] ) self.update_overlays() diff --git a/hexrdgui/main_window.py b/hexrdgui/main_window.py index 08c64a133..fd03c620d 100644 --- a/hexrdgui/main_window.py +++ b/hexrdgui/main_window.py @@ -332,6 +332,8 @@ def setup_connections(self): self.update_drawn_mask_line_picker_canvas) HexrdConfig().tab_images_changed.connect( self.update_mask_region_canvas) + HexrdConfig().update_instrument_toolbox.connect( + self.update_config_gui) ImageLoadManager().update_needed.connect(self.update_all) ImageLoadManager().new_images_loaded.connect(self.new_images_loaded) diff --git a/hexrdgui/overlay_manager.py b/hexrdgui/overlay_manager.py index 94afe545e..1b518ab87 100644 --- a/hexrdgui/overlay_manager.py +++ b/hexrdgui/overlay_manager.py @@ -47,6 +47,7 @@ def setup_connections(self): self.ui.add_button.pressed.connect(self.add) self.ui.remove_button.pressed.connect(self.remove) self.ui.edit_style_button.pressed.connect(self.edit_style) + HexrdConfig().update_overlay_manager.connect(self.update_table) HexrdConfig().update_overlay_editor.connect(self.update_overlay_editor) HexrdConfig().materials_added.connect(self.update_table) HexrdConfig().material_renamed.connect(self.on_material_renamed) diff --git a/hexrdgui/overlays/rotation_series_overlay.py b/hexrdgui/overlays/rotation_series_overlay.py index 854ef470f..cbd1f3a61 100644 --- a/hexrdgui/overlays/rotation_series_overlay.py +++ b/hexrdgui/overlays/rotation_series_overlay.py @@ -20,9 +20,10 @@ class RotationSeriesOverlay(Overlay): def __init__(self, material_name, crystal_params=None, eta_ranges=None, ome_ranges=None, ome_period=None, aggregated=True, - ome_width=np.radians(5.0).item(), tth_width=None, - eta_width=None, sync_ome_period=True, sync_ome_ranges=True, - **overlay_kwargs): + ome_width=np.radians(1.5).item(), + tth_width=np.radians(0.25).item(), + eta_width=np.radians(1.0).item(), + sync_ome_period=True, sync_ome_ranges=True, **overlay_kwargs): super().__init__(material_name, **overlay_kwargs) if crystal_params is None: diff --git a/hexrdgui/refinements_editor.py b/hexrdgui/refinements_editor.py index 314081981..e00083f09 100644 --- a/hexrdgui/refinements_editor.py +++ b/hexrdgui/refinements_editor.py @@ -29,6 +29,7 @@ def __init__(self, parent=None): self.ui.tree_view_layout.addWidget(self.tree_view) + self._hide_bottom_buttons = False self.iconfig_values_modified = False self.material_values_modified = False @@ -43,6 +44,17 @@ def setup_connections(self): self.ui.button_box.accepted.connect(self.ui.accept) self.ui.button_box.rejected.connect(self.ui.reject) + @property + def hide_bottom_buttons(self): + return self._hide_bottom_buttons + + @hide_bottom_buttons.setter + def hide_bottom_buttons(self, b): + self._hide_bottom_buttons = b + + self.ui.reset.setVisible(not b) + self.ui.button_box.setVisible(not b) + def reset_dict(self): config = {} config['instrument'] = self.create_instrument_dict() @@ -65,7 +77,7 @@ def create_instrument_dict(self): # Recurse through it, setting all status keys and renaming them to # "_refinable". blacklisted = ['saturation_level', 'buffer', 'pixels', 'id', - 'source_distance'] + 'source_distance', 'detector_type'] def recurse(cur, idict): if 'status' in cur: @@ -98,7 +110,7 @@ def recurse(cur, idict): def create_materials_dict(self): mdict = {} - for overlay in self.overlays: + for overlay in self.visible_overlays: name = overlay.name values = refinement_values(overlay) if not values: @@ -152,7 +164,7 @@ def recurse(cur, idict): def update_materials_config(self): mdict = self.dict['materials'] - for overlay in self.overlays: + for overlay in self.visible_overlays: name = overlay.name refinements = [] values = [] @@ -168,6 +180,10 @@ def update_materials_config(self): def overlays(self): return HexrdConfig().overlays + @property + def visible_overlays(self): + return [x for x in self.overlays if x.visible] + def setup_actions(self): labels = list(self.actions.keys()) self.ui.action.clear() @@ -180,6 +196,7 @@ def apply_action(self): # Update the tree view self.update_tree_view() + self.tree_view.dict_modified.emit() @property def actions(self): @@ -238,7 +255,7 @@ def powder_values(): return ret def laue_values(): - params = overlay.crystal_params + params = copy.deepcopy(overlay.crystal_params) # These params should be in the same order as the refinements params[:3] = to_convention(params[:3]) for i, label in enumerate(overlay.refinement_labels): diff --git a/hexrdgui/resources/indexing/default_indexing_config.yml b/hexrdgui/resources/indexing/default_indexing_config.yml index 1dd67a984..7feaed5d1 100644 --- a/hexrdgui/resources/indexing/default_indexing_config.yml +++ b/hexrdgui/resources/indexing/default_indexing_config.yml @@ -147,7 +147,7 @@ fit_grains: # Some custom ones we have added for the GUI _hedm_calibration: - do_refit: false - clobber_strain: false + do_refit: true + clobber_strain: true + clobber_grain_Y: true clobber_centroid: false - clobber_grain_Y: false diff --git a/hexrdgui/resources/ui/hedm_calibration_options_dialog.ui b/hexrdgui/resources/ui/hedm_calibration_options_dialog.ui index c58c5f498..b377ccf8f 100644 --- a/hexrdgui/resources/ui/hedm_calibration_options_dialog.ui +++ b/hexrdgui/resources/ui/hedm_calibration_options_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 479 - 558 + 606 + 850 @@ -17,7 +17,14 @@ - View Grains Table + Select Grains for Calibration Refinement + + + + + + + View Refinements @@ -40,52 +47,20 @@ - - - Clobbering + + + η and ω ranges can be edited within the "Overlay Manager" - - - - - <html><head/><body><p>Sets and fixes the stretch matrix to identity.</p></body></html> - - - Clobber strain? - - - - - - - <html><head/><body><p>Sets and fixes the grain position to the origin: (0, 0, 0).</p></body></html> - - - Clobber centroid? - - - - - - - <html><head/><body><p>Sets and fixes the grain Y to zero.</p></body></html> - - - Clobber grain Y? - - - - - + Number of hkls selected: - + Refitting @@ -96,6 +71,9 @@ false + + <html><head/><body><p>If &quot;Do refit?&quot; is not checked: The grain and instrument parameters will be refined once.</p><p>If &quot;Do refit?&quot; is checked: The grain and instrument parameters will first be refined. Afterwards, a filtering step will filter out reflections that are too far away in x, y, or omega from the predicted values, and then the grain and instrument parameters will be refined again.</p><p><br/></p><p>The &quot;Refit pixel scale&quot; is the maximum distance (in pixels) in x or y before the reflection is filtered out.</p><p><br/></p><p>The &quot;Refit omega step scale&quot; is the maximum distance in omega steps before the reflection is filtered out. For example, if the omega step size is 0.25 degrees, and the &quot;Refit omega step scale&quot; is 2, then the maximum allowable difference in omega is 2 * 0.25 degrees = 0.5 degrees.</p></body></html> + Refit pixel scale @@ -103,9 +81,15 @@ + + <html><head/><body><p>If &quot;Do refit?&quot; is not checked: The grain and instrument parameters will be refined once.</p><p>If &quot;Do refit?&quot; is checked: The grain and instrument parameters will first be refined. Afterwards, a filtering step will filter out reflections that are too far away in x, y, or omega from the predicted values, and then the grain and instrument parameters will be refined again.</p><p><br/></p><p>The &quot;Refit pixel scale&quot; is the maximum distance (in pixels) in x or y before the reflection is filtered out.</p><p><br/></p><p>The &quot;Refit omega step scale&quot; is the maximum distance in omega steps before the reflection is filtered out. For example, if the omega step size is 0.25 degrees, and the &quot;Refit omega step scale&quot; is 2, then the maximum allowable difference in omega is 2 * 0.25 degrees = 0.5 degrees.</p></body></html> + Do refit? + + true + @@ -113,6 +97,9 @@ false + + <html><head/><body><p>If &quot;Do refit?&quot; is not checked: The grain and instrument parameters will be refined once.</p><p>If &quot;Do refit?&quot; is checked: The grain and instrument parameters will first be refined. Afterwards, a filtering step will filter out reflections that are too far away in x, y, or omega from the predicted values, and then the grain and instrument parameters will be refined again.</p><p><br/></p><p>The &quot;Refit pixel scale&quot; is the maximum distance (in pixels) in x or y before the reflection is filtered out.</p><p><br/></p><p>The &quot;Refit omega step scale&quot; is the maximum distance in omega steps before the reflection is filtered out. For example, if the omega step size is 0.25 degrees, and the &quot;Refit omega step scale&quot; is 2, then the maximum allowable difference in omega is 2 * 0.25 degrees = 0.5 degrees.</p></body></html> + Refit ome step scale @@ -129,6 +116,9 @@ 0 + + <html><head/><body><p>If &quot;Do refit?&quot; is not checked: The grain and instrument parameters will be refined once.</p><p>If &quot;Do refit?&quot; is checked: The grain and instrument parameters will first be refined. Afterwards, a filtering step will filter out reflections that are too far away in x, y, or omega from the predicted values, and then the grain and instrument parameters will be refined again.</p><p><br/></p><p>The &quot;Refit pixel scale&quot; is the maximum distance (in pixels) in x or y before the reflection is filtered out.</p><p><br/></p><p>The &quot;Refit omega step scale&quot; is the maximum distance in omega steps before the reflection is filtered out. For example, if the omega step size is 0.25 degrees, and the &quot;Refit omega step scale&quot; is 2, then the maximum allowable difference in omega is 2 * 0.25 degrees = 0.5 degrees.</p></body></html> + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -148,6 +138,9 @@ false + + <html><head/><body><p>If &quot;Do refit?&quot; is not checked: The grain and instrument parameters will be refined once.</p><p>If &quot;Do refit?&quot; is checked: The grain and instrument parameters will first be refined. Afterwards, a filtering step will filter out reflections that are too far away in x, y, or omega from the predicted values, and then the grain and instrument parameters will be refined again.</p><p><br/></p><p>The &quot;Refit pixel scale&quot; is the maximum distance (in pixels) in x or y before the reflection is filtered out.</p><p><br/></p><p>The &quot;Refit omega step scale&quot; is the maximum distance in omega steps before the reflection is filtered out. For example, if the omega step size is 0.25 degrees, and the &quot;Refit omega step scale&quot; is 2, then the maximum allowable difference in omega is 2 * 0.25 degrees = 0.5 degrees.</p></body></html> + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -165,13 +158,183 @@ - + Choose HKLs + + + + Refinement Choices + + + + + + <html><head/><body><p>Sets all calibration grains' stretch tensors to identity and holds them fixed during refinement.</p></body></html> + + + Assume calibration grains strain-free + + + false + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p>Select a preset refinement choice. Here are descriptions of each:</p><p><br/></p><p><span style=" font-weight:600;">Fix origin based on current sample/detector position</span> - Holds detectors fixed along the Y axis during refinement</p><p><br/></p><p><span style=" font-weight:600;">Reset origin to grain centroid position</span> - Set first calibration grain's centroid to [0,0,0] and holds it fixed during refinement</p><p><br/></p><p><span style=" font-weight:600;">Reset Y axis origin to grain's Y position</span> - Sets the first calibration grain's Y position to zero and holds it fixed in Y during refinement</p><p><br/></p><p><span style=" font-weight:600;">Custom refinement parameters</span> - Uses current parameter refinement choices. Check refinements tab to see current selection. <span style=" font-weight:600;">Warning</span>: Incompatible choices can be made without proper consideration of the system's degrees of freedom</p></body></html> + + + Choice: + + + + + + + <html><head/><body><p>Select a preset refinement choice. Here are descriptions of each:</p><p><br/></p><p><span style=" font-weight:600;">Fix origin based on current sample/detector position</span> - Holds detectors fixed along the Y axis during refinement</p><p><br/></p><p><span style=" font-weight:600;">Reset origin to grain centroid position</span> - Set first calibration grain's centroid to [0,0,0] and holds it fixed during refinement</p><p><br/></p><p><span style=" font-weight:600;">Reset Y axis origin to grain's Y position</span> - Sets the first calibration grain's Y position to zero and holds it fixed in Y during refinement</p><p><br/></p><p><span style=" font-weight:600;">Custom refinement parameters</span> - Uses current parameter refinement choices. Check refinements tab to see current selection. <span style=" font-weight:600;">Warning</span>: Incompatible choices can be made without proper consideration of the system's degrees of freedom</p></body></html> + + + + + + + + + + + + Number of Polar Subdivisions: + + + + + + + Number of grains to be refined: + + + + + + + 0 + + + 100000 + + + 4 + + + + + + + Selected Material: + + + + + + + + + + Tolerances + + + + + + Selected Grain: + + + + + + + + + + + 0 + 0 + + + + + 16777215 + 100 + + + + 175 + + + true + + + false + + + + New Row + + + + + + + + + + η + + + + + ω + + + + + + + + + + + Threshold: + + + + + + + 8 + + + 10000000.000000000000000 + + + 25.000000000000000 + + + @@ -210,7 +373,19 @@ + view_grains_table + view_refinements + material choose_hkls + tolerances_selected_grain + tolerances_table + npdiv + threshold + do_refit + refit_pixel_scale + refit_ome_step_scale + fix_strain + refinement_choice @@ -221,8 +396,8 @@ accept() - 248 - 254 + 259 + 546 157 @@ -237,8 +412,8 @@ reject() - 316 - 260 + 327 + 546 286 @@ -253,12 +428,12 @@ setEnabled(bool) - 354 - 218 + 235 + 385 - 123 - 253 + 149 + 426 @@ -269,12 +444,12 @@ setEnabled(bool) - 354 - 218 + 235 + 385 - 354 - 253 + 452 + 426 @@ -285,12 +460,12 @@ setEnabled(bool) - 354 - 218 + 235 + 385 - 123 - 290 + 149 + 467 @@ -301,12 +476,12 @@ setEnabled(bool) - 354 - 218 + 235 + 385 - 354 - 290 + 452 + 467 diff --git a/hexrdgui/resources/ui/refinements_editor.ui b/hexrdgui/resources/ui/refinements_editor.ui index 6741eee68..260db50e1 100644 --- a/hexrdgui/resources/ui/refinements_editor.ui +++ b/hexrdgui/resources/ui/refinements_editor.ui @@ -10,6 +10,9 @@ 700 + + Refinements Editor + diff --git a/hexrdgui/resources/ui/rotation_series_overlay_editor.ui b/hexrdgui/resources/ui/rotation_series_overlay_editor.ui index 46731934b..986c58261 100644 --- a/hexrdgui/resources/ui/rotation_series_overlay_editor.ui +++ b/hexrdgui/resources/ui/rotation_series_overlay_editor.ui @@ -94,7 +94,7 @@ 360.000000000000000 - 3.000000000000000 + 1.000000000000000 @@ -249,6 +249,9 @@ 10000000.000000000000000 + + 1.500000000000000 + diff --git a/hexrdgui/select_grains_dialog.py b/hexrdgui/select_grains_dialog.py index 325a72738..25b609d9b 100644 --- a/hexrdgui/select_grains_dialog.py +++ b/hexrdgui/select_grains_dialog.py @@ -17,7 +17,7 @@ class SelectGrainsDialog(QObject): accepted = Signal() rejected = Signal() - def __init__(self, num_requested_grains=1, parent=None): + def __init__(self, num_requested_grains: int | None = 1, parent=None): super().__init__(parent) self.ignore_errors = False @@ -26,7 +26,10 @@ def __init__(self, num_requested_grains=1, parent=None): self.num_requested_grains = num_requested_grains - if num_requested_grains >= 1: + if num_requested_grains is None: + # None means any number of grains + self.ui.setWindowTitle('Please select grains') + elif num_requested_grains >= 1: suffix = 's' if num_requested_grains > 1 else '' title = f'Please select {num_requested_grains} grain{suffix}' self.ui.setWindowTitle(title) @@ -168,7 +171,10 @@ def grains_table(self, v): # If the number of rows is equal to the number of requested grains, # select all rows automatically for convenience. - if len(v) == self.num_requested_grains: + if ( + self.num_requested_grains is not None and + len(v) == self.num_requested_grains + ): selection_model = view.selectionModel() command = QItemSelectionModel.Select | QItemSelectionModel.Rows for i in range(len(v)): @@ -231,7 +237,10 @@ def update_enable_states(self): button_box = self.ui.button_box ok_button = button_box.button(QDialogButtonBox.Ok) if ok_button: - enable = self.num_selected_grains == self.num_requested_grains + enable = ( + self.num_requested_grains is None or + self.num_selected_grains == self.num_requested_grains + ) ok_button.setEnabled(enable) grains_loaded = self.num_grains_loaded > 0 diff --git a/hexrdgui/tree_views/multi_column_dict_tree_view.py b/hexrdgui/tree_views/multi_column_dict_tree_view.py index e8e7523eb..50d364bff 100644 --- a/hexrdgui/tree_views/multi_column_dict_tree_view.py +++ b/hexrdgui/tree_views/multi_column_dict_tree_view.py @@ -1,4 +1,4 @@ -from PySide6.QtCore import Signal, QModelIndex, Qt +from PySide6.QtCore import Signal, QModelIndex, Qt, QTimer from PySide6.QtGui import QCursor from PySide6.QtWidgets import ( QCheckBox, QDialog, QItemEditorFactory, QMenu, QStyledItemDelegate, @@ -237,8 +237,13 @@ def reset_gui(self): self.open_persistent_editors() self.expand_rows() - # Restore the vertical scroll bar position - sb.setValue(prev_pos) + def restore_vertical_bar(): + # Restore the vertical scroll bar position + sb.setValue(prev_pos) + + # Do this in the next iteration of the event loop, because + # it may require the GUI to finish updating. + QTimer.singleShot(0, restore_vertical_bar) class MultiColumnDictTreeViewDialog(QDialog):