Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor HEDM calibration to use new workflow #1800

Merged
merged 1 commit into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 36 additions & 143 deletions hexrdgui/cal_tree_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,15 @@
from hexrdgui import constants
from hexrdgui.utils import is_int

# Global constants
FIXED = 0
REFINABLE = 1

KEY_COL = BaseTreeItemModel.KEY_COL
VALUE_COL = KEY_COL + 1
STATUS_COL = VALUE_COL + 1


class CalTreeItemModel(BaseTreeItemModel):

def __init__(self, parent=None):
super().__init__(parent)
self.root_item = TreeItem(['key', 'value', 'refinable'])
self.root_item = TreeItem(['key', 'value'])
self.cfg = HexrdConfig()
self.rebuild_tree()

Expand All @@ -40,12 +35,6 @@ def data(self, index, role):
item = self.get_item(index)

value = item.data(index.column())
if index.column() == STATUS_COL:
if role == Qt.EditRole:
return value
if role == Qt.DisplayRole:
return None

if isinstance(value, np.generic):
# Get a native python type for display. Otherwise,
# it won't display anything...
Expand Down Expand Up @@ -84,17 +73,17 @@ def setData(self, index, value, role):

if item.child_count() == 0:
parent = item.parent_item.data(KEY_COL)
chi_path = ['oscillation_stage', 'chi', 'value']
chi_path = ['oscillation_stage', 'chi']
if path == chi_path:
# Convert to radians before saving
value = np.radians(value).item()
if (parent == 'tilt' and
HexrdConfig().rotation_matrix_euler() is not None and
len(path) > 1 and path[-2] == 'value'):
len(path) > 1):
# Convert tilt values to radians before saving
value = np.radians(value).item()
self.cfg.set_instrument_config_val(path, value)
dist_func_path = ['distortion', 'function_name', 'value']
dist_func_path = ['distortion', 'function_name']
if len(path) > 4 and path[2:5] == dist_func_path:
# Rebuild the tree if the distortion function changed
QObject.parent(self).rebuild_tree()
Expand All @@ -120,52 +109,49 @@ def flags(self, index):

# The second and third columns with no children are editable
editable = (
index.column() == STATUS_COL or
(index.column() == VALUE_COL and
item.child_count() == 0 and
item.data(KEY_COL) not in non_editable_keys and
item.parent_item.data(KEY_COL) not in non_editable_parent_keys)
index.column() == VALUE_COL and
item.child_count() == 0 and
item.data(KEY_COL) not in non_editable_keys and
item.parent_item.data(KEY_COL) not in non_editable_parent_keys
)

if editable:
flags = flags | Qt.ItemIsEditable

return flags

def add_tree_item(self, key, value, status, parent):
def add_tree_item(self, key, value, parent):
# In the case of the panel buffer we don't want to added children
# The editor will take care of this.
if parent.data(KEY_COL) == constants.BUFFER_KEY:
return

data = [key, value, status]
data = [key, value]
tree_item = TreeItem(data, parent)
return tree_item

def rebuild_tree(self):
# Rebuild the tree from scratch
self.clear()
for key in self.cfg.internal_instrument_config.keys():
tree_item = self.add_tree_item(key, None, REFINABLE,
self.root_item)
tree_item = self.add_tree_item(key, None, self.root_item)
self.recursive_add_tree_items(
self.cfg.internal_instrument_config[key], tree_item)
self.update_parent_status(tree_item)

def recursive_add_tree_items(self, cur_config, cur_tree_item):
if isinstance(cur_config, dict):
keys = cur_config.keys()
elif isinstance(cur_config, list):
keys = range(len(cur_config))
else:
# This must be a value. Set it.
cur_tree_item.set_data(STATUS_COL, cur_config)
# Must be a root-level value. Just set it.
cur_tree_item.set_data(VALUE_COL, cur_config)
return

blacklisted_keys = []

if ('source_distance' in keys and
cur_config['source_distance']['value'] == np.inf):
cur_config['source_distance'] == np.inf):
# Hide the source distance if it is infinite, as the infinite
# value does not get displayed correctly by Qt
# (maybe we need to register it as a custom type?)
Expand All @@ -174,37 +160,32 @@ def recursive_add_tree_items(self, cur_config, cur_tree_item):
for key in keys:
if key in blacklisted_keys:
continue
elif key == 'value':
name = cur_tree_item.data(KEY_COL)
data = cur_config[key]
path = self.path_to_value(cur_tree_item, VALUE_COL)

