Skip to content
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
117 changes: 103 additions & 14 deletions hexrdgui/calibration/calibration_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
normalize_euler_convention,
param_names_euler_convention,
)
from hexrd.fitting.calibration.relative_constraints import (
RelativeConstraintsType,
)

from hexrdgui import resource_loader
from hexrdgui.calibration.tree_item_models import (
Expand Down Expand Up @@ -38,6 +41,7 @@ class CalibrationDialog(QObject):
edit_picks_clicked = Signal()
save_picks_clicked = Signal()
load_picks_clicked = Signal()
relative_constraints_changed = Signal(RelativeConstraintsType)
engineering_constraints_changed = Signal(str)

pinhole_correction_settings_modified = Signal()
Expand All @@ -47,8 +51,10 @@ class CalibrationDialog(QObject):
finished = Signal()

def __init__(self, instr, params_dict, format_extra_params_func=None,
parent=None, engineering_constraints=None,
window_title='Calibration Dialog', help_url='calibration/'):
parent=None, relative_constraints=None,
engineering_constraints=None,
window_title='Calibration Dialog',
help_url='calibration/'):
super().__init__(parent)

loader = UiLoader()
Expand All @@ -68,11 +74,16 @@ def __init__(self, instr, params_dict, format_extra_params_func=None,
HexrdConfig().physics_package_modified.connect(
self.on_pinhole_correction_settings_modified)

self.populate_relative_constraint_options()

self.instr = instr
self._params_dict = params_dict
self.format_extra_params_func = format_extra_params_func
self.relative_constraints = relative_constraints
self.engineering_constraints = engineering_constraints

self._ignore_next_tree_view_update = False

instr_type = guess_instrument_type(instr.detectors)
# Use delta boundaries by default for anything other than TARDIS
# and PXRDIP. We might want to change this to a whitelist later.
Expand All @@ -98,6 +109,8 @@ def setup_connections(self):
self.on_active_beam_changed)
self.ui.show_picks_from_all_xray_sources.toggled.connect(
self.show_picks_from_all_xray_sources_toggled)
self.ui.relative_constraints.currentIndexChanged.connect(
self.on_relative_constraints_changed)
self.ui.engineering_constraints.currentIndexChanged.connect(
self.on_engineering_constraints_changed)
self.ui.delta_boundaries.toggled.connect(
Expand Down Expand Up @@ -128,6 +141,17 @@ def hide(self):
def load_settings(self):
pass

def populate_relative_constraint_options(self):
# We are skipping group constraints until it is actually implemented
options = [
RelativeConstraintsType.none,
RelativeConstraintsType.system,
]
w = self.ui.relative_constraints
w.clear()
for option in options:
w.addItem(option.value, option)

def update_edit_picks_enable_state(self):
is_polar = HexrdConfig().image_mode == ViewType.polar

Expand Down Expand Up @@ -308,6 +332,21 @@ def undo_enabled(self):
def undo_enabled(self, b):
self.ui.undo_run_button.setEnabled(b)

@property
def relative_constraints(self) -> RelativeConstraintsType:
ret = self.ui.relative_constraints.currentData()
return ret if ret is not None else RelativeConstraintsType.none

@relative_constraints.setter
def relative_constraints(self, v: RelativeConstraintsType):
v = v if v is not None else RelativeConstraintsType.none
w = self.ui.relative_constraints
options = [w.itemText(i) for i in range(w.count())]
if v.value not in options:
raise Exception(f'Invalid relative constraints: {v.value}')

w.setCurrentText(v.value)

@property
def engineering_constraints(self):
return self.ui.engineering_constraints.currentText()
Expand Down Expand Up @@ -353,6 +392,20 @@ def tth_distortion(self, v):
first = next(iter(v.values()))
self.pinhole_correction_editor.update_from_object(first)

def on_relative_constraints_changed(self):
# If the relative constraints is not None, then the engineering
# constraints must be set to None
enable = self.relative_constraints == RelativeConstraintsType.none
if not enable:
self._ignore_next_tree_view_update = True
self.engineering_constraints = None

self.ui.engineering_constraints.setEnabled(enable)
self.ui.mirror_constraints_from_first_detector.setEnabled(enable)

self.relative_constraints_changed.emit(self.relative_constraints)
self.reinitialize_tree_view()

def on_engineering_constraints_changed(self):
self.engineering_constraints_changed.emit(self.engineering_constraints)

Expand Down Expand Up @@ -404,6 +457,7 @@ def mirror_constraints_from_first_detector(self):
self.tree_view.reset_gui()

def update_from_calibrator(self, calibrator):
self.relative_constraints = calibrator.relative_constraints_type
self.engineering_constraints = calibrator.engineering_constraints
self.tth_distortion = calibrator.tth_distortion
self.params_dict = calibrator.params
Expand Down Expand Up @@ -504,6 +558,9 @@ def recursively_set_items(this_config, this_template):
# Now generate the detectors
detector_template = template_dict['detectors'].pop('{det}')

euler_convention = HexrdConfig().euler_angle_convention
euler_normalized = normalize_euler_convention(euler_convention)

def recursively_format_det(det, this_config, this_template):
for k, v in this_template.items():
if isinstance(v, dict):
Expand All @@ -524,10 +581,11 @@ def recursively_format_det(det, this_config, this_template):
current = template.format(det=det, i=i)
elif k == 'tilt':
# Special case. Take into account euler angles.
convention = HexrdConfig().euler_angle_convention
normalized = normalize_euler_convention(convention)
param_names = param_names_euler_convention(det, convention)
labels = TILT_LABELS_EULER[normalized]
param_names = param_names_euler_convention(
det,
euler_convention,
)
labels = TILT_LABELS_EULER[euler_normalized]
this_dict = this_config.setdefault(k, {})
for label, param_name in zip(labels, param_names):
param = params_dict[param_name]
Expand All @@ -540,14 +598,39 @@ def recursively_format_det(det, this_config, this_template):
if v in params_dict:
this_config[k] = create_param_item(params_dict[v])

det_dict = tree_dict.setdefault('detectors', {})
for det_key in self.instr.detectors:
this_config = det_dict.setdefault(det_key, {})
this_template = copy.deepcopy(detector_template)

# For the parameters, we need to convert dashes to underscores
det = det_key.replace('-', '_')
recursively_format_det(det, this_config, this_template)
if self.relative_constraints == RelativeConstraintsType.none:
det_dict = tree_dict.setdefault('detectors', {})
for det_key in self.instr.detectors:
this_config = det_dict.setdefault(det_key, {})
this_template = copy.deepcopy(detector_template)

# For the parameters, we need to convert dashes to underscores
det = det_key.replace('-', '_')
recursively_format_det(det, this_config, this_template)
elif self.relative_constraints == RelativeConstraintsType.group:
raise NotImplementedError(self.relative_constraints)
elif self.relative_constraints == RelativeConstraintsType.system:
det_dict = tree_dict.setdefault('detector system', {})

tvec_names = [
'system_tvec_x',
'system_tvec_y',
'system_tvec_z',
]
tilt_names = param_names_euler_convention(
'system', euler_convention)

this_config = det_dict.setdefault('translation', {})
tvec_keys = ['X', 'Y', 'Z']
for key, name in zip(tvec_keys, tvec_names):
this_config[key] = create_param_item(params_dict[name])

this_config = det_dict.setdefault('tilt', {})
tilt_keys = TILT_LABELS_EULER[euler_normalized]
for key, name in zip(tilt_keys, tilt_names):
this_config[key] = create_param_item(params_dict[name])
else:
raise NotImplementedError(self.relative_constraints)

if self.format_extra_params_func is not None:
self.format_extra_params_func(params_dict, tree_dict,
Expand Down Expand Up @@ -597,6 +680,12 @@ def reinitialize_tree_view(self):
self.tree_view.verticalScrollBar().setValue(scroll_value)

def update_tree_view(self):
if self._ignore_next_tree_view_update:
# Sometimes this is necessary when updating multiple
# parameters at once.
self._ignore_next_tree_view_update = False
return

tree_dict = self.tree_view_dict_of_params
self.tree_view.model().config = tree_dict
self.tree_view.reset_gui()
Expand Down
38 changes: 27 additions & 11 deletions hexrdgui/calibration/calibration_dialog_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QFileDialog

import lmfit

from hexrd.fitting.calibration.lmfit_param_handling import (
create_instr_params,
update_instrument_from_params,
)
from hexrd.instrument import HEDMInstrument

from hexrdgui.hexrd_config import HexrdConfig
from hexrdgui.utils import instr_to_internal_dict
Expand Down Expand Up @@ -50,6 +54,8 @@ def setup_connections(self):
dialog.edit_picks_clicked.connect(self.on_edit_picks_clicked)
dialog.save_picks_clicked.connect(self.on_save_picks_clicked)
dialog.load_picks_clicked.connect(self.on_load_picks_clicked)
dialog.relative_constraints_changed.connect(
self.on_relative_constraints_changed)
dialog.engineering_constraints_changed.connect(
self.on_engineering_constraints_changed)
dialog.run.connect(self.on_run_clicked)
Expand Down Expand Up @@ -112,9 +118,12 @@ def update_dialog_from_calibrator(self):

def push_undo_stack(self):
stack_item = {
'relative_constraints': self.calibrator.relative_constraints,
'engineering_constraints': self.calibrator.engineering_constraints,
'tth_distortion': self.calibrator.tth_distortion,
'params': self.calibrator.params,
# Create a custom instrument parameters list to use for undo
'instr_params': _create_instr_params(self.instr),
'advanced_options': self.dialog.advanced_options,
}
# Make deep copies to ensure originals will not be edited
Expand All @@ -127,9 +136,10 @@ def pop_undo_stack(self):
stack_item = self.undo_stack.pop(-1)

calibrator_items = [
'relative_constraints',
'engineering_constraints',
'tth_distortion',
# Put this last so it will get set last
# Put this later in the list so it will get set later
'params',
]

Expand All @@ -140,8 +150,7 @@ def pop_undo_stack(self):

update_instrument_from_params(
self.instr,
self.calibrator.params,
self.euler_convention,
stack_item['instr_params'],
)
self.update_config_from_instrument()
self.update_dialog_from_calibrator()
Expand All @@ -152,13 +161,18 @@ def pop_undo_stack(self):
def update_undo_enable_state(self):
self.dialog.undo_enabled = bool(self.undo_stack)

def on_relative_constraints_changed(self, new_constraint):
self.calibrator.relative_constraints_type = new_constraint
self.on_constraints_changed()

def on_engineering_constraints_changed(self, new_constraint):
self.calibrator.engineering_constraints = new_constraint
self.on_constraints_changed()

def on_constraints_changed(self):
# Keep old settings in the dialog if they are present in the new params
# Remember everything except the name (should be the same) and
# the expression (which might be modified from the engineering
# constraints).
# the expression (which might be modified from the constraints).
to_remember = [
'value',
'vary',
Expand Down Expand Up @@ -239,6 +253,7 @@ def run_calibration(self, **extra_kwargs):

def on_calibration_finished(self):
self.update_config_from_instrument()
self.dialog.params_dict = self.calibrator.params

def update_config_from_instrument(self):
output_dict = instr_to_internal_dict(self.instr)
Expand All @@ -261,15 +276,9 @@ def update_config_from_instrument(self):

self.instrument_updated.emit()

# Update the tree_view in the GUI with the new refinements
self.update_refinements_tree_view()

# Update the drawn picks with their new locations
self.redraw_picks()

def update_refinements_tree_view(self):
self.dialog.params_dict = self.calibrator.params

def update_tth_distortion_from_dialog(self):
self.calibrator.tth_distortion = self.dialog.tth_distortion

Expand Down Expand Up @@ -321,3 +330,10 @@ def on_finished(self):
self.draw_picks(False)
# Ensure focus mode is off (even if it wasn't set)
self.set_focus_mode(False)


def _create_instr_params(instr: HEDMInstrument) -> lmfit.Parameters:
params = create_instr_params(instr)
params_dict = lmfit.Parameters()
params_dict.add_many(*params)
return params_dict
20 changes: 20 additions & 0 deletions hexrdgui/calibration/tree_item_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ def set_config_val(self, path, value):
# Now set the attribute on the param
attribute = path[-1].removeprefix('_')

if attribute == 'value':
# Make sure the min/max are shifted to accomodate this value
if value < param.min or value > param.max:
# Shift the min/max to accomodate, because lmfit won't
# let us set the value otherwise.
param.min = value - (param.value - param.min)
param.max = value + (param.max - param.value)
super().set_config_val(path[:-1] + ['_min'], param.min)
super().set_config_val(path[:-1] + ['_max'], param.max)
self.dict_modified.emit()

if '_min' in self.COLUMNS.values():
# Get the GUI to update
for name in ('_min', '_max'):
col = list(self.COLUMNS.values()).index(name) + 1
index = self.create_index(path[:-1], col)
item = self.get_item(index)
item.set_data(index.column(), getattr(param, name[1:]))
self.dataChanged.emit(index, index)

setattr(param, attribute, value)


Expand Down
Loading
Loading