-
Nick Draper authoredNick Draper authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
spectraselectordialog.py 10.30 KiB
# 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
# 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)
return RED_ASTERISK
SpectraSelectionDialogUI, SpectraSelectionDialogUIBase = load_ui(__file__, 'spectraselectordialog.ui')
class SpectraSelection(object):
Individual = 0
Waterfall = 1
Tiled = 2
def __init__(self, workspaces):
self.workspaces = workspaces
self.wksp_indices = None
self.spectra = None
self.plot_type = SpectraSelection.Individual
class SpectraSelectionDialog(SpectraSelectionDialogUIBase):
@staticmethod
def get_compatible_workspaces(workspaces):
matrix_workspaces = []
for ws in 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._ui = None
self._show_colorfill_button = show_colorfill_btn
self._overplot = overplot
self._init_ui()
self._set_placeholder_text()
self._setup_connections()
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
return True
def _init_ui(self):
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
# button actions
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 = self._ui
ui.specNums.clear()
ui.specNumsValid.hide()
self._parse_wksp_indices()
ui.wkspIndicesValid.setVisible(not self._is_input_valid())
if self._is_input_valid():
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():
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())
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
"""
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