Skip to content
Open
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
75 changes: 69 additions & 6 deletions hexrdgui/calibration/tree_item_models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import numpy as np

from PySide6.QtCore import Qt
from PySide6.QtGui import QColor

Expand Down Expand Up @@ -34,27 +36,61 @@ def set_config_val(self, path, value):
# Now set the attribute on the param
attribute = path[-1].removeprefix('_')

if attribute in ('value', 'min', 'max', 'delta'):
# Check if there is a conversion we need to make before proceeding
config = self.config_path(path[:-1])
if config.get('_conversion_funcs'):
# Apply the conversion
value = config['_conversion_funcs']['from_display'](value)
# Swap the min/max if they ought to be swapped
# (due to the conversion resulting in an inverse proportionality)
if (
config.get('_min_max_inverted') and
attribute in ('min', 'max')
):
attribute = 'max' if attribute == 'min' else 'min'

if attribute == 'value':
# Make sure the min/max are shifted to accomodate this value
if value < param.min or value > param.max:
config = self.config_path(path[:-1])
conversion_funcs = config.get('_conversion_funcs')
min_key = '_min'
max_key = '_max'
if conversion_funcs and config.get('_min_max_inverted'):
min_key, max_key = max_key, min_key

def convert_if_needed(v):
if conversion_funcs is None:
return v

return conversion_funcs['to_display'](v)

# 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)
super().set_config_val(
path[:-1] + [min_key], convert_if_needed(param.min),
)
super().set_config_val(
path[:-1] + [max_key], convert_if_needed(param.max),
)

col = list(self.COLUMNS.values()).index(path[-1]) + 1
index = self.create_index(path[:-1], col)
self.dict_modified.emit(index)

if '_min' in self.COLUMNS.values():
# Get the GUI to update
for name in ('_min', '_max'):
for name, key in zip(('_min', '_max'), (min_key, max_key)):
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:]))
item.set_data(
index.column(),
convert_if_needed(getattr(param, key[1:])),
)
self.dataChanged.emit(index, index)

setattr(param, attribute, value)
Expand Down Expand Up @@ -82,7 +118,29 @@ def data(self, index, role):

return QColor(color)

return super().data(index, role)
data = super().data(index, role)

if (
role in (Qt.DisplayRole, Qt.EditRole) and
index.column() in self.BOUND_INDICES and
data is not None
):
# Check if there are any units that should be displayed
item = self.get_item(index)
path = self.path_to_item(item)
config = self.config_path(path)

if role == Qt.DisplayRole and config.get('_units'):
is_inf = isinstance(data, float) and np.isinf(data)
# Don't attach units to infinity
if not is_inf:
if isinstance(data, float):
# Format it into a string
data = f'{data:.6g}'

data = f"{data}{config['_units']}"

return data


class DefaultCalibrationTreeItemModel(CalibrationTreeItemModel):
Expand Down Expand Up @@ -116,7 +174,12 @@ def data(self, index, role):
if index.column() not in pair:
continue

if abs(item.data(pair[0]) - item.data(pair[1])) < atol:
data0 = item.data(pair[0])
data1 = item.data(pair[1])
if (
np.all([np.isinf(x) for x in (data0, data1)]) or
abs(data0 - data1) < atol
):
return QColor('red')

return super().data(index, role)
Expand Down
169 changes: 161 additions & 8 deletions hexrdgui/calibration/wppf_options_dialog.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import copy
from functools import partial
from pathlib import Path
import re
import sys
import time

import h5py
import lmfit
import matplotlib.pyplot as plt
import numpy as np
import re
import yaml

from PySide6.QtCore import QObject, Signal
Expand Down Expand Up @@ -1158,14 +1158,45 @@ def tree_view_dict_of_params(self):
# Keep track of which params have been used.
used_params = []

def create_param_item(param):
def create_param_item(param, units=None, conversion_funcs=None,
min_max_inverted=False):

# Convert to display units if needed
def convert_if_needed(x):
if conversion_funcs is None:
return x

return conversion_funcs['to_display'](x)

def convert_stderr_if_needed(x):
if x == '--':
return x

if conversion_funcs is None:
return x

if 'to_display_stderr' in conversion_funcs:
# Special conversion needed
return conversion_funcs['to_display_stderr'](
param.value,
x,
)

# Default to the regular conversion
return convert_if_needed(x)

used_params.append(param.name)
stderr = stderr_values.get(param.name, '--')
d = {
'_param': param,
'_value': param.value,
'_value': convert_if_needed(param.value),
'_vary': bool(param.vary),
'_stderr': stderr_values.get(param.name, '--'),
'_stderr': convert_stderr_if_needed(stderr),
'_units': units,
'_conversion_funcs': conversion_funcs,
'_min_max_inverted': min_max_inverted,
}

if self.delta_boundaries:
if not hasattr(param, 'delta'):
# We store the delta on the param object
Expand All @@ -1176,12 +1207,15 @@ def create_param_item(param):
]
param.delta = min(diffs)

d['_delta'] = param.delta
d['_delta'] = convert_if_needed(param.delta)
else:
d.update(**{
'_min': param.min,
'_max': param.max,
'_min': convert_if_needed(param.min),
'_max': convert_if_needed(param.max),
})
if min_max_inverted:
# Swap the min and max
d['_min'], d['_max'] = d['_max'], d['_min']

# Make a callback for when `vary` gets modified by the user.
f = partial(self.on_param_vary_modified, param=param)
Expand Down Expand Up @@ -1211,6 +1245,10 @@ def recursively_set_items(this_config, this_template):
else:
# Assume it is a string. Grab it if in the params.
if v in params:
units = None
if v == 'zero_error':
units = '°'

