Skip to content

Commit

Permalink
Merge pull request mantidproject#36588 from koshchii/dns_gui_single_c…
Browse files Browse the repository at this point in the history
…rystal_elastic_upload_main_skeleton_part_1

Main skeleton for Single Crystal Elastic mode of DNS Reduction GUI
  • Loading branch information
thomashampson authored Sep 4, 2024
2 parents dae4545 + a5d41e6 commit 16fe75a
Show file tree
Hide file tree
Showing 31 changed files with 1,314 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Interface Usage and Description

* :ref:`DNS Powder TOF <dns_powder_tof-ref>`
* :ref:`DNS Powder Elastic <dns_powder_elastic-ref>`
* DNS Single Crystal Elastic
* :ref:`DNS Single Crystal Elastic <dns_single_crystal_elastic-ref>`
* DNS Simulation

Feedback & Comments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ The buttons below the **Plotting** tab have the following functionality (from le
The rest of the buttons have the same functionality as in matplotlib's navigation toolbar.

When the mouse cursor is hovered over the plot, the cursor's :math:`(x, y)` coordinates together with the coresponding
:math:`hkl` values will be displayed on the right hand side of matplotlib's control buttons. In addition, the correponding
value of intensity (with an error-bar) of the closest measured data point will be displayed as well. (This does not give the
intensity of the quadrilateral, which could involve interpolation.)
:math:`hkl` values (in relative lattice units, r.l.u.) will be displayed on the right hand side of matplotlib's control buttons.
In addition, the correponding value of intensity (with an error-bar) of the closest measured data point will be displayed as well.
(This does not give the intensity of the quadrilateral, which could involve interpolation.)

The **X**, **Y** and **Z** input lines below the navigation buttons allow to manually specify the region to zoom into. The
syntax to be used inside these lines is similar to the python's list slicing (or dnsplot's range). For example, **0:2** in the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ def get_option_dict(self):
self.own_dict["subtract"] = self._plot_list
return self.own_dict

def _finish_script_run(self):
def _finish_script_run(self, options=None):
self._plot_list = self.model.get_plot_list()
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<x>0</x>
<y>0</y>
<width>701</width>
<height>25</height>
<height>24</height>
</rect>
</property>
<widget class="QMenu" name="menuFiles">
Expand All @@ -53,6 +53,7 @@
</property>
<addaction name="actionPowder_TOF"/>
<addaction name="actionPowder_Elastic"/>
<addaction name="actionSingle_Crystal_Elastic"/>
</widget>
<addaction name="menuChange_Mode"/>
</widget>
Expand Down Expand Up @@ -98,9 +99,9 @@
<string>Simulation</string>
</property>
</action>
<action name="actionSingle_crystal_elastic">
<action name="actionSingle_Crystal_Elastic">
<property name="text">
<string>Single Crystal elastic</string>
<string>Single Crystal Elastic</string>
</property>
</action>
<action name="actionPowder_Elastic">
Expand Down Expand Up @@ -128,9 +129,14 @@
<string>DNS website</string>
</property>
</action>
<action name="actionSC_elastic">
<action name="actionSingle_Crystal_Elastic">
<property name="text">
<string>Single crystal elastic</string>
<string>Single Crystal Elastic</string>
</property>
</action>
<action name="actionSingle_Crystal_Elastic">
<property name="text">
<string>Single Crystal Elastic</string>
</property>
</action>
</widget>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
DNSElasticPowderScriptGeneratorWidget,
)

# single crystal elastic
from mantidqtinterfaces.dns_single_crystal_elastic.options.elastic_single_crystal_options_widget import DNSElasticSCOptionsWidget
from mantidqtinterfaces.dns_single_crystal_elastic.plot.elastic_single_crystal_plot_widget import DNSElasticSCPlotWidget
from mantidqtinterfaces.dns_single_crystal_elastic.script_generator.elastic_single_crystal_script_generator_widget import (
DNSElasticSCScriptGeneratorWidget,
)


