# coding: utf-8
"""
Dialog to let the user select smoothing options.
This code was taken as-is from QuickNXS v1
"""
# pylint: disable=bare-except
from mantid.simpleapi import logger
from matplotlib.lines import Line2D
from matplotlib.patches import Ellipse
from numpy import float64
from numpy.typing import NDArray
from qtpy import QtWidgets
from quicknxs.interfaces import load_ui
from quicknxs.interfaces.configuration import Configuration
from quicknxs.interfaces.data_manager import DataManager
from quicknxs.ui.mplwidget import MPLWidget
[docs]
class SmoothDialog(QtWidgets.QDialog):
"""Dialog to define smoothing parameters."""
INTENSITY_MIN = 1e-6 # starting value for the color scale
INTENSITY_MAX = 1.0 # ending value for the color scale
GRID_OFFSET = 0.05 # Starting percentage offset of the grid area inside the whole plot area
drawing = False
def __init__(self, parent, data_manager: DataManager):
QtWidgets.QDialog.__init__(self, parent)
self.ui = load_ui("ui_smooth_dialog.ui", base_instance=self)
self.data_manager = data_manager
self.ui.plot.canvas.mpl_connect("motion_notify_event", self.plotSelect)
self.ui.plot.canvas.mpl_connect("button_press_event", self.plotSelect)
self.drawPlot()
def _grid_region_coordinates(
self, x_min: float, x_max: float, y_min: float, y_max: float
) -> tuple[float, float, float, float]:
"""
Calculate the coordinates of box inside the plot area representing the grid region.
Parameters
----------
x_min:
k_diff_min, qx_min or ki_z_min
x_max:
k_diff_max, qx_max or ki_z_max
y_min:
qz_min or kf_z_min
y_max:
qz_max or kf_z_max
Returns
-------
Coordinates of the grid region box (x1, x2, y1, y2)
"""
x_offset = (x_max - x_min) * self.GRID_OFFSET
y_offset = (y_max - y_min) * self.GRID_OFFSET
return x_min + x_offset, x_max - x_offset, y_min + y_offset, y_max - y_offset
def _paint_intensities(
self,
ki_z: NDArray[float64],
kf_z: NDArray[float64],
Qx: NDArray[float64],
Qz: NDArray[float64],
I: NDArray[float64],
plot: MPLWidget,
):
"""
Color-paint the intensities versus appropriate X and Y coordinates.
X and Y coordinates are selected based on the checked option:
- (ki_z - kf_z), Qz
- Qx, Qz
- ki_z, kf_z
Parameters
----------
ki_z:
Array of k$_{i,z}$ values
kf_z:
Array of k$_{f,z}$ values
Qx:
Array of Q$_x$ values
Qz:
Array of Q$_z$ values
I:
Intensity array
plot:
The plot object to draw on
"""
common_args = {
"log": True,
"imin": self.INTENSITY_MIN,
"imax": self.INTENSITY_MAX,
"cmap": "jet",
"shading": "gouraud",
}
if self.ui.kizmkfzVSqz.isChecked():
x, y = (ki_z - kf_z), Qz
elif self.ui.qxVSqz.isChecked():
x, y = Qx, Qz
elif self.ui.kizVSkfz.isChecked():
x, y = ki_z, kf_z
else:
raise ValueError("No valid plot option selected")
plot.pcolormesh(x, y, I, **common_args)
[docs]
def drawPlot(self):
"""Plot the unsmoothed data."""
self.drawing = True
# initialize the plot widget
plot: MPLWidget = self.ui.plot
plot.clear()
plot.set_xticks_fontsize(8)
plot.set_yticks_fontsize(8)
#
# Block to paint the intensities on the canvas
#
Qzmax = 0.001
k_diff_min = 0.01
k_diff_max = -0.01
qz_min = 0.5
qz_max = -0.1
qx_min = -0.001
qx_max = 0.001
ki_z_min = 0.1
ki_z_max = -0.1
kf_z_min = 0.1
kf_z_max = -0.1
first_state = self.data_manager.reduction_states[0] # Get data from first cross-section
for item in self.data_manager.reduction_list: # type: NexusData
offspec = item.cross_sections[first_state].off_spec
Qx, Qz, ki_z, kf_z, I, _ = (offspec.Qx, offspec.Qz, offspec.ki_z, offspec.kf_z, offspec.S, offspec.dS)
n_total = len(I[0])
# P_0 and P_N are the number of points to cut in TOF on each side
p_0 = item.cross_sections[first_state].configuration.cut_first_n_points
p_n = n_total - item.cross_sections[first_state].configuration.cut_last_n_points
Qx = Qx[:, p_0:p_n]
Qz = Qz[:, p_0:p_n]
ki_z = ki_z[:, p_0:p_n]
kf_z = kf_z[:, p_0:p_n]
I = I[:, p_0:p_n]
# Extend the X and Y limits of the plotting area so that intensities for all runs are visible
try:
Qzmax = max(ki_z.max() * 2.0, Qzmax)
qz_max = max(Qz[I > 0].max(), qz_max)
qz_min = min(Qz[I > 0].min(), qz_min)
qx_min = min(qx_min, Qx[I > 0].min())
qx_max = max(qx_max, Qx[I > 0].max())
ki_z_min = min(ki_z_min, ki_z[I > 0].min())
ki_z_max = max(ki_z_max, ki_z[I > 0].max())
kf_z_min = min(kf_z_min, kf_z[I > 0].min())
kf_z_max = max(kf_z_max, kf_z[I > 0].max())
k_diff_min = min(k_diff_min, (ki_z - kf_z)[I > 0].min())
k_diff_max = max(k_diff_max, (ki_z - kf_z)[I > 0].max())
except Exception as exception:
logger.error(f"Error extending plotting limits: {exception}")
self._paint_intensities(ki_z, kf_z, Qx, Qz, I, plot) # color-paint intensities on the canvas
#
# Block to set the plot's X and Y limits, the X and Y labels, and the grid region
#
sigma_percentage = 0.005 # percentage of the sigma spot from the whole plot area
min_sigma_size = 0.0001 # default-minimum sigma x,y size
if self.ui.kizmkfzVSqz.isChecked():
plot.canvas.ax.set_xlim([k_diff_min, k_diff_max])
plot.canvas.ax.set_ylim([qz_min, qz_max])
plot.set_xlabel("k$_{i,z}$-k$_{f,z}$ [Å$^{-1}$]", fontsize=14)
plot.set_ylabel("Q$_z$ [Å$^{-1}$]", fontsize=14)
x1, x2, y1, y2 = self._grid_region_coordinates(k_diff_min, k_diff_max, qz_min, qz_max)
elif self.ui.qxVSqz.isChecked():
plot.canvas.ax.set_xlim([qx_min, qx_max])
plot.canvas.ax.set_ylim([qz_min, qz_max])
plot.set_xlabel("Q$_x$ [Å$^{-1}$]", fontsize=14)
plot.set_ylabel("Q$_z$ [Å$^{-1}$]", fontsize=14)
x1, x2, y1, y2 = self._grid_region_coordinates(qx_min, qx_max, qz_min, qz_max)
elif self.ui.kizVSkfz.isChecked():
plot.canvas.ax.set_xlim([ki_z_min, ki_z_max])
plot.canvas.ax.set_ylim([kf_z_min, kf_z_max])
plot.set_xlabel("k$_{i,z}$ [Å$^{-1}$]", fontsize=14)
plot.set_ylabel("k$_{f,z}$ [Å$^{-1}$]", fontsize=14)
x1, x2, y1, y2 = self._grid_region_coordinates(ki_z_min, ki_z_max, kf_z_min, kf_z_max)
else:
raise ValueError("No valid plot option selected")
self.rect_region = Line2D([x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1])
plot.canvas.ax.add_line(self.rect_region)
self.ui.gridXmin.setValue(x1)
self.ui.gridXmax.setValue(x2)
self.ui.gridYmin.setValue(y1)
self.ui.gridYmax.setValue(y2)
#
# Block to set the sigma spot and determine sigma properties
#
sigma_x = max((x2 - x1) * sigma_percentage, min_sigma_size)
sigma_y = max((y2 - y1) * sigma_percentage, min_sigma_size)
self.ui.sigmaX.setValue(sigma_x)
self.ui.sigmaY.setValue(sigma_y)
self.ui.sigmasCoupled.setChecked(True)
self.ui.sigmaY.setEnabled(True)
sigma_ang = 0.0
if self.ui.kizmkfzVSqz.isChecked():
sigma_pos = (0.0, Qzmax / 3.0)
self.ui.sigmaY.setEnabled(False)
elif self.ui.qxVSqz.isChecked():
sigma_pos = (0.0, Qzmax / 3.0)
self.ui.sigmasCoupled.setChecked(False)
else:
sigma_pos = (Qzmax / 6.0, Qzmax / 6.0)
self.sigma_1 = Ellipse(
sigma_pos, self.ui.sigmaX.value() * 2, self.ui.sigmaY.value() * 2, angle=sigma_ang, fill=False
)
self.sigma_2 = Ellipse(
sigma_pos, self.ui.sigmaX.value() * 4, self.ui.sigmaY.value() * 4, angle=sigma_ang, fill=False
)
self.sigma_3 = Ellipse(
sigma_pos, self.ui.sigmaX.value() * 6, self.ui.sigmaY.value() * 6, angle=sigma_ang, fill=False
)
plot.canvas.ax.add_artist(self.sigma_1)
plot.canvas.ax.add_artist(self.sigma_2)
plot.canvas.ax.add_artist(self.sigma_3)
# Show the plot
if plot.cplot is not None:
plot.cplot.set_clim([self.INTENSITY_MIN, self.INTENSITY_MAX])
plot.draw()
self.updateGrid()
self.drawing = False
[docs]
def updateSettings(self):
if self.drawing:
return
self.drawing = True
if self.ui.sigmasCoupled.isChecked():
self.ui.sigmaY.setValue(self.ui.sigmaX.value())
self.updateGrid()
# redraw indicators
x1 = self.ui.gridXmin.value()
x2 = self.ui.gridXmax.value()
y1 = self.ui.gridYmin.value()
y2 = self.ui.gridYmax.value()
self.rect_region.set_data([x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1])
self.sigma_1.width = 2 * self.ui.sigmaX.value()
self.sigma_1.height = 2 * self.ui.sigmaY.value()
self.sigma_2.width = 4 * self.ui.sigmaX.value()
self.sigma_2.height = 4 * self.ui.sigmaY.value()
self.sigma_3.width = 6 * self.ui.sigmaX.value()
self.sigma_3.height = 6 * self.ui.sigmaY.value()
self.ui.plot.draw()
self.drawing = False
[docs]
def updateGrid(self):
if self.ui.gridSizeCoupled.isChecked():
sx = self.ui.sigmaX.value()
sy = self.ui.sigmaY.value()
x1 = self.ui.gridXmin.value()
x2 = self.ui.gridXmax.value()
y1 = self.ui.gridYmin.value()
y2 = self.ui.gridYmax.value()
self.ui.gridSizeX.setValue(int((x2 - x1) / sx * 1.41))
self.ui.gridSizeY.setValue(int((y2 - y1) / sy * 1.41))
[docs]
def plotSelect(self, event):
"""Plot for y-projection has been clicked."""
# if event.button == 1 and self.ui.plot.toolbar._active is None and event.xdata is not None:
if event.button == 1 and event.xdata is not None:
x = event.xdata
y = event.ydata
x1 = self.ui.gridXmin.value()
x2 = self.ui.gridXmax.value()
y1 = self.ui.gridYmin.value()
y2 = self.ui.gridYmax.value()
if x < x1 or abs(x - x1) < abs(x - x2):
x1 = x
else:
x2 = x
if y < y1 or abs(y - y1) < abs(y - y2):
y1 = y
else:
y2 = y
self.drawing = True
self.ui.gridXmin.setValue(x1)
self.ui.gridXmax.setValue(x2)
self.ui.gridYmin.setValue(y1)
self.ui.gridYmax.setValue(y2)
self.drawing = False
self.updateSettings()
[docs]
def update_output_options(self, output_options: dict) -> dict:
"""Update a dict with smoothing options."""
if self.ui.kizVSkfz.isChecked():
output_options["off_spec_x_axis"] = Configuration.KZI_VS_KZF
elif self.ui.qxVSqz.isChecked():
output_options["off_spec_x_axis"] = Configuration.QX_VS_QZ
else:
output_options["off_spec_x_axis"] = Configuration.DELTA_KZ_VS_QZ
output_options["off_spec_nxbins"] = self.ui.gridSizeX.value()
output_options["off_spec_nybins"] = self.ui.gridSizeY.value()
output_options["off_spec_sigmas"] = self.ui.rSigmas.value()
output_options["off_spec_sigmax"] = self.ui.sigmaX.value()
output_options["off_spec_sigmay"] = self.ui.sigmaY.value()
output_options["off_spec_x_min"] = self.ui.gridXmin.value()
output_options["off_spec_x_max"] = self.ui.gridXmax.value()
output_options["off_spec_y_min"] = self.ui.gridYmin.value()
output_options["off_spec_y_max"] = self.ui.gridYmax.value()
return output_options