Newer
Older
# Mantid Repository : https://github.com/mantidproject/mantid
# Copyright © 2019 ISIS Rutherford Appleton Laboratory UKRI,
# NScD Oak Ridge National Laboratory, European Spallation Source,
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
# SPDX - License - Identifier: GPL - 3.0 +
# This file is part of the mantidqt package
from __future__ import (absolute_import, unicode_literals)
from qtpy.QtCore import Qt
from qtpy.QtGui import QIcon
from qtpy.QtWidgets import QDialogButtonBox, QMessageBox
from mantid.kernel import logger
from mantid.api import MatrixWorkspace
from mantidqt.icons import get_icon
from mantidqt.utils.qt import load_ui
# Constants
RANGE_SPECIFIER = '-'
PLACEHOLDER_FORMAT = 'valid range: {}' + RANGE_SPECIFIER + '{}'
RED_ASTERISK = None
def red_asterisk():
global RED_ASTERISK
if RED_ASTERISK is None:
RED_ASTERISK = get_icon('mdi.asterisk', 'red', 0.6)
SpectraSelectionDialogUI, SpectraSelectionDialogUIBase = load_ui(__file__, 'spectraselectordialog.ui')
class SpectraSelection(object):
def __init__(self, workspaces):
self.workspaces = workspaces
self.wksp_indices = None
self.spectra = None
self.plot_type = SpectraSelection.Individual
class SpectraSelectionDialog(SpectraSelectionDialogUIBase):
def get_compatible_workspaces(workspaces):
matrix_workspaces = []
if isinstance(ws, MatrixWorkspace):
matrix_workspaces.append(ws)
else:
# Log an error but carry on so valid workspaces can be plotted.
logger.warning("{}: Expected MatrixWorkspace, found {}".format(ws.name(), ws.__class__.__name__))
return matrix_workspaces
def __init__(self, workspaces, parent=None, show_colorfill_btn=False, overplot=False):
super(SpectraSelectionDialog, self).__init__(parent)
self.icon = self.setWindowIcon(QIcon(':/images/MantidIcon.ico'))
self.setAttribute(Qt.WA_DeleteOnClose, True)
workspaces = self.get_compatible_workspaces(workspaces)
# attributes
self._workspaces = workspaces
self.spec_min, self.spec_max = None, None
self.wi_min, self.wi_max = None, None
self.selection = None
self._show_colorfill_button = show_colorfill_btn
self._overplot = overplot
self._set_placeholder_text()
self._setup_connections()
self._on_specnums_changed()
self._on_wkspindices_changed()
def on_ok_clicked(self):
if self._check_number_of_plots(self.selection):
self.accept()
def on_plot_all_clicked(self):
selection = SpectraSelection(self._workspaces)
selection.wksp_indices = range(self.wi_min, self.wi_max + 1)
selection.plot_type = self._ui.plotType.currentIndex()
if self._check_number_of_plots(selection):
self.selection = selection
self.accept()
def on_colorfill_clicked(self):
self.selection = 'colorfill'
self.accept()
# ------------------- Private -------------------------
def _check_number_of_plots(self, selection):
index_length = len(selection.wksp_indices) if selection.wksp_indices else len(selection.spectra)
number_of_lines_to_plot = len(selection.workspaces) * index_length
if selection.plot_type == SpectraSelection.Tiled and number_of_lines_to_plot > 12:
response = QMessageBox.warning(self, 'Mantid Workbench',
'Are you sure you want to plot {} subplots?'.format(number_of_lines_to_plot),
QMessageBox.Ok | QMessageBox.Cancel)
return response == QMessageBox.Ok
elif selection.plot_type != SpectraSelection.Tiled and number_of_lines_to_plot > 10:
response = QMessageBox.warning(self, 'Mantid Workbench', 'You selected {} spectra to plot. Are you sure '
'you want to plot this many?'.format(number_of_lines_to_plot),
QMessageBox.Ok | QMessageBox.Cancel)
return response == QMessageBox.Ok
ui = SpectraSelectionDialogUI()
ui.setupUi(self)
self._ui = ui
ui.colorfillButton.setVisible(self._show_colorfill_button)
# overwrite the "Yes to All" button text
ui.buttonBox.button(QDialogButtonBox.YesToAll).setText('Plot All')
# ok disabled by default
ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
# validity markers
ui.wkspIndicesValid.setIcon(red_asterisk())
ui.specNumsValid.setIcon(red_asterisk())
def _set_placeholder_text(self):
"""Sets placeholder text to indicate the ranges possible"""
workspaces = self._workspaces
# workspace index range
wi_max = min([ws.getNumberHistograms() - 1 for ws in workspaces])
self._ui.wkspIndices.setPlaceholderText(PLACEHOLDER_FORMAT.format(0, wi_max))
self.wi_min, self.wi_max = 0, wi_max
# spectra range
ws_spectra = [{ws.getSpectrum(i).getSpectrumNo() for i in range(ws.getNumberHistograms())} for ws in workspaces]
plottable = ws_spectra[0]
if len(ws_spectra) > 1:
for sp_set in ws_spectra[1:]:
plottable = plottable.intersection(sp_set)
plottable = sorted(plottable)
if len(plottable) == 0:
raise Exception('Error: Workspaces have no common spectra.')
spec_min, spec_max = min(plottable), max(plottable)
self._ui.specNums.setPlaceholderText(PLACEHOLDER_FORMAT.format(spec_min, spec_max))
self.spec_min, self.spec_max = spec_min, spec_max
def _setup_connections(self):
ui = self._ui
ui.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.on_ok_clicked)
ui.buttonBox.button(QDialogButtonBox.Cancel).clicked.connect(self.reject)
ui.buttonBox.button(QDialogButtonBox.YesToAll).clicked.connect(self.on_plot_all_clicked)
ui.colorfillButton.clicked.connect(self.on_colorfill_clicked)
# line edits are mutually exclusive
ui.wkspIndices.textChanged.connect(self._on_wkspindices_changed)
ui.specNums.textChanged.connect(self._on_specnums_changed)
# combobox changed
ui.plotType.currentIndexChanged.connect(self._on_plot_type_changed)
def _on_wkspindices_changed(self):
ui.specNums.clear()
ui.specNumsValid.hide()
self._parse_wksp_indices()
ui.wkspIndicesValid.setVisible(not self._is_input_valid())
if self._is_input_valid() or ui.wkspIndices.text() == "":
ui.wkspIndicesValid.setVisible(False)
ui.wkspIndicesValid.setToolTip("")
else:
ui.wkspIndicesValid.setVisible(True)
ui.wkspIndicesValid.setToolTip("Not in " + ui.wkspIndices.placeholderText())
ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(self._is_input_valid())
def _on_specnums_changed(self):
ui = self._ui
ui.wkspIndices.clear()
ui.wkspIndicesValid.hide()
self._parse_spec_nums()
ui.specNumsValid.setVisible(not self._is_input_valid())
if self._is_input_valid() or ui.specNums.text() == "":
ui.specNumsValid.setVisible(False)
ui.specNumsValid.setToolTip("")
else:
ui.specNumsValid.setVisible(True)
ui.specNumsValid.setToolTip("Not in " + ui.specNums.placeholderText())
ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(self._is_input_valid())
def _on_plot_type_changed(self, new_index):
if self._overplot:
self._ui.plotType.setCurrentIndex(0)
return
if self.selection:
self.selection.plot_type = new_index
def _parse_wksp_indices(self):
wksp_indices = parse_selection_str(self._ui.wkspIndices.text(), self.wi_min, self.wi_max)
if wksp_indices:
selection = SpectraSelection(self._workspaces)
selection.wksp_indices = wksp_indices
selection.plot_type = self._ui.plotType.currentIndex()
else:
selection = None
self.selection = selection
def _parse_spec_nums(self):
spec_nums = parse_selection_str(self._ui.specNums.text(), self.spec_min, self.spec_max)
if spec_nums:
selection = SpectraSelection(self._workspaces)
selection.spectra = spec_nums
selection.plot_type = self._ui.plotType.currentIndex()
else:
selection = None
self.selection = selection
def _is_input_valid(self):
return self.selection is not None
def parse_selection_str(txt, min_val, max_val):
"""Parse an input string containing plot index selection.
:param txt: A single line of text containing a comma-separated list of values or range of values, i.e.
3-4,5,6,8,10-11
:param min_val: The minimum allowed value
:param max_val: The maximum allowed value
:returns A list containing each value in the range or None if the string is invalid
"""
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
def append_if_valid(out, val):
try:
val = int(val)
if is_in_range(val):
out.add(val)
else:
return False
except ValueError:
return False
return True
def is_in_range(val):
return min_val <= val <= max_val
# split up any commas
comma_separated = txt.split(',')
# find and expand ranges
parsed_numbers = set()
valid = True
for cs_item in comma_separated:
post_split = cs_item.split('-')
if len(post_split) == 1:
valid = append_if_valid(parsed_numbers, post_split[0])
elif len(post_split) == 2:
# parse as range
try:
beg, end = int(post_split[0]), int(post_split[1])
except ValueError:
valid = False
else:
if is_in_range(beg) and is_in_range(end):
parsed_numbers = parsed_numbers.union(set(range(beg, end + 1)))
else:
valid = False
else:
valid = False
if not valid:
break
return list(parsed_numbers) if valid > 0 else None