diff --git a/CHAP/edd/select_material_params_gui.py b/CHAP/edd/select_material_params_gui.py new file mode 100755 index 0000000..eca8f1d --- /dev/null +++ b/CHAP/edd/select_material_params_gui.py @@ -0,0 +1,332 @@ +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +import matplotlib.pyplot as plt +import numpy as np +import tkinter as tk +from tkinter import messagebox + +class MaterialParamSelector: + def __init__(self, root, x, y, tth, label, preselected_materials, + on_complete): + + self.root = root + self.root.title('Material Parameter Selection') + self.on_complete = on_complete # Completion callback + + # Reference data + self.ref_data_x = x + self.ref_data_y = y + self.ref_data_label = label + self.tth = tth + + # Materials + self.materials = [] + self.selected_material = None + + # Create plot + self.figure, self.ax = plt.subplots() + self.legend_handles = [] + self.canvas = FigureCanvasTkAgg(self.figure, self.root) + self.canvas.get_tk_widget().grid(row=0, column=0, rowspan=12) + + # Widgets for material list and parameters + self.material_listbox = tk.Listbox(root, height=5) + self.material_listbox.grid(row=0, column=1, rowspan=2) + self.material_listbox.bind( + '<>', self.on_material_select) + + self.add_material_button = tk.Button( + root, text='Add Material', command=self.add_material) + self.add_material_button.grid(row=0, column=2) + + self.remove_material_button = tk.Button( + root, text='Remove Material', command=self.remove_material) + self.remove_material_button.grid(row=1, column=2) + + # Parameter fields + self.fields = {} + for i, field in enumerate( + ['Material Name', 'Space Group', + 'a', 'b', 'c', 'alpha', 'beta', 'gamma']): + if i > 1: + units = 'Angstroms' if i < 5 else 'degrees' + text = f'{field} ({units})' + else: + text = field + tk.Label(root, text=text).grid(row=i+2, column=1) + entry = tk.Entry(root) + entry.grid(row=i+2, column=2) + self.fields[field] = entry + + self.update_button = tk.Button( + root, text='Update Material Properties', + command=self.update_material) + self.update_button.grid(row=11, column=1, columnspan=2) + self.update_button = tk.Button( + root, text='Update Material Properties', + command=self.update_material) + self.update_button.grid(row=11, column=1, columnspan=2) + + self.confirm_button = tk.Button( + root, text='Confirm\nAll\nSelected\nMaterial\nProperties', + command=self.on_close) + self.confirm_button.grid(row=0, column=3, rowspan=12) + + # Initial Material Data + if not preselected_materials: + self.materials = [None] + self.add_material(None) + else: + for material in preselected_materials: + self.add_material(material) + self.selected_material = 0 + + # Overwrite the root window's close action to call `self.on_close` + self.root.protocol('WM_DELETE_WINDOW', self.on_close) + + def plot_reference_data(self): + # Plot reference data as a simple sine wave for illustration + handle = self.ax.plot( + self.ref_data_x, self.ref_data_y, label=self.ref_data_label) + self.legend_handles = handle + self.ax.legend(handles=self.legend_handles) + self.ax.set_xlabel('Energy (keV)') + self.ax.set_ylabel('Intensity (a.u)') + self.canvas.draw() + + def add_material(self, new_material=None): + from CHAP.edd.models import MaterialConfig + if new_material is None: + new_material = MaterialConfig( + material_name='Ti64', + sgnum=194, + lattice_parameters=[2.9217, 4.66027] + ) + self.materials.append(new_material) + self.material_listbox.insert(tk.END, new_material.material_name) + self.material_listbox.select_set(tk.END) + self.on_material_select(None) + self.update_plot() + + def remove_material(self): + if self.selected_material is not None: + self.materials.pop(self.selected_material) + self.material_listbox.delete(self.selected_material) + self.selected_material = None + self.clear_fields() + self.update_plot() + + def update_material(self): + from CHAP.edd.utils import make_material + + if self.selected_material is None: + return + + material = self.materials[self.selected_material] + try: + # Retrieve values from fields + name = self.fields['Material Name'].get() + sgnum = int(self.fields['Space Group'].get()) + lattice_parameters = [ + float(self.fields[param].get()) + for param in ('a', 'b', 'c', 'alpha', 'beta', 'gamma') + ] + # Make a hexrd material from those values so we can + # propagate any other updates required by the material's + # symmetries + _material = make_material(name, sgnum, lattice_parameters) + material.material_name = name + material.sgnum = _material.sgnum + material.lattice_parameters = [ + _material.latticeParameters[i].value for i in range(6) + ] + material._material = _material + # If the updated field forces other field(s) to get new + # values (because of space group symmetries), propagate + # those new values to the gui entries too. + for key, entry in self.fields.items(): + if key == 'Material Name': + continue + entry.delete(0, tk.END) + self.fields['Space Group'].insert(0, str(material.sgnum)) + for i, key in enumerate(('a', 'b', 'c', 'alpha', 'beta', 'gamma')): + self.fields[key].insert( + 0, str(_material.latticeParameters[i].value)) + + # Update the listbox name display + self.material_listbox.delete(self.selected_material) + self.material_listbox.insert( + self.selected_material, material.material_name) + self.update_plot() + except ValueError: + messagebox.showerror( + 'Invalid input', + 'Please enter valid numbers for lattice parameters.') + + def on_material_select(self, event): + if len(self.material_listbox.curselection()) == 0: + # Listbox item deselection event can be ignored + return + # Update the selected material index + self.selected_material = self.material_listbox.curselection()[0] + material = self.materials[self.selected_material] + self.clear_fields() + self.fields['Material Name'].insert(0, material.material_name) + self.fields['Space Group'].insert(0, str(material.sgnum)) + for i, key in enumerate(('a', 'b', 'c', 'alpha', 'beta', 'gamma')): + self.fields[key].insert( + 0, str(material._material.latticeParameters[i].value)) + + def clear_fields(self): + for entry in self.fields.values(): + entry.delete(0, tk.END) + + def update_plot(self): + from CHAP.edd.utils import ( + get_unique_hkls_ds, get_peak_locations + ) + self.ax.cla() + self.legend_handles = [] + self.plot_reference_data() # Re-plot reference data + + # Plot each material's hkl peak locations + for i, material in enumerate(self.materials): + hkls, ds = get_unique_hkls_ds([material]) + E0s = get_peak_locations(ds, self.tth) + for hkl, E0 in zip(hkls, E0s): + if E0 < min(self.ref_data_x) or E0 > max(self.ref_data_x): + continue + line = self.ax.axvline( + E0, c=f'C{i+1}', ls='--', lw=1, + label=material.material_name) + self.ax.text(E0, 1, str(hkl)[1:-1], c=f'C{i+1}', + ha='right', va='top', rotation=90, + transform=self.ax.get_xaxis_transform()) + self.legend_handles.append(line) + self.ax.legend(handles=self.legend_handles) + self.canvas.draw() + + def on_close(self): + """Handle closing the GUI and triggering the on_complete + callback.""" + if self.on_complete: + self.on_complete(self.materials, self.figure) + self.root.destroy() # Close the tkinter root window + + +def run_material_selector( + x, y, tth, preselected_materials=None, label='Reference Data', + on_complete=None, interactive=False): + """Run the MaterialParamSelector tkinter application. + + :param x: MCA channel energies. + :type x: np.ndarray + :param y: MCA intensities. + :type y: np.ndarray + :param tth: The (calibrated) 2θ angle. + :type tth: float + :param preselected_materials: Materials to get HKLs and lattice + spacings for. + :type preselected_materials: list[hexrd.material.Material], optional + :param label: Legend label for the 1D plot of reference MCA data + from the parameters `x`, `y`, defaults to `"Reference Data"`. + :type label: str, optional + :param on_complete: Callback function to handle completion of the + material selection, defaults to `None`. + :type on_complete: Callable, optional + :param interactive: Show the plot and allow user interactions with + the matplotlib figure, defaults to `False`. + :type interactive: bool, optional + :return: The selected materials for the strain analyses. + :rtype: list[CHAP.edd.models.MaterialConfig] + """ + import tkinter as tk + + # Initialize the main application window + root = tk.Tk() + + # Create the material parameter selection GUI within the main + # window + # This GUI allows the user to adjust and visualize lattice + # parameters and space group + app = MaterialParamSelector( + root, x, y, tth, preselected_materials, label, on_complete) + + if interactive: + # If interactive mode is enabled, start the GUI event loop to + # allow user interaction + root.mainloop() + else: + # If not in interactive mode, immediately close the + # application + app.on_close() + + +def select_material_params( + x, y, tth, preselected_materials=None, label='Reference Data', + interactive=False, filename=None): + """Interactively adjust the lattice parameters and space group for + a list of materials. It is possible to add / remove materials from + the list. + + :param x: MCA channel energies. + :type x: np.ndarray + :param y: MCA intensities. + :type y: np.ndarray + :param tth: The (calibrated) 2&theta angle. + :type tth: float + :param preselected_materials: Materials to get HKLs and + lattice spacings for. + :type preselected_materials: list[hexrd.material.Material], + optional + :param label: Legend label for the 1D plot of reference MCA data + from the parameters `x`, `y`, defaults to `"Reference Data"`. + :type label: str, optional + :param interactive: Show the plot and allow user interactions with + the matplotlib figure, defaults to `False`. + :type interactive: bool, optional + :param filename: Save a .png of the plot to filename, defaults to + `None`, in which case the plot is not saved. + :type filename: str, optional + :return: The selected materials for the strain analyses. + :rtype: list[CHAP.edd.models.MaterialConfig] + """ + from CHAP.edd.select_material_params_gui import run_material_selector + # Run the MaterialParamSelector with the callback function to + # handle materials data + materials = None + figure = None + def on_complete(_materials, _figure): + nonlocal materials, figure + materials = _materials + figure = _figure + + run_material_selector(x, y, tth, label, preselected_materials, + on_complete, interactive) + + if filename is not None: + figure.savefig(filename) + + return materials + + +if __name__ == '__main__': + import numpy as np + from CHAP.edd.models import MaterialConfig + + x = np.linspace(40, 100, 100) + y = np.sin(x) + tth = 5 + + preselected_materials = [ + MaterialConfig( + material_name='Ti64_orig', + sgnum=194, + lattice_parameters=[2.9217, 4.66027] + ) + ] + materials = select_material_params( + x, y, tth, preselected_materials=preselected_materials, + interactive=True, + filename=None, + ) + print(f'Returned materials: {materials}') diff --git a/CHAP/edd/utils.py b/CHAP/edd/utils.py index 05e3905..ac64266 100755 --- a/CHAP/edd/utils.py +++ b/CHAP/edd/utils.py @@ -277,273 +277,12 @@ def confirm(event): return tth_new_guess -def select_material_params_old( - x, y, tth, materials=None, label='Reference Data', interactive=False, - filename=None): - """Interactively select the lattice parameters and space group for - a list of materials. A matplotlib figure will be shown with a plot - of the reference data (`x` and `y`). The figure will contain - widgets to add / remove materials and update selections for space - group number and lattice parameters for each one. The HKLs for the - materials defined by the widgets' values will be shown over the - reference data and updated when the widgets' values are - updated. - - :param x: MCA channel energies. - :type x: np.ndarray - :param y: MCA intensities. - :type y: np.ndarray - :param tth: The (calibrated) 2&theta angle. - :type tth: float - :param materials: Materials to get HKLs and lattice spacings for. - :type materials: list[hexrd.material.Material], optional - :param label: Legend label for the 1D plot of reference MCA data - from the parameters `x`, `y`, defaults to `"Reference Data"`. - :type label: str, optional - :param interactive: Show the plot and allow user interactions with - the matplotlib figure, defaults to `False`. - :type interactive: bool, optional - :param filename: Save a .png of the plot to filename, defaults to - `None`, in which case the plot is not saved. - :type filename: str, optional - :return: The selected materials for the strain analyses. - :rtype: list[CHAP.edd.models.MaterialConfig] - """ - # Third party modules - if interactive or filename is not None: - import matplotlib.pyplot as plt - from matplotlib.widgets import ( - Button, - TextBox, - ) - - # Local modules - from CHAP.edd.models import MaterialConfig - - def change_error_text(error): - """Change the error text.""" - if error_texts: - error_texts[0].remove() - error_texts.pop() - error_texts.append(plt.figtext(*error_pos, error, **error_props)) - - def draw_plot(): - """Redraw plot of reference data and HKL locations based on - the `_materials` list on the Matplotlib axes `ax`. - """ - ax.clear() - ax.set_title('Reference Data') - ax.set_xlabel('MCA channel energy (keV)') - ax.set_ylabel('MCA intensity (counts)') - ax.set_xlim(x[0], x[-1]) - ax.plot(x, y, label=label) - for i, material in enumerate(_materials): - hkls, ds = get_unique_hkls_ds([material]) - E0s = get_peak_locations(ds, tth) - for hkl, E0 in zip(hkls, E0s): - if x[0] <= E0 <= x[-1]: - ax.axvline(E0, c=f'C{i}', ls='--', lw=1) - ax.text(E0, 1, str(hkl)[1:-1], c=f'C{i}', - ha='right', va='top', rotation=90, - transform=ax.get_xaxis_transform()) - ax.legend() - ax.get_figure().canvas.draw() - - def add_material(*args, material=None): - """Callback function for the "Add material" button to add a new - row of material-property-editing widgets to the figure and - update the plot with new HKLs. - """ - if error_texts: - error_texts[0].remove() - error_texts.pop() - if material is None: - material = make_material('new_material', 225, 3.0) - _materials.append(material) - elif isinstance(material, MaterialConfig): - material = material._material - bottom = len(_materials) * 0.075 - plt.subplots_adjust(bottom=bottom + 0.125) - name_input = TextBox(plt.axes([0.1, bottom, 0.09, 0.05]), - 'Material: ', - initial=material.name) - sgnum_input = TextBox(plt.axes([0.3, bottom, 0.06, 0.05]), - 'Space Group: ', - initial=material.sgnum) - a_input = TextBox(plt.axes([0.4, bottom, 0.06, 0.05]), - '$a$ ($\\AA$): ', - initial=material.latticeParameters[0].value) - b_input = TextBox(plt.axes([0.5, bottom, 0.06, 0.05]), - '$b$ ($\\AA$): ', - initial=material.latticeParameters[1].value) - c_input = TextBox(plt.axes([0.6, bottom, 0.06, 0.05]), - '$c$ ($\\AA$): ', - initial=material.latticeParameters[2].value) - alpha_input = TextBox(plt.axes([0.7, bottom, 0.06, 0.05]), - '$\\alpha$ ($\\degree$): ', - initial=material.latticeParameters[3].value) - beta_input = TextBox(plt.axes([0.8, bottom, 0.06, 0.05]), - '$\\beta$ ($\\degree$): ', - initial=material.latticeParameters[4].value) - gamma_input = TextBox(plt.axes([0.9, bottom, 0.06, 0.05]), - '$\\gamma$ ($\\degree$): ', - initial=material.latticeParameters[5].value) - widgets.append( - (name_input, sgnum_input, a_input, b_input, c_input, - alpha_input, beta_input, gamma_input)) - widget_callbacks.append( - [(widget, widget.on_submit(update_materials)) \ - for widget in widgets[-1]]) - draw_plot() - - def update_materials(*args, **kwargs): - """Callback function for the material-property-editing widgets - button to validate input material properties from widgets, - update the `_materials` list, and redraw the plot. - """ - def set_vals(material_i): - """Set all widget values from the `_materials` list for a - particular material. - """ - material = _materials[material_i] - # Temporarily disconnect widget callbacks - callbacks = widget_callbacks[material_i+2] - for widget, callback in callbacks: - widget.disconnect(callback) - # Set widget values - name_input, sgnum_input, \ - a_input, b_input, c_input, \ - alpha_input, beta_input, gamma_input = widgets[material_i] - name_input.set_val(material.name) - sgnum_input.set_val(material.sgnum) - a_input.set_val(material.latticeParameters[0].value) - b_input.set_val(material.latticeParameters[1].value) - c_input.set_val(material.latticeParameters[2].value) - alpha_input.set_val(material.latticeParameters[3].value) - beta_input.set_val(material.latticeParameters[4].value) - gamma_input.set_val(material.latticeParameters[5].value) - # Reconnect widget callbacks - for i, (w, _) in enumerate(widget_callbacks[material_i+2]): - widget_callbacks[material_i+2][i] = ( - w, w.on_submit(update_materials)) - - # Update the _materials list - for i, (material, - (name_input, sgnum_input, - a_input, b_input, c_input, - alpha_input, beta_input, gamma_input)) \ - in enumerate(zip(_materials, widgets)): - # Skip if no parameters were changes on this material - old_material_params = ( - material.name, material.sgnum, - [material.latticeParameters[i].value for i in range(6)] - ) - new_material_params = ( - name_input.text, int(sgnum_input.text), - [float(a_input.text), float(b_input.text), float(c_input.text), - float(alpha_input.text), float(beta_input.text), - float(gamma_input.text)] - ) - if old_material_params == new_material_params: - continue - try: - new_material = make_material(*new_material_params) - except: - change_error_text(f'Bad input for {material.name}') - else: - _materials[i] = new_material - finally: - set_vals(i) - - # Redraw reference data plot - draw_plot() - - def confirm(event): - """Callback function for the "Confirm" button.""" - if error_texts: - error_texts[0].remove() - error_texts.pop() - plt.close() - - widgets = [] - widget_callbacks = [] - error_texts = [] - - if materials is None: - _materials = [] - else: - _materials = deepcopy(materials) - for i, m in enumerate(_materials): - if isinstance(m, MaterialConfig): - _materials[i] = m._material - - # Create the Matplotlib figure - if interactive or filename is not None: - fig, ax = plt.subplots(figsize=(11, 8.5)) - - if not interactive: - - draw_plot() - - else: - - error_pos = (0.5, 0.95) - error_props = { - 'fontsize': 'x-large', 'ha': 'center', 'va': 'bottom'} - - plt.subplots_adjust(bottom=0.1) - - # Setup "Add material" button - add_material_btn = Button( - plt.axes([0.125, 0.015, 0.1, 0.05]), 'Add material') - add_material_cid = add_material_btn.on_clicked(add_material) - widget_callbacks.append([(add_material_btn, add_material_cid)]) - - # Setup "Confirm" button - confirm_btn = Button(plt.axes([0.75, 0.015, 0.1, 0.05]), 'Confirm') - confirm_cid = confirm_btn.on_clicked(confirm) - widget_callbacks.append([(confirm_btn, confirm_cid)]) - - # Setup material-property-editing buttons for each material - for material in _materials: - add_material(material=material) - - # Show figure for user interaction - plt.show() - - # Disconnect all widget callbacks when figure is closed - # and remove the buttons before returning the figure - for group in widget_callbacks: - for widget, callback in group: - widget.disconnect(callback) - widget.ax.remove() - - # Save the figures if requested and close - fig.tight_layout() - if filename is not None: - fig.savefig(filename) - plt.close() - - new_materials = [ - MaterialConfig( - material_name=m.name, sgnum=m.sgnum, - lattice_parameters=[ - m.latticeParameters[i].value for i in range(6)]) - for m in _materials] - - return new_materials - - def select_material_params( x, y, tth, preselected_materials=None, label='Reference Data', interactive=False, filename=None): - """Interactively select the lattice parameters and space group for - a list of materials. A matplotlib figure will be shown with a plot - of the reference data (`x` and `y`). The figure will contain - widgets to modify, add, or remove materials. The HKLs for the - materials defined by the widgets' values will be shown over the - reference data and updated when the widgets' values are - updated. + """Interactively adjust the lattice parameters and space group for + a list of materials. It is possible to add / remove materials from + the list. :param x: MCA channel energies. :type x: np.ndarray @@ -567,309 +306,22 @@ def select_material_params( :return: The selected materials for the strain analyses. :rtype: list[CHAP.edd.models.MaterialConfig] """ - # Third party modules - if interactive or filename is not None: - from hexrd.material import Material - import matplotlib.pyplot as plt - from matplotlib.widgets import ( - Button, - RadioButtons, - ) - - # Local modules - from CHAP.edd.models import MaterialConfig - from CHAP.utils.general import round_to_n + from CHAP.edd.select_material_params_gui import run_material_selector - def add_material(new_material): - """Add a new material to the selected materials.""" - if isinstance(new_material, Material): - m = new_material - else: - if not isinstance(new_material, MaterialConfig): - new_material = MaterialConfig(**new_material) - m = new_material._material - materials.append(m) - lat_params = [round_to_n(m.latticeParameters[i].value, 6) - for i in range(6)] - bottom = 0.05*len(materials) - if interactive: - bottom += 0.075 - mat_texts.append( - plt.figtext( - 0.15, bottom, - f'- {m.name}: sgnum = {m.sgnum}, lat params = {lat_params}', - fontsize='large', ha='left', va='center')) - - def modify(event): - """Callback function for the "Modify" button.""" - # Select material - for mat_text in mat_texts: - mat_text.remove() - mat_texts.clear() - for button in buttons: - button[0].disconnect(button[1]) - button[0].ax.remove() - buttons.clear() - modified_material.clear() - if len(materials) == 1: - modified_material.append(materials[0].name) - plt.close() - else: - def modify_material(label): - modified_material.append(label) - radio_btn.disconnect(radio_cid) - radio_btn.ax.remove() - plt.close() - - mat_texts.append( - plt.figtext( - 0.1, 0.1 + 0.05*len(materials), - 'Select a material to modify:', - fontsize='x-large', ha='left', va='center')) - radio_btn = RadioButtons( - plt.axes([0.1, 0.05, 0.3, 0.05*len(materials)]), - labels = list(reversed([m.name for m in materials])), - activecolor='k') - radio_cid = radio_btn.on_clicked(modify_material) - plt.draw() + materials = None + figure = None + def on_complete(_materials, _figure): + nonlocal materials, figure + materials = _materials + figure = _figure - def add(event): - """Callback function for the "Add" button.""" - added_material.append(True) - plt.close() - - def remove(event): - """Callback function for the "Remove" button.""" - for mat_text in mat_texts: - mat_text.remove() - mat_texts.clear() - for button in buttons: - button[0].disconnect(button[1]) - button[0].ax.remove() - buttons.clear() - if len(materials) == 1: - removed_material.clear() - removed_material.append(materials[0].name) - plt.close() - else: - def remove_material(label): - removed_material.clear() - removed_material.append(label) - radio_btn.disconnect(radio_cid) - radio_btn.ax.remove() - plt.close() - - mat_texts.append( - plt.figtext( - 0.1, 0.1 + 0.05*len(materials), - 'Select a material to remove:', - fontsize='x-large', ha='left', va='center')) - radio_btn = RadioButtons( - plt.axes([0.1, 0.05, 0.3, 0.05*len(materials)]), - labels = list(reversed([m.name for m in materials])), - activecolor='k') - removed_material.append(radio_btn.value_selected) - radio_cid = radio_btn.on_clicked(remove_material) - plt.draw() - - def accept(event): - """Callback function for the "Accept" button.""" - plt.close() - - if not interactive and filename is None: - if preselected_materials is None: - raise RuntimeError( - 'If the material properties are not explicitly provided, ' - f'{self.__class__.__name__} must be run with ' - '`interactive=True`.') - return preselected_materials - - materials = [] - modified_material = [] - added_material = [] - removed_material = [] - mat_texts = [] - buttons = [] - - # Create figure - fig, ax = plt.subplots(figsize=(11, 8.5)) - ax.set_title(label, fontsize='x-large') - ax.set_xlabel('MCA channel energy (keV)', fontsize='large') - ax.set_ylabel('MCA intensity (counts)', fontsize='large') - ax.set_xlim(x[0], x[-1]) - ax.plot(x, y) - - # Add materials - if preselected_materials is None: - preselected_materials = [] - for m in reversed(preselected_materials): - add_material(m) - - # Add materials to figure - for i, material in enumerate(materials): - hkls, ds = get_unique_hkls_ds([material]) - E0s = get_peak_locations(ds, tth) - for hkl, E0 in zip(hkls, E0s): - if x[0] <= E0 <= x[-1]: - ax.axvline(E0, c=f'C{i}', ls='--', lw=1) - ax.text(E0, 1, str(hkl)[1:-1], c=f'C{i}', - ha='right', va='top', rotation=90, - transform=ax.get_xaxis_transform()) - - if not interactive: - - if materials: - mat_texts.append( - plt.figtext( - 0.1, 0.05 + 0.05*len(materials), - 'Currently selected materials:', - fontsize='x-large', ha='left', va='center')) - plt.subplots_adjust(bottom=0.125 + 0.05*len(materials)) - - else: - - if materials: - mat_texts.append( - plt.figtext( - 0.1, 0.125 + 0.05*len(materials), - 'Currently selected materials:', - fontsize='x-large', ha='left', va='center')) - else: - mat_texts.append( - plt.figtext( - 0.1, 0.125, 'Add at least one material', - fontsize='x-large', ha='left', va='center')) - plt.subplots_adjust(bottom=0.2 + 0.05*len(materials)) - - # Setup "Modify" button - if materials: - modify_btn = Button( - plt.axes([0.1, 0.025, 0.15, 0.05]), 'Modify material') - modify_cid = modify_btn.on_clicked(modify) - buttons.append((modify_btn, modify_cid)) - - # Setup "Add" button - add_btn = Button(plt.axes([0.317, 0.025, 0.15, 0.05]), 'Add material') - add_cid = add_btn.on_clicked(add) - buttons.append((add_btn, add_cid)) - - # Setup "Remove" button - if materials: - remove_btn = Button( - plt.axes([0.533, 0.025, 0.15, 0.05]), 'Remove material') - remove_cid = remove_btn.on_clicked(remove) - buttons.append((remove_btn, remove_cid)) - - # Setup "Accept" button - accept_btn = Button( - plt.axes([0.75, 0.025, 0.15, 0.05]), 'Accept materials') - accept_cid = accept_btn.on_clicked(accept) - buttons.append((accept_btn, accept_cid)) - - plt.show() - - # Disconnect all widget callbacks when figure is closed - # and remove the buttons before returning the figure - for button in buttons: - button[0].disconnect(button[1]) - button[0].ax.remove() - buttons.clear() + run_material_selector(x, y, tth, label, preselected_materials, + on_complete, interactive) if filename is not None: - for mat_text in mat_texts: - pos = mat_text.get_position() - if interactive: - mat_text.set_position((pos[0], pos[1]-0.075)) - else: - mat_text.set_position(pos) - if mat_text.get_text() == 'Currently selected materials:': - mat_text.set_text('Selected materials:') - mat_text.set_in_layout(True) - fig.tight_layout(rect=(0, 0.05 + 0.05*len(materials), 1, 1)) - fig.savefig(filename) - plt.close() + figure.savefig(filename) - if modified_material: - # Local modules - from CHAP.utils.general import input_num_list - - for index, m in enumerate(materials): - if m.name in modified_material: - break - error = True - while error: - try: - print(f'\nCurrent lattice parameters for {m.name}: ' - f'{[m.latticeParameters[i].value for i in range(6)]}') - lat_params = input_num_list( - 'Enter updated lattice parameters for this material', - raise_error=True, log=False) - new_material = MaterialConfig( - material_name=m.name, sgnum=m.sgnum, - lattice_parameters=lat_params) - materials[index] = new_material - error = False - except ( - ValueError, TypeError, SyntaxError, MemoryError, - RecursionError, IndexError) as e: - print(f'{e}: try again') - except: - raise - return select_material_params( - x, y, tth, preselected_materials=materials, label=label, - interactive=interactive, filename=filename) - - if added_material: - # Local modules - from CHAP.utils.general import ( - input_int, - input_num_list, - ) - - error = True - while error: - try: - print('\nEnter the name of the material to be added:') - name = input() - sgnum = input_int( - 'Enter the space group for this material', - raise_error=True, log=False) - lat_params = input_num_list( - 'Enter the lattice parameters for this material', - raise_error=True, log=False) - print() - new_material = MaterialConfig( - material_name=name, sgnum=sgnum, - lattice_parameters=lat_params) - error = False - except ( - ValueError, TypeError, SyntaxError, MemoryError, - RecursionError, IndexError) as e: - print(f'{e}: try again') - except: - raise - materials.append(new_material) - return select_material_params( - x, y, tth, preselected_materials=materials, label=label, - interactive=interactive, filename=filename) - - if removed_material: - return select_material_params( - x, y, tth, - preselected_materials=[ - m for m in materials if m.name not in removed_material], - label=label, interactive=interactive, filename=filename) - - if not materials: - return select_material_params( - x, y, tth, label=label, interactive=interactive, filename=filename) - - return [ - MaterialConfig( - material_name=m.name, sgnum=m.sgnum, - lattice_parameters=[ - m.latticeParameters[i].value for i in range(6)]) - for m in materials] + return materials def select_mask_and_hkls(x, y, hkls, ds, tth, preselected_bin_ranges=None,