chi_path = ['oscillation_stage', 'chi', 'value']
if path == chi_path:
data = np.degrees(data).item()
elif (name == 'tilt' and
HexrdConfig().rotation_matrix_euler() is not None):
data = [np.degrees(rad).item() for rad in cur_config[key]]
self.set_value(key, data, cur_tree_item)
continue
elif key == 'status':
tree_item = cur_tree_item
else:
tree_item = self.add_tree_item(key, None, REFINABLE,
cur_tree_item)
if tree_item is not None:

tree_item = self.add_tree_item(key, None, cur_tree_item)
data = cur_config[key]
if isinstance(data, dict):
self.recursive_add_tree_items(cur_config[key], tree_item)
continue

if isinstance(data, list):
for i in range(len(data)):
self.add_tree_item(i, None, tree_item)

def update_parent_status(self, parent):
children = parent.child_items
for child in children:
if child.child_count() > 0:
self.update_parent_status(child)
if child.data(STATUS_COL):
parent.set_data(STATUS_COL, FIXED)
# Must be a value. Set it.
name = tree_item.data(KEY_COL)
path = self.path_to_value(tree_item, VALUE_COL)

chi_path = ['oscillation_stage', 'chi']
if path == chi_path:
data = np.degrees(data).item()
elif (name == 'tilt' and
HexrdConfig().rotation_matrix_euler() is not None):
data = [np.degrees(rad).item() for rad in cur_config[key]]

self.set_value(key, data, tree_item)

def path_to_value(self, tree_item, column):
path = ['value'] if column == VALUE_COL else ['status']
path = []
cur_tree_item = tree_item
while True:
text = cur_tree_item.data(KEY_COL)
Expand All @@ -226,47 +207,6 @@ def set_value(self, key, cur_config, cur_tree_item):
child.set_data(VALUE_COL, value)
else:
cur_tree_item.set_data(VALUE_COL, cur_config)
return


class CheckBoxDelegate(QStyledItemDelegate):

def __init__(self, parent=None):
super(CheckBoxDelegate, self).__init__(parent)

def createEditor(self, parent, option, index):
check = QCheckBox(parent)

# Only indicate the status has changed on user interaction
check.clicked.connect(self.statusChanged)

return check

def statusChanged(self):
self.commitData.emit(self.sender())

def setModelData(self, check, model, index):
item = self.parent().model().get_item(index)
if item.child_count() > 0:
self.setChildData(item, int(check.isChecked()))
model.setData(index, int(check.isChecked()), Qt.DisplayRole)
self.updateModel(index)

def setChildData(self, parent, value):
children = parent.child_items
for child in children:
child.set_data(STATUS_COL, value)
if child.child_count() == 0:
path = self.parent().model().path_to_value(child, STATUS_COL)
self.parent().model().cfg.set_instrument_config_val(path,
value)
else:
self.setChildData(child, value)

def updateModel(self, index):
end = self.parent().model().index(
-1, STATUS_COL, self.parent().model().parent(index))
self.parent().model().dataChanged.emit(index, end)


class CalTreeView(QTreeView):
Expand All @@ -276,14 +216,11 @@ def __init__(self, parent=None):
self.setModel(CalTreeItemModel(self))
self.setItemDelegateForColumn(
VALUE_COL, ValueColumnDelegate(self))
self.setItemDelegateForColumn(
STATUS_COL, CheckBoxDelegate(self))

self.expand_all_rows()

self.resizeColumnToContents(KEY_COL)
self.resizeColumnToContents(VALUE_COL)
self.resizeColumnToContents(STATUS_COL)

self.header().resizeSection(KEY_COL, 180)
self.header().resizeSection(VALUE_COL, 170)
Expand All @@ -307,22 +244,12 @@ def contextMenuEvent(self, event):
uncheck = None
if children:
menu.addSeparator()
check = menu.addAction('Check All')
uncheck = menu.addAction('Uncheck All')
action = menu.exec(QCursor.pos())

if action == collapse:
self.collapse_selection(item, index)
elif action == expand:
self.expand_selection(item, index)
elif action == check:
self.itemDelegateForColumn(STATUS_COL).setChildData(
item, True)
self.itemDelegateForColumn(STATUS_COL).updateModel(index)
elif action == uncheck:
self.itemDelegateForColumn(STATUS_COL).setChildData(
item, False)
self.itemDelegateForColumn(STATUS_COL).updateModel(index)

def rebuild_tree(self):
# We rebuild it from scratch every time it is shown in case
Expand Down Expand Up @@ -360,8 +287,6 @@ def fix_row_states(self, parent=QModelIndex()):
if collapsed_state and path in collapsed_state:
self.collapse(index)

self.display_status_checkbox(i, parent)

self.fix_row_states(index)