this_config[k] = create_param_item(params[v])
param_set = True

Expand Down Expand Up @@ -1321,8 +1359,46 @@ def recursively_format_mat(mat, this_config, this_template):
if '{mat}' in v:
v = v.format(mat=sanitized_mat)

# Determine if units and conversion funcs are needed
# We can't have a global dict of these, because some
# labels are the same. For example, 'α' is also in the
# stacking fault parameters.
units = None
conversion_funcs = None
min_max_inverted = False
prefix = sanitized_mat
if v == f'{prefix}_X':
# Provide wavelength in micrometers
wlen = HexrdConfig().beam_wavelength / 1e4
units = ' µm'
conversion_funcs = mat_lx_to_p_funcs_factory(wlen)
min_max_inverted = True
elif v == f'{prefix}_Y':
units = '%'
conversion_funcs = mat_ly_to_s_funcs
elif v == f'{prefix}_P':
# Provide wavelength in micrometers
wlen = HexrdConfig().beam_wavelength / 1e4
units = ' µm'
conversion_funcs = mat_gp_to_p_funcs_factory(wlen)
min_max_inverted = True
elif v in [f'{prefix}_{k}' for k in ('a', 'b', 'c')]:
units = ' Å'
conversion_funcs = nm_to_angstroms_funcs
elif v in [f'{prefix}_{k}' for k in ('α', 'β', 'γ')]:
units = '°'
elif re.search(rf'^{prefix}_s\d\d\d$', v):
# It is a stacking parameter
conversion_funcs = shkl_to_angstroms_minus_4_funcs
units = ' Å⁻⁴'

if v in params:
this_config[k] = create_param_item(params[v])
this_config[k] = create_param_item(
params[v],
units,
conversion_funcs,
min_max_inverted,
)

mat_dict = tree_dict.setdefault('Materials', {})
for mat in self.selected_materials:
Expand Down Expand Up @@ -2381,6 +2457,83 @@ def changed_signal(w):
return w.valueChanged


nm_to_angstroms_funcs = {
'to_display': lambda x: x * 10,
'from_display': lambda x: x / 10,
}


shkl_to_angstroms_minus_4_funcs = {
'to_display': lambda x: x / 1000,
'from_display': lambda x: x * 1000,
}


mat_ly_to_s_funcs = {
'to_display': lambda ly: ly * 100 * np.pi / 18000,
'from_display': lambda s: s / 100 / np.pi * 18000,
'to_display_stderr': lambda ly, ly_stderr: 100 * np.pi / 18000 * ly_stderr
}


def mat_lx_to_p_funcs_factory(wlen: float) -> dict:
k = 0.91

def to_display(lx: float):
if abs(lx) <= 1e-8:
return np.inf
elif np.isinf(lx):
return 0

return 18000 * k * wlen / np.pi / lx

def from_display(p: float):
if abs(p) <= 1e-8:
return np.inf
elif np.isinf(p):
return 0

return 18000 * k * wlen / np.pi / p

def to_display_stderr(lx: float, lx_stderr: float) -> float:
return 18000 * k * wlen / np.pi / (lx**2) * lx_stderr

return {
'to_display': to_display,
'from_display': from_display,
'to_display_stderr': to_display_stderr,
}


def mat_gp_to_p_funcs_factory(wlen: float) -> dict:
k = 0.91

def to_display(gp: float) -> float:
if abs(gp) <= 1e-8:
return np.inf
elif np.isinf(gp):
return 0

return 18000 * k * wlen / np.pi / np.sqrt(gp)

def from_display(p: float) -> float:
if abs(p) <= 1e-8:
return np.inf
elif np.isinf(p):
return 0

return (18000 * k * wlen / np.pi / p)**2

def to_display_stderr(gp: float, gp_stderr: float) -> float:
return 9000 * k * wlen / np.pi / (gp**1.5) * gp_stderr

return {
'to_display': to_display,
'from_display': from_display,
'to_display_stderr': to_display_stderr,
}


if __name__ == '__main__':
from PySide6.QtWidgets import QApplication

Expand Down
4 changes: 2 additions & 2 deletions hexrdgui/resources/wppf/tree_views/LeBail.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ Materials:
α: '{mat}_sf_alpha'
β: '{mat}_twin_beta'
Peak Broadening:
Lorentzian Scherrer Broadening: '{mat}_X'
Gaussian Scherrer Broadening: '{mat}_P'
Lorentzian Particle Size: '{mat}_X'
Gaussian Particle Size: '{mat}_P'
Microstrain: '{mat}_Y'
Lattice Constants:
a: '{mat}_a'
Expand Down
4 changes: 2 additions & 2 deletions hexrdgui/resources/wppf/tree_views/Rietveld.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Materials:
β: '{mat}_twin_beta'
Phase Fraction: '{mat}_phase_fraction'
Peak Broadening:
Lorentzian Scherrer Broadening: '{mat}_X'
Gaussian Scherrer Broadening: '{mat}_P'
Lorentzian Particle Size: '{mat}_X'
Gaussian Particle Size: '{mat}_P'
Microstrain: '{mat}_Y'
Lattice Constants:
a: '{mat}_a'
Expand Down
8 changes: 8 additions & 0 deletions hexrdgui/tree_views/multi_column_dict_tree_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,14 @@ def state_changed(self):

def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)

if isinstance(editor, ScientificDoubleSpinBox):
item = self.model.get_item(index)
path = self.model.path_to_item(item)
config = self.model.config_path(path)
if config.get('_units'):
editor.setSuffix(config['_units'])

if self.tree_view.has_disabled_editors:
item = self.model.get_item(index)
path = self.model.path_to_item(item) + [index.column()]
Expand Down
Loading