class DNSModus:
"""
Expand All @@ -48,6 +55,14 @@ def __init__(self, name, parent):
"xml_dump",
"plot_elastic_powder",
],
"single_crystal_elastic": [
"paths",
"file_selector",
"elastic_single_crystal_options",
"elastic_single_crystal_script_generator",
"xml_dump",
"plot_elastic_single_crystal",
],
}
# Yapf: disable
self._widget_map = {
Expand All @@ -61,6 +76,10 @@ def __init__(self, name, parent):
"elastic_powder_options": DNSElasticPowderOptionsWidget,
"elastic_powder_script_generator": DNSElasticPowderScriptGeneratorWidget,
"plot_elastic_powder": DNSElasticPowderPlotWidget,
# single crystal elastic
"elastic_single_crystal_options": DNSElasticSCOptionsWidget,
"elastic_single_crystal_script_generator": DNSElasticSCScriptGeneratorWidget,
"plot_elastic_single_crystal": DNSElasticSCPlotWidget,
}
# Yapf: enable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ def _format_view(self):
self.num_columns = self.model.get_active_model_column_count()
self.view.set_first_column_spanned(self.model.get_scan_range())
# expand all only in the case when the total number of files
# to display is less than 151 (more files to expand might take
# significant time to process)
# to display is less than 151 (more files to expand takes
# some time to execute)
file_count = self.model.get_number_of_files_in_treeview()
if file_count <= 150:
self.view.expand_all()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, name=None, view=None, parameter_abo=None, modus=None, parent=
self.command_line_reader = command_line_reader
self._parameter_abo = parameter_abo
self.model = self._parameter_abo
self._switch_mode("powder_elastic")
self._switch_mode("single_crystal_elastic")
# connect signals
self._attach_signal_slots()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@ def __init__(self, parent=None, app=None, within_mantid=None):
self.modus_mapping = {
self.ui.actionPowder_Elastic: "powder_elastic",
self.ui.actionPowder_TOF: "powder_tof",
self.ui.actionSingle_Crystal_Elastic: "single_crystal_elastic",
}
self.modus_titles = {
"powder_elastic": "DNS Reduction GUI - Powder Elastic",
"powder_tof": "DNS Reduction GUI - Powder TOF",
"single_crystal_elastic": "DNS Reduction GUI - Single Crystal Elastic",
}
for key in self.modus_mapping:
key.triggered.connect(self._modus_change)
Expand Down Expand Up @@ -101,7 +103,7 @@ def _help_button_clicked(self):
show_interface_help("direct/dns_reduction/DNS Reduction", QProcess(self))

def add_submenu(self, subview):
for menu in subview.menues:
for menu in subview.menus:
submenu = self.menu.insertMenu(self.ui.menuHelp.menuAction(), menu)
self.subview_menus.append(submenu)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def __init__(self, name=None, parent=None, app=None, within_mantid=None):
self.view = DNSReductionGUIView(parent=self, app=app, within_mantid=within_mantid)
self.parameter_abo = ParameterAbo()
self.model = self.parameter_abo
self.modus = DNSModus(name="powder_elastic", parent=self)
self.modus = DNSModus(name="single_crystal_elastic", parent=self)
self.presenter = DNSReductionGUIPresenter(
parent=self, view=self.view, modus=self.modus, name=name, parameter_abo=self.parameter_abo
)
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ def _generate_script(self):
self.view.show_status_message(error, 30, clear=True)
else:
self._script_number += 1
self._finish_script_run()
self._finish_script_run(options)
# activate copy script to clipboard button
self.view.pB_copy_to_clipboard.setEnabled(True)

def _finish_script_run(self):
def _finish_script_run(self, options):
pass

def _get_sample_data(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2023 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source
# & Institut Laue - Langevin
# SPDX - License - Identifier: GPL - 3.0 +
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2023 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source
# & Institut Laue - Langevin
# SPDX - License - Identifier: GPL - 3.0 +

"""
Class which loads and stores a single DNS datafile in a dictionary.
"""

import numpy as np
from matplotlib import path
from matplotlib import tri
from mantidqtinterfaces.dns_powder_tof.data_structures.object_dict import ObjectDict
from mantidqtinterfaces.dns_single_crystal_elastic.plot.elastic_single_crystal_helpers import angle_to_q


def _get_mesh(omega, two_theta, z_mesh):
omega_mesh_no_nan, two_theta_mesh_no_nan = np.meshgrid(omega, two_theta)
not_nan_pos = ~np.isnan(z_mesh)
omega_mesh_no_nan = omega_mesh_no_nan[not_nan_pos]
two_theta_mesh_no_nan = two_theta_mesh_no_nan[not_nan_pos]
z_mesh_no_nan = z_mesh[not_nan_pos]
return omega_mesh_no_nan, two_theta_mesh_no_nan, z_mesh_no_nan


def _get_unique(omega_mesh, two_theta_mesh):
omega = np.unique(omega_mesh)
two_theta = np.unique(two_theta_mesh)
return omega, two_theta


def _get_q_mesh(omega_mesh, two_theta_mesh, wavelength):
q_x, q_y = angle_to_q(two_theta=two_theta_mesh, omega=omega_mesh, wavelength=wavelength)
return q_x, q_y


def _get_hkl_mesh(qx_mesh, qy_mesh, dx, dy):
hklx_mesh = qx_mesh * dx / 2.0 / np.pi
hkly_mesh = qy_mesh * dy / 2.0 / np.pi
return hklx_mesh, hkly_mesh


class DNSScMap(ObjectDict):
"""
Class for storing data of a single DNS single crystal plot.
"""

def __init__(self, parameter, two_theta=None, omega=None, z_mesh=None, error_mesh=None):
super().__init__()
# non interpolated data:
omega_mesh, two_theta_mesh, z_mesh = _get_mesh(omega, two_theta, z_mesh)
omega_unique, two_theta_unique = _get_unique(omega_mesh, two_theta_mesh)
qx_mesh, qy_mesh = _get_q_mesh(omega_mesh, two_theta_mesh, parameter["wavelength"])
hklx_mesh, hkly_mesh = _get_hkl_mesh(qx_mesh, qy_mesh, parameter["dx"], parameter["dy"])
# setting attributes dictionary keys
self.omega_interpolated = None
self.two_theta = two_theta_unique
self.omega = omega_unique
self.omega_offset = parameter["omega_offset"]
self.hklx_mesh = hklx_mesh
self.hkly_mesh = hkly_mesh
self.z_mesh = z_mesh
self.error_mesh = error_mesh
self.dx = parameter["dx"]
self.dy = parameter["dy"]
self.hkl1 = parameter["hkl1"]
self.hkl2 = parameter["hkl2"]
self.wavelength = parameter["wavelength"]
self.hkl_mesh = [self.hklx_mesh, self.hkly_mesh, self.z_mesh]

def triangulate(self, mesh_name, switch=False):
plot_x, plot_y, _z = getattr(self, mesh_name)
self.triangulation = tri.Triangulation(plot_x.flatten(), plot_y.flatten())
return self.triangulation

def interpolate_triangulation(self, interpolation=0):
if self.triangulation is None:
return None
z = self.z_mesh
triangulator = self.triangulation
return [triangulator, z.flatten()]

def get_dns_map_border(self, mesh_name):
two_theta = self.two_theta
omega = self.omega
dns_path = np.zeros((2 * two_theta.size + 2 * omega.size, 2))
hkl_path = np.zeros((2 * two_theta.size + 2 * omega.size, 2))
dns_path[:, 0] = np.concatenate(
(two_theta[0] * np.ones(omega.size), two_theta, two_theta[-1] * np.ones(omega.size), np.flip(two_theta))
)
dns_path[:, 1] = np.concatenate((np.flip(omega), omega[0] * np.ones(two_theta.size), omega, omega[-1] * np.ones(two_theta.size)))
dns_path[:, 0], dns_path[:, 1] = angle_to_q(dns_path[:, 0], dns_path[:, 1], self.wavelength)
if "hkl" in mesh_name:
hkl_path[:, 0] = dns_path[:, 0] * self.dx / 2.0 / np.pi
hkl_path[:, 1] = dns_path[:, 1] * self.dy / 2.0 / np.pi
return path.Path(hkl_path)
return path.Path(dns_path)

def mask_triangles(self, mesh_name):
x, y, _z = getattr(self, mesh_name)
x = x.flatten()
y = y.flatten()
dns_path = self.get_dns_map_border(mesh_name)
triangles = self.triangulation.triangles
x_y_triangles = np.zeros((len(x[triangles]), 2))
x_y_triangles[:, 0] = np.mean(x[triangles], axis=1)
x_y_triangles[:, 1] = np.mean(y[triangles], axis=1)
maxi = dns_path.contains_points(x_y_triangles)
self.triangulation.set_mask(np.invert(maxi))
return self.triangulation
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2023 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source
# & Institut Laue - Langevin
# SPDX - License - Identifier: GPL - 3.0 +
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright &copy; 2023 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source
# & Institut Laue - Langevin
# SPDX - License - Identifier: GPL - 3.0 +

"""
DNS single crystal elastic options tab presenter of DNS reduction GUI.
"""

from mantidqtinterfaces.dns_powder_tof.options.common_options_presenter import DNSCommonOptionsPresenter


class DNSElasticSCOptionsPresenter(DNSCommonOptionsPresenter):
def __init__(self, name=None, parent=None, view=None, model=None):
super().__init__(parent=parent, name=name, view=view, model=model)
# connect signals
self._attach_signal_slots()

def _set_auto_two_theta_binning(self):
if self.param_dict["file_selector"]["full_data"]:
sample_data = self.param_dict["file_selector"]["full_data"]
two_theta_params_dict = get_automatic_two_theta_binning(sample_data)
own_options = self.get_option_dict()
for parameter in two_theta_params_dict.keys():
own_options[parameter] = two_theta_params_dict[parameter]
self.set_view_from_param()

def _set_auto_omega_binning(self):
if self.param_dict["file_selector"]["full_data"]:
sample_data = self.param_dict["file_selector"]["full_data"]
omega_params_dict = get_automatic_omega_binning(sample_data)
own_options = self.get_option_dict()
for parameter in omega_params_dict.keys():
own_options[parameter] = omega_params_dict[parameter]
self.set_view_from_param()

def _get_automatic_binning_state(self):
return self.view._map["automatic_binning"].isChecked()

def tab_got_focus(self):
auto_binning_is_on = self._get_automatic_binning_state()
if auto_binning_is_on:
self._set_auto_two_theta_binning()
self._set_auto_omega_binning()

def process_request(self):
own_options = self.get_option_dict()
if own_options["get_wavelength"]:
self._determine_wavelength()

def get_option_dict(self):
if self.view is not None:
self.own_dict.update(self.view.get_state())
return self.own_dict

def _attach_signal_slots(self):
self.view.sig_get_wavelength.connect(self._determine_wavelength)
self.view.sig_auto_binning_clicked.connect(self._set_auto_two_theta_binning)
self.view.sig_auto_binning_clicked.connect(self._set_auto_omega_binning)


def get_automatic_two_theta_binning(sample_data):
det_rot = [-x["det_rot"] for x in sample_data]
two_theta_last_det = 115.0
two_theta_max = max(det_rot) + two_theta_last_det
two_theta_min = min(det_rot)
two_theta_step = 0.5
number_two_theta_bins = int(round((two_theta_max - two_theta_min) / two_theta_step) + 1)
two_theta_binning_dict = {
"two_theta_min": two_theta_min,
"two_theta_max": two_theta_max,
"two_theta_bin_size": two_theta_step,
"nbins": number_two_theta_bins,
}
return two_theta_binning_dict


def get_automatic_omega_binning(sample_data):
omega = [x["sample_rot"] - x["det_rot"] for x in sample_data]
omega_min = min(omega)
omega_max = max(omega)
omega_step = 1.0
number_omega_bins = int(round((omega_max - omega_min) / omega_step) + 1)
omega_binning_dict = {"omega_min": omega_min, "omega_max": omega_max, "omega_bin_size": omega_step, "omega_nbins": number_omega_bins}
return omega_binning_dict
Loading

0 comments on commit 16fe75a

Please sign in to comment.