Skip to content

Commit cabd02b

Browse files
authored
Add partial functionality to visualisation features in plotting tab (#39612)
This PR contains changes that activate functionality of several buttons in Plotting tab of Single Crystal Elastic mode of the DNS Reduction interface. In particular, the following buttons are now functional: 1) "Grid selector", 2) "Grid style selector", 3) "Font size selector", 4) "Reverse color map". This PR includes a second part of adding advanced plotting capabilities for the Single Cristal Elastic mode of DNS Reduction GUI. Complete integration of the Single Cristal Elastic mode into the DNS Reduction GUI will be implemented in several steps (in order to ensure reasonable PR sizes and reduce the workload for the reviewers). Fixes part 6b of #36407
1 parent 8ddeb1f commit cabd02b

18 files changed

+1102
-19
lines changed

docs/source/interfaces/direct/dns_reduction/dns_single_crystal_elastic_plotting_tab.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ The buttons below the **Plotting** tab have the following functionality (from le
3232
+------------------------------------------------+----------------------------------------------------------------------------+
3333
| |exp-grid| Grid selector | Toggle through different grid sizes. |
3434
+------------------------------------------------+----------------------------------------------------------------------------+
35-
| |exp-crystal-axes| Grid style selector | Switch between a) orthogonal axes defined by :math:`n_x` and :math:`n_y`, |
36-
| | or b) crystalographic principal axes lying in the horizontal scattering |
37-
| | plane. The latter works only if two principle axes change in the |
35+
| |exp-crystal-axes| Grid style selector | Enable/disable crystalographic principal axes laying in the horizontal |
36+
| | scattering plane. It works only if two principle axes change in the |
3837
| | horizontal plane. |
3938
+------------------------------------------------+----------------------------------------------------------------------------+
4039
| |exp-projections| Projection selector | Turn on/off projections of the intensity function averaged along |

qt/python/mantidqtinterfaces/mantidqtinterfaces/dns_powder_tof/helpers/helpers_for_testing.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
# NScD Oak Ridge National Laboratory, European Spallation Source
55
# & Institut Laue - Langevin
66
# SPDX - License - Identifier: GPL - 3.0 +
7-
from mantid.simpleapi import BinMD, FakeMDEventData, CreateMDWorkspace
87

8+
import numpy as np
9+
10+
from mantid.simpleapi import BinMD, FakeMDEventData, CreateMDWorkspace
911
from mantidqtinterfaces.dns_powder_tof.data_structures.object_dict import ObjectDict
1012