def expand_selection(self, parent, index):
Expand All @@ -383,35 +308,3 @@ def update_collapsed_status(self, index):
path = self.model().path_to_value(item, KEY_COL)

HexrdConfig().update_collapsed_state(path)

# Display status checkbox for the row if the requirements are met
def display_status_checkbox(self, row, parent=QModelIndex()):

index = self.model().index(row, KEY_COL, parent)
item = self.model().get_item(index)

# If it has children, return
if item.child_count() != 0:
return

# If the data is a string, return
if isinstance(item.data(VALUE_COL), str):
return

# If the key is blacklisted, return
blacklisted_keys = ['saturation_level', 'buffer', 'source_distance']
if item.data(KEY_COL) in blacklisted_keys:
return

# If one of the parents of the item is blacklisted, return
blacklisted_parents = ['pixels']
parent_item = item.parent_item
while parent_item is not None:
if parent_item.data(KEY_COL) in blacklisted_parents:
return

parent_item = parent_item.parent_item

# Show the checkbox
editor_idx = self.model().index(row, STATUS_COL, parent)
self.openPersistentEditor(editor_idx)
4 changes: 0 additions & 4 deletions hexrdgui/calibration/auto/powder_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ def _run(self):
# The options they chose are saved here
options = HexrdConfig().config['calibration']['powder']
self.instr = create_hedm_instrument()
# Set this for the default calibration flags we will use
self.instr.calibration_flags = (
HexrdConfig().get_statuses_instrument_format()
)

if options['auto_guess_initial_fwhm']:
fwhm_estimate = None
Expand Down
8 changes: 8 additions & 0 deletions hexrdgui/calibration/calibration_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def setup_connections(self):
self.on_active_beam_switched)

def show(self):
self.update_visibility_states()
self.ui.show()

def hide(self):
Expand Down Expand Up @@ -176,6 +177,10 @@ def set_instrument_defaults(self):
if instr_type == 'FIDDLE':
self.relative_constraints = RelativeConstraintsType.system
self.tilt_center_of_rotation = RotationCenter.lab_origin
elif instr_type == 'DEXELAS_COMPOSITE':
self.relative_constraints = RelativeConstraintsType.group
elif instr_type == 'EIGER_COMPOSITE':
self.relative_constraints = RelativeConstraintsType.system

def update_edit_picks_enable_state(self):
is_polar = HexrdConfig().image_mode == ViewType.polar
Expand Down Expand Up @@ -245,6 +250,9 @@ def update_visibility_states(self):
self.ui.active_beam_label.setVisible(has_multi_xrs)
self.ui.show_picks_from_all_xray_sources.setVisible(has_multi_xrs)

has_physics_package = HexrdConfig().physics_package is not None
self.ui.pinhole_distortion_group.setVisible(has_physics_package)

def update_relative_constraint_visibilities(self):
visible = self.relative_constraints != RelativeConstraintsType.none

Expand Down
16 changes: 9 additions & 7 deletions hexrdgui/calibration/calibration_dialog_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ def push_undo_stack(self):
self.update_undo_enable_state()

def pop_undo_stack(self):
if not self.undo_stack:
# Sometimes, this gets called faster than we can update.
# Just ignore invalid calls.
return

stack_item = self.undo_stack.pop(-1)

calibrator_items = [
Expand All @@ -162,10 +167,14 @@ def pop_undo_stack(self):
v = stack_item[k]
setattr(self.calibrator, k, v)

# Update instrument and calibrators
update_instrument_from_params(
self.instr,
stack_item['instr_params'],
)
for calibrator in self.calibrator.calibrators:
calibrator.update_from_lmfit_params(stack_item['params'])

self.update_config_from_instrument()
self.update_dialog_from_calibrator()
self.dialog.advanced_options = stack_item['advanced_options']
Expand Down Expand Up @@ -304,7 +313,6 @@ def run_calibration(self, **extra_kwargs):
self.push_undo_stack()

odict = copy.deepcopy(self.dialog.advanced_options)

x0 = self.calibrator.params.valuesdict()
result = self.calibrator.run_calibration(odict=odict, **extra_kwargs)

Expand Down Expand Up @@ -344,12 +352,6 @@ def update_config_from_instrument(self):
# add in any "None" detector distortions
HexrdConfig().set_detector_defaults_if_missing()

# Add status values
HexrdConfig().add_status(output_dict)

# Set the previous statuses to be the current statuses
HexrdConfig().set_statuses_from_prev_iconfig(prev_iconfig)

self.instrument_updated.emit()

# Update the drawn picks with their new locations
Expand Down
Loading
Loading