1113
dataset_dic = [
@@ -229,8 +231,7 @@ def get_fake_tof_options():
229231
return tof_opt
230232

231233

232-
# OKcomment: not used anywhere
233-
def get_fake_elastic_sc_options():
234+
def get_fake_elastic_single_crystal_options():
234235
el_opt = {
235236
"a": 2,
236237
"b": 3,
@@ -294,6 +295,15 @@ def get_elastic_standard_data_dic():
294295
}
295296

296297

298+
def get_fake_elastic_single_crystal_dataset():
299+
return {
300+
"two_theta_array": [0, 1, 2],
301+
"omega_array": [4, 5],
302+
"intensity": np.transpose(np.asarray([[8.0, 9.0, 10.0], [11.0, 12.0, 13.0]])),
303+
"error": np.transpose(np.asarray([[14.0, 15.0, 16.0], [17.0, 18.0, 19.0]])),
304+
}
305+
306+
297307
def get_fake_MD_workspace_unique(name="test", factor=1):
298308
ws = CreateMDWorkspace(
299309
Dimensions="3",

qt/python/mantidqtinterfaces/mantidqtinterfaces/dns_single_crystal_elastic/data_structures/dns_single_crystal_map.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@
1313
from matplotlib import path
1414
from matplotlib import tri
1515
from mantidqtinterfaces.dns_powder_tof.data_structures.object_dict import ObjectDict
16-
from mantidqtinterfaces.dns_single_crystal_elastic.plot.elastic_single_crystal_helpers import angle_to_q
16+
from mantidqtinterfaces.dns_single_crystal_elastic.plot.elastic_single_crystal_helpers import angle_to_q, get_hkl_float_array
1717

1818

19-
def _get_mesh(omega, two_theta, z_mesh):
19+
def _get_mesh(omega, two_theta, z_mesh, z_error_mesh):
2020
omega_mesh_no_nan, two_theta_mesh_no_nan = np.meshgrid(omega, two_theta)
2121
not_nan_pos = ~np.isnan(z_mesh)
2222
omega_mesh_no_nan = omega_mesh_no_nan[not_nan_pos]
2323
two_theta_mesh_no_nan = two_theta_mesh_no_nan[not_nan_pos]
2424
z_mesh_no_nan = z_mesh[not_nan_pos]
25-
return omega_mesh_no_nan, two_theta_mesh_no_nan, z_mesh_no_nan
25+
z_error_mesh_no_nan = z_error_mesh[not_nan_pos]
26+
return omega_mesh_no_nan, two_theta_mesh_no_nan, z_mesh_no_nan, z_error_mesh_no_nan
2627

2728

2829
def _correct_omega_offset(omega, omega_offset):
@@ -55,7 +56,7 @@ def __init__(self, parameter, two_theta=None, omega=None, z_mesh=None, error_mes
5556
super().__init__()
5657
# non interpolated data:
5758
omega_corrected = _correct_omega_offset(omega, parameter["omega_offset"])
58-
omega_mesh, two_theta_mesh, z_mesh = _get_mesh(omega_corrected, two_theta, z_mesh)
59+
omega_mesh, two_theta_mesh, z_mesh, z_error_mesh = _get_mesh(omega_corrected, two_theta, z_mesh, error_mesh)
5960
omega_unique, two_theta_unique = _get_unique(omega_mesh, two_theta_mesh)
6061
qx_mesh, qy_mesh = _get_q_mesh(omega_mesh, two_theta_mesh, parameter["wavelength"])
6162
hklx_mesh, hkly_mesh = _get_hkl_mesh(qx_mesh, qy_mesh, parameter["dx"], parameter["dy"])
@@ -67,7 +68,7 @@ def __init__(self, parameter, two_theta=None, omega=None, z_mesh=None, error_mes
6768
self.hklx_mesh = hklx_mesh
6869
self.hkly_mesh = hkly_mesh
6970
self.z_mesh = z_mesh
70-
self.error_mesh = error_mesh
71+
self.error_mesh = z_error_mesh
7172
self.dx = parameter["dx"]
7273
self.dy = parameter["dy"]
7374
self.hkl1 = parameter["hkl1"]
@@ -115,3 +116,30 @@ def mask_triangles(self, mesh_name):
115116
maxi = dns_path.contains_points(x_y_triangles)
116117
self.triangulation.set_mask(np.invert(maxi))
117118
return self.triangulation
119+
120+
def get_changing_indexes(self):
121+
"""
122+
Keeps track of the coordinates after transformation to the horizontal
123+
scattering plane and returns the list of relevant indices of (x,y)
124+
crystallographic coordinates.
125+
"""
126+
hkl1 = get_hkl_float_array(self.hkl1)
127+
hkl2 = get_hkl_float_array(self.hkl2)
128+
hkl = np.add(np.outer(self.hklx_mesh.flatten()[0:10], hkl1), np.outer(self.hkly_mesh.flatten()[0:10], hkl2))
129+
projection_dims = [len(np.unique(hkl[:, 0])), len(np.unique(hkl[:, 1])), len(np.unique(hkl[:, 2]))]
130+
basis_indexes = list(range(len(projection_dims)))
131+
del basis_indexes[projection_dims.index(min(projection_dims))]
132+
return basis_indexes
133+
134+
def get_changing_hkl_components(self):
135+
index = self.get_changing_indexes()
136+
hkl1 = get_hkl_float_array(self.hkl1)
137+
hkl2 = get_hkl_float_array(self.hkl2)
138+
# transformation to crystal axis following
139+
# hkl_comp1 = a * x + b * y
140+
# hkl_comp2 = c * x + d * y
141+
a = hkl1[index[0]]
142+
b = hkl2[index[0]]
143+
c = hkl1[index[1]]
144+
d = hkl2[index[1]]
145+
return a, b, c, d

qt/python/mantidqtinterfaces/mantidqtinterfaces/dns_single_crystal_elastic/plot/elastic_single_crystal_helpers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
Helper functions for DNS single crystal elastic calculations.
1010
"""
1111

12-
from numpy import cos, pi, radians, sin
12+
from numpy import asarray, cos, pi, radians, sin
13+
14+
15+
def get_hkl_float_array(hkl_string):
16+
return asarray([float(x) for x in hkl_string.split(",")])
1317

1418

1519
def angle_to_q(two_theta, omega, wavelength): # should work with np arrays

qt/python/mantidqtinterfaces/mantidqtinterfaces/dns_single_crystal_elastic/plot/elastic_single_crystal_plot_model.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ def get_axis_labels(self, axis_type, crystal_axes, switch=False):
7272
labels = axis_labels[axis_type]
7373
return labels
7474

75+
def get_changing_hkl_components(self):
76+
return self._single_crystal_map.get_changing_hkl_components()
77+
7578
def get_omega_offset(self):
7679
return self._single_crystal_map["omega_offset"]
7780

qt/python/mantidqtinterfaces/mantidqtinterfaces/dns_single_crystal_elastic/plot/elastic_single_crystal_plot_plot.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"""
1111

1212
import matplotlib
13+
from matplotlib.ticker import AutoMinorLocator, NullLocator
1314
from mpl_toolkits.axisartist import Subplot
1415

1516

@@ -44,6 +45,16 @@ def set_shading(self, shading):
4445
if self._plot is not None:
4546
self._plot.set_shading(shading)
4647

48+
def set_grid(self, major=False, minor=False):
49+
if minor:
50+
self._ax.xaxis.set_minor_locator(AutoMinorLocator(5))
51+
else:
52+
self._ax.xaxis.set_minor_locator(NullLocator())
53+
if major:
54+
self._ax.grid(True, which="both", zorder=1000, linestyle="--")
55+
else:
56+
self._ax.grid(0)
57+
4758
def set_zlim(self, zlim):
4859
if self._plot is not None:
4960
self._plot.set_clim(zlim[0], zlim[1])

qt/python/mantidqtinterfaces/mantidqtinterfaces/dns_single_crystal_elastic/plot/elastic_single_crystal_plot_presenter.py

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from mantidqtinterfaces.dns_powder_tof.data_structures.dns_observer import DNSObserver
1313
from mantidqtinterfaces.dns_powder_tof.data_structures.object_dict import ObjectDict
1414
from mantidqtinterfaces.dns_single_crystal_elastic.plot import mpl_helpers
15+
from mantidqtinterfaces.dns_single_crystal_elastic.plot.grid_locator import get_grid_helper
1516

1617

1718
class DNSElasticSCPlotPresenter(DNSObserver):
@@ -24,6 +25,7 @@ def __init__(self, name=None, parent=None, view=None, model=None):
2425
self._plot_param.grid_state = 0
2526
self._plot_param.grid_helper = None
2627
self._plot_param.colormap_name = "jet"
28+
self._plot_param.font_size = 1
2729
self._plot_param.lines = 0
2830

2931
def _datalist_updated(self, workspaces):
@@ -39,12 +41,15 @@ def _plot(self, initial_values=None):
3941
if not plot_list:
4042
return
4143
self._plot_param.plot_name = plot_list[0]
44+
self._change_font_size(draw=False)
4245
generated_dict = self.param_dict["elastic_single_crystal_script_generator"]
4346
data_array = generated_dict["data_arrays"][self._plot_param.plot_name]
4447
options = self.param_dict["elastic_single_crystal_options"]
4548
self.model.create_single_crystal_map(data_array, options, initial_values)
49+
self._change_grid_state(draw=False, change=False)
4650
self.view.create_subfigure(self._plot_param.grid_helper)
4751
self._want_plot(axis_type["plot_type"])
52+
self._set_plotting_grid(self._crystallographical_axes())
4853
self._set_axis_labels()
4954
self.view.single_crystal_plot.create_colorbar()
5055
self.view.single_crystal_plot.on_resize()
@@ -91,11 +96,6 @@ def _plot_triangulation(self, interpolate, axis_type, switch):
9196
triangulation, z = self.model.get_interpolated_triangulation(interpolate, axis_type, switch)
9297
self.view.single_crystal_plot.plot_triangulation(triangulation, z, color_map, edge_colors, shading)
9398

94-
def _set_colormap(self):
95-
cmap = self._get_plot_styles()[0]
96-
self.view.single_crystal_plot.set_cmap(cmap)
97-
self.view.draw()
98-
9999
def _want_plot(self, plot_type):
100100
axis_type = self.view.get_axis_type()
101101
self._plot_triangulation(axis_type["interpolate"], axis_type["type"], axis_type["switch"])
@@ -105,15 +105,76 @@ def _get_plot_styles(self):
105105
shading = "flat"
106106
edge_colors = ["face", "white", "black"][self._plot_param.lines]
107107
colormap_name = own_dict["colormap"]
108+
if own_dict["invert_cb"]:
109+
colormap_name += "_r"
108110
cmap = mpl_helpers.get_cmap(colormap_name)
109111
return cmap, edge_colors, shading
110112

111113
def _set_axis_labels(self):
112114
axis_type = self.view.get_axis_type()
113-
own_dict = self.view.get_state()
114-
x_label, y_label = self.model.get_axis_labels(axis_type["type"], own_dict["crystal_axes"])
115+
x_label, y_label = self.model.get_axis_labels(axis_type["type"], self._crystallographical_axes())
115116
self.view.single_crystal_plot.set_axis_labels(x_label, y_label)
116117

118+
def _change_crystal_axes_grid(self):
119+
current_state = self._plot_param.grid_state % 4
120+
if current_state == 0:
121+
self._plot_param.grid_state = 1
122+
else:
123+
self._plot_param.grid_state = current_state
124+
self._set_plotting_grid(crystallographic_axes=True)
125+
126+
def _change_normal_grid(self):
127+
self._plot_param.grid_state = self._plot_param.grid_state % 3
128+
self._set_plotting_grid(crystallographic_axes=False)
129+
130+
def _set_plotting_grid(self, crystallographic_axes=True):
131+
if crystallographic_axes:
132+
self._plot_param.grid_helper = self._create_grid_helper()
133+
self.view.single_crystal_plot.set_grid(major=self._plot_param.grid_state, minor=self._plot_param.grid_state // 3)
134+
else:
135+
self._plot_param.grid_helper = None
136+
self.view.single_crystal_plot.set_grid(major=self._plot_param.grid_state, minor=self._plot_param.grid_state // 2)
137+
138+
def _change_grid_state(self, draw=True, change=True):
139+
if change:
140+
self._plot_param.grid_state = self._plot_param.grid_state + 1
141+
if self._crystallographical_axes():
142+
self._change_crystal_axes_grid()
143+
else:
144+
self._change_normal_grid()
145+
if draw:
146+
self.view.draw()
147+
148+
def _change_crystal_axes(self):
149+
if self._crystallographical_axes():
150+
self._plot_param.grid_state = 1
151+
else:
152+
self._plot_param.grid_state = 0
153+
self._plot()
154+
155+
def _create_grid_helper(self):
156+
axis_type = self.view.get_axis_type()
157+
a, b, c, d = self.model.get_changing_hkl_components()
158+
return get_grid_helper(self._plot_param.grid_helper, self._plot_param.grid_state, a, b, c, d, axis_type["switch"])
159+
160+
def _set_colormap(self):
161+
cmap = self._get_plot_styles()[0]
162+
self.view.single_crystal_plot.set_cmap(cmap)
163+
self.view.draw()
164+
165+
def _change_font_size(self, draw=True):
166+
own_dict = self.view.get_state()
167+
font_size = own_dict["fontsize"]
168+
if self._plot_param.font_size != font_size:
169+
self._plot_param.font_size = font_size
170+
self.view.single_crystal_plot.set_fontsize(font_size)
171+
if draw:
172+
self._plot()
173+
174+
def _crystallographical_axes(self):
175+
own_dict = self.view.get_state()
176+
return own_dict["crystal_axes"]
177+
117178
def _attach_signal_slots(self):
118179
self.view.sig_plot.connect(self._plot)
119180
self.view.sig_update_omega_offset.connect(self._update_omega_offset)
@@ -122,3 +183,6 @@ def _attach_signal_slots(self):
122183
self.view.sig_restore_default_dxdy.connect(self._set_default_dx_dy)
123184
self.view.sig_change_colormap.connect(self._set_colormap)
124185
self._plotted_script_number = 0
186+
self.view.sig_change_grid.connect(self._change_grid_state)
187+
self.view.sig_change_crystal_axes.connect(self._change_crystal_axes)
188+
self.view.sig_change_font_size.connect(self._change_font_size)

qt/python/mantidqtinterfaces/mantidqtinterfaces/dns_single_crystal_elastic/plot/elastic_single_crystal_plot_view.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,27 @@ def __init__(self, parent):
8989
sig_restore_default_dxdy = Signal()
9090
sig_update_omega_offset = Signal(float)
9191
sig_update_dxdy = Signal(float, float)
92+
sig_change_grid = Signal()
93+
sig_change_crystal_axes = Signal(bool)
9294
sig_change_colormap = Signal()
95+
sig_change_font_size = Signal()
96+
97+
# emitting custom signals for presenter
98+
def _change_font_size(self):
99+
self.sig_change_font_size.emit()
93100

94101
def _change_colormap(self):
95102
self.sig_change_colormap.emit()
96103

104+
def _change_crystal_axes(self, pressed):
105+
self.sig_change_crystal_axes.emit(pressed)
106+
97107
def _plot(self):
98108
self.sig_plot.emit()
99109

110+
def _change_grid(self):
111+
self.sig_change_grid.emit()
112+
100113
def set_plot_view_menu_visibility(self, visible):
101114
self.views_menu.menuAction().setVisible(visible)
102115

@@ -128,4 +141,8 @@ def draw(self):
128141
self.canvas.draw()
129142

130143
def _attach_signal_slots(self):
144+
self._map["fontsize"].editingFinished.connect(self._change_font_size)
145+
self._map["grid"].clicked.connect(self._change_grid)
131146
self._map["colormap"].currentIndexChanged.connect(self._change_colormap)
147+
self._map["crystal_axes"].clicked.connect(self._change_crystal_axes)
148+
self._map["invert_cb"].clicked.connect(self._change_colormap)

0 commit comments

Comments
 (0)