Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1728: Finishing the Q-Range Sliders #1891

Merged
merged 28 commits into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5699795
Enable invariant q-range sliders, change trigger to update when finis…
krzywon Feb 5, 2021
58870c7
Add q-range sliders to linear fit plots.
krzywon Feb 10, 2021
1758d42
Force redraw of q-range sliders when updating input value linked to s…
krzywon Feb 15, 2021
e64ce6d
Start the removal of q-range sliders from plotter data - create clean…
krzywon Feb 23, 2021
c03d1dc
Merge release_5.0.4 into slider-final-work
krzywon Mar 26, 2021
558019c
Marge main into slicer_final_work
krzywon Jul 8, 2021
f73e683
Start of the work to remove q range sliders from plotter data
krzywon Jul 8, 2021
850a642
Replace direct calls to methods and inputs with name strings of those…
krzywon Jul 29, 2021
c4d1fcd
Apply QRangeSlider strings to fitting perspective.
krzywon Jul 29, 2021
878b44c
Apply QRangeSlider strings to p(r) inversion perspective.
krzywon Jul 29, 2021
4f279c7
Apply QRangeSlider strings to invariant perspective perspective.
krzywon Jul 29, 2021
7f6d1f0
Remove unused code related to q range sliders
krzywon Jul 30, 2021
8ffdba4
Add context menu option to toggle Q-range sliders on/off
krzywon Jul 30, 2021
355dbb8
Properly link number of invariant points for high and low Q to the q-…
krzywon Aug 2, 2021
c6358e7
Better linking of linear fits to q-range slider, but they appear and …
krzywon Aug 2, 2021
8d9ab68
Retain slider visibility between plot updates
krzywon Aug 4, 2021
c1faf64
Update plot when closing linear fit window so slider bars are no long…
krzywon Aug 4, 2021
621531d
Unit tests for the QRangeSlider class, with the default values and Li…
krzywon Aug 5, 2021
f1d687c
Add global to expose loaded perspectives to other parts of sasview. U…
krzywon Aug 6, 2021
213bfa7
Refresh plot after hiding q slider so sliders don't persist
krzywon Aug 6, 2021
11c24ec
Write QRangeSlider unit tests for invariant and inversion tests.
krzywon Aug 6, 2021
2789a72
Get QRangeSlider Invariant and Inversion unit tests working.
krzywon Aug 9, 2021
bc362e7
Fix unit tests for q-range sliders related to fitting perspective
krzywon Aug 9, 2021
3a9a97c
Ensure data is plotted for linear fit tests
krzywon Aug 9, 2021
184a65b
Document QRangeSlide
krzywon Aug 10, 2021
cace230
Fix issues with LinearFit.py q-range sliders
krzywon Sep 2, 2021
a02c42d
Merge branch 'main' into slider-final-work
krzywon Oct 5, 2021
a5bfaa6
Suppress plot redraws while q-range sliders are moving to fix sluggis…
krzywon Oct 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/sas/qtgui/GUITests.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
from Plotting.UnitTesting import PlotterBaseTest
from Plotting.UnitTesting import PlotterTest
from Plotting.UnitTesting import Plotter2DTest
from Plotting.UnitTesting import QRangeSliderTests

# Calculators
from Calculators.UnitTesting import KiessigCalculatorTest
Expand Down Expand Up @@ -121,6 +122,7 @@ def plottingSuite():
unittest.makeSuite(SlicerParametersTest.SlicerParametersTest, 'test'),
unittest.makeSuite(PlotterBaseTest.PlotterBaseTest, 'test'),
unittest.makeSuite(PlotterTest.PlotterTest, 'test'),
unittest.makeSuite(QRangeSliderTests.QRangeSlidersTest, 'test'),
)
return unittest.TestSuite(suites)

Expand Down
24 changes: 0 additions & 24 deletions src/sas/qtgui/MainWindow/DataExplorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,36 +398,12 @@ def allDataForModel(self, model):
if data is None: continue
# Now, all plots under this item
name = data.name
######################################################
# Reset all slider values in data so save/load does not choke on them
# Remove once slider definition moved out of PlotterData
data.slider_low_q_setter = None
data.slider_high_q_setter = None
data.slider_low_q_input = None
data.slider_high_q_input = None
data.slider_update_on_move = False
data.slider_low_q_getter = None
data.slider_high_q_getter = None
######################################################
is_checked = item.checkState()
properties['checked'] = is_checked
other_datas = []
# save underlying theories
other_datas = GuiUtils.plotsFromDisplayName(name, model)
# skip the main plot
other_datas = list(other_datas.values())[1:]
for datas in other_datas:
######################################################
# Reset all slider values in data so save/load does not choke on them
# Remove once slider definition moved out of PlotterData
datas.slider_low_q_setter = None
datas.slider_high_q_setter = None
datas.slider_low_q_input = None
datas.slider_high_q_input = None
datas.slider_update_on_move = False
datas.slider_low_q_getter = None
datas.slider_high_q_getter = None
######################################################
all_data[data.id] = [data, properties, other_datas]
return all_data

Expand Down
7 changes: 7 additions & 0 deletions src/sas/qtgui/MainWindow/GuiManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@

logger = logging.getLogger(__name__)

# Expose the loaded perspectives for easy linking between perspective methods/inputs and plots
LOADED_PERSPECTIVES = {}


class GuiManager(object):
"""
Expand Down Expand Up @@ -192,6 +195,8 @@ def loadAllPerspectives(self):
except Exception as e:
logger.warning(f"Unable to load {name} perspective.\n{e}")
self.loadedPerspectives = loaded_dict
global LOADED_PERSPECTIVES
LOADED_PERSPECTIVES = self.loadedPerspectives

def closeAllPerspectives(self):
# Close all perspectives if they are open
Expand All @@ -205,6 +210,8 @@ def closeAllPerspectives(self):
except Exception as e:
logger.warning(f"Unable to close {name} perspective\n{e}")
self.loadedPerspectives = {}
global LOADED_PERSPECTIVES
LOADED_PERSPECTIVES = self.loadedPerspectives
self._current_perspective = None

def addCategories(self):
Expand Down
10 changes: 6 additions & 4 deletions src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -2964,10 +2964,12 @@ def complete1D(self, return_data):
fitted_data.show_q_range_sliders = True
# Suppress the GUI update until the move is finished to limit model calculations
fitted_data.slider_update_on_move = False
fitted_data.slider_high_q_input = self.options_widget.txtMaxRange
fitted_data.slider_high_q_setter = self.options_widget.updateMaxQ
fitted_data.slider_low_q_input = self.options_widget.txtMinRange
fitted_data.slider_low_q_setter = self.options_widget.updateMinQ
fitted_data.slider_tab_name = self.modelName()
fitted_data.slider_perspective_name = 'Fitting'
fitted_data.slider_high_q_input = ['options_widget', 'txtMaxRange']
fitted_data.slider_high_q_setter = ['options_widget', 'updateMaxQ']
fitted_data.slider_low_q_input = ['options_widget', 'txtMinRange']
fitted_data.slider_low_q_setter = ['options_widget', 'updateMinQ']

self.model_data = fitted_data
new_plots = [fitted_data]
Expand Down
50 changes: 33 additions & 17 deletions src/sas/qtgui/Perspectives/Invariant/InvariantPerspective.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,17 @@ def set_low_q_extrapolation_upper_limit(self, value):
n_pts = (np.abs(self._data.x - value)).argmin() + 1
item = QtGui.QStandardItem(str(n_pts))
self.model.setItem(WIDGETS.W_NPTS_LOWQ, item)
self.txtNptsLowQ.setText(str(n_pts))

def get_high_q_extrapolation_lower_limit(self):
q_value = self._data.x[len(self._data.x) - int(self.txtNptsHighQ.text()) - 1]
return q_value

def set_high_q_extrapolation_lower_limit(self, value):
n_pts = (np.abs(self._data.x - value)).argmin() + 1
n_pts = len(self._data.x) - (np.abs(self._data.x - value)).argmin() + 1
item = QtGui.QStandardItem(str(int(n_pts)))
self.model.setItem(WIDGETS.W_NPTS_HIGHQ, item)
self.txtNptsHighQ.setText(str(n_pts))

def enabling(self):
""" """
Expand Down Expand Up @@ -260,27 +262,27 @@ def plotResult(self, model):
self.high_extrapolation_plot.plot_role = Data1D.ROLE_DEFAULT
self.high_extrapolation_plot.symbol = "Line"
self.high_extrapolation_plot.show_errors = False
# TODO: Fix the link between npts and q and then enable the q-range sliders
#self.high_extrapolation_plot.show_q_range_sliders = True
#self.high_extrapolation_plot.slider_update_on_move = False
#self.high_extrapolation_plot.slider_low_q_input = self.txtNptsHighQ
#self.high_extrapolation_plot.slider_low_q_setter = self.set_high_q_extrapolation_lower_limit
#self.high_extrapolation_plot.slider_low_q_getter = self.get_high_q_extrapolation_lower_limit
#self.high_extrapolation_plot.slider_high_q_input = self.txtExtrapolQMax
self.high_extrapolation_plot.show_q_range_sliders = True
self.high_extrapolation_plot.slider_update_on_move = False
self.high_extrapolation_plot.slider_perspective_name = self.name
self.high_extrapolation_plot.slider_low_q_input = ['txtNptsHighQ']
self.high_extrapolation_plot.slider_low_q_setter = ['set_high_q_extrapolation_lower_limit']
self.high_extrapolation_plot.slider_low_q_getter = ['get_high_q_extrapolation_lower_limit']
self.high_extrapolation_plot.slider_high_q_input = ['txtExtrapolQMax']
GuiUtils.updateModelItemWithPlot(self._model_item, self.high_extrapolation_plot,
self.high_extrapolation_plot.title)
plots.append(self.high_extrapolation_plot)
if self.low_extrapolation_plot:
self.low_extrapolation_plot.plot_role = Data1D.ROLE_DEFAULT
self.low_extrapolation_plot.symbol = "Line"
self.low_extrapolation_plot.show_errors = False
# TODO: Fix the link between npts and q and then enable the q-range sliders
#self.low_extrapolation_plot.show_q_range_sliders = True
#self.low_extrapolation_plot.slider_update_on_move = False
#self.low_extrapolation_plot.slider_high_q_input = self.txtNptsLowQ
#self.low_extrapolation_plot.slider_high_q_setter = self.set_low_q_extrapolation_upper_limit
#self.low_extrapolation_plot.slider_high_q_getter = self.get_low_q_extrapolation_upper_limit
#self.low_extrapolation_plot.slider_low_q_input = self.txtExtrapolQMin
self.low_extrapolation_plot.show_q_range_sliders = True
self.low_extrapolation_plot.slider_update_on_move = False
self.low_extrapolation_plot.slider_perspective_name = self.name
self.low_extrapolation_plot.slider_high_q_input = ['txtNptsLowQ']
self.low_extrapolation_plot.slider_high_q_setter = ['set_low_q_extrapolation_upper_limit']
self.low_extrapolation_plot.slider_high_q_getter = ['get_low_q_extrapolation_upper_limit']
self.low_extrapolation_plot.slider_low_q_input = ['txtExtrapolQMin']
GuiUtils.updateModelItemWithPlot(self._model_item, self.low_extrapolation_plot,
self.low_extrapolation_plot.title)
plots.append(self.low_extrapolation_plot)
Expand Down Expand Up @@ -571,12 +573,16 @@ def setupSlots(self):

self.txtNptsHighQ.textChanged.connect(self.checkLength)

self.txtExtrapolQMin.editingFinished.connect(self.checkQMinRange)
self.txtExtrapolQMin.textChanged.connect(self.checkQMinRange)

self.txtExtrapolQMax.editingFinished.connect(self.checkQMaxRange)
self.txtExtrapolQMax.textChanged.connect(self.checkQMaxRange)

self.txtNptsLowQ.editingFinished.connect(self.checkQRange)
self.txtNptsLowQ.textChanged.connect(self.checkQRange)

self.txtNptsHighQ.editingFinished.connect(self.checkQRange)
self.txtNptsHighQ.textChanged.connect(self.checkQRange)

def stateChanged(self):
Expand Down Expand Up @@ -639,12 +645,22 @@ def checkQExtrapolatedData(self):
self._path, name,
self.sender().checkState())

def checkQMaxRange(self):
def checkQMaxRange(self, value=None):
if not value:
value = float(self.txtExtrapolQMax.text()) if self.txtExtrapolQMax.text() else ''
if value == '':
self.model.setItem(WIDGETS.W_EX_QMAX, QtGui.QStandardItem(value))
return
item = QtGui.QStandardItem(self.txtExtrapolQMax.text())
self.model.setItem(WIDGETS.W_EX_QMAX, item)
self.checkQRange()

def checkQMinRange(self):
def checkQMinRange(self, value=None):
if not value:
value = float(self.txtExtrapolQMin.text()) if self.txtExtrapolQMin.text() else ''
if value == '':
self.model.setItem(WIDGETS.W_EX_QMIN, QtGui.QStandardItem(value))
return
item = QtGui.QStandardItem(self.txtExtrapolQMin.text())
self.model.setItem(WIDGETS.W_EX_QMIN, item)
self.checkQRange()
Expand Down
18 changes: 10 additions & 8 deletions src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,10 +670,10 @@ def removeData(self, data_list=None):
self._dataList.pop(data, None)
if self.dataPlot:
# Reset dataplot sliders
self.dataPlot.slider_low_q_input = None
self.dataPlot.slider_high_q_input = None
self.dataPlot.slider_low_q_setter = None
self.dataPlot.slider_high_q_setter = None
self.dataPlot.slider_low_q_input = []
self.dataPlot.slider_high_q_input = []
self.dataPlot.slider_low_q_setter = []
self.dataPlot.slider_high_q_setter = []
self._data = None
length = len(self.dataList)
for index in reversed(range(length)):
Expand Down Expand Up @@ -1047,10 +1047,12 @@ def _calculateUpdate(self, output_tuple):
self.dataPlot.filename = self.logic.data.filename

self.dataPlot.show_q_range_sliders = True
self.dataPlot.slider_low_q_input = self.minQInput
self.dataPlot.slider_low_q_setter = self.check_q_low
self.dataPlot.slider_high_q_input = self.maxQInput
self.dataPlot.slider_high_q_setter = self.check_q_high
self.dataPlot.slider_update_on_move = False
self.dataPlot.slider_perspective_name = "Inversion"
self.dataPlot.slider_low_q_input = ['minQInput']
self.dataPlot.slider_low_q_setter = ['check_q_low']
self.dataPlot.slider_high_q_input = ['maxQInput']
self.dataPlot.slider_high_q_setter = ['check_q_high']

# Udpate internals and GUI
self.updateDataList(self._data)
Expand Down
38 changes: 36 additions & 2 deletions src/sas/qtgui/Plotting/LinearFit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"""
import re
import numpy
from numbers import Number
from typing import Optional
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5 import QtWidgets
Expand All @@ -12,12 +14,14 @@
from sas.qtgui.Plotting import Fittings
from sas.qtgui.Plotting import DataTransform
from sas.qtgui.Plotting.LineModel import LineModel
from sas.qtgui.Plotting.QRangeSlider import QRangeSlider
import sas.qtgui.Utilities.GuiUtils as GuiUtils

# Local UI
from sas.qtgui.UI import main_resources_rc
from sas.qtgui.Plotting.UI.LinearFitUI import Ui_LinearFitUI


class LinearFit(QtWidgets.QDialog, Ui_LinearFitUI):
updatePlot = QtCore.pyqtSignal(tuple)
def __init__(self, parent=None,
Expand All @@ -26,7 +30,7 @@ def __init__(self, parent=None,
fit_range=(0.0, 0.0),
xlabel="",
ylabel=""):
super(LinearFit, self).__init__()
super(LinearFit, self).__init__(parent)

self.setupUi(self)
# disable the context help icon
Expand Down Expand Up @@ -100,6 +104,9 @@ def __init__(self, parent=None,
self.cstB = Fittings.Parameter(self.model, 'B', self.default_B)
self.transform = DataTransform

self.q_sliders = None
self.drawSliders()

self.setFixedSize(self.minimumSizeHint())

# connect Fit button
Expand Down Expand Up @@ -239,7 +246,9 @@ def fit(self, event):
tempx = numpy.array(tempx)
tempy = numpy.array(tempy)

self.clearSliders()
self.updatePlot.emit((tempx, tempy))
self.drawSliders()

def origData(self):
# Store the transformed values of view x, y and dy before the fit
Expand Down Expand Up @@ -314,4 +323,29 @@ def floatInvTransform(self, x):
return numpy.sqrt(numpy.sqrt(numpy.power(10.0, x)))
return x


def drawSliders(self):
"""Show new Q-range sliders"""
self.data.show_q_range_sliders = True
self.q_sliders = QRangeSlider(self.parent, self.parent.ax, data=self.data)
self.q_sliders.line_min.input = self.txtFitRangeMin
self.q_sliders.line_max.input = self.txtFitRangeMax
# Ensure values are updated on redraw of plots
self.q_sliders.line_min.inputChanged()
self.q_sliders.line_max.inputChanged()

def clearSliders(self):
"""Clear existing sliders"""
if self.q_sliders:
self.q_sliders.clear()
self.data.show_q_range_sliders = False
self.q_sliders = None

def closeEvent(self, ev):
self.clearSliders()
self.parent.update()

def accept(self, ev):
self.close()

def reject(self, ev):
self.close()
16 changes: 15 additions & 1 deletion src/sas/qtgui/Plotting/Plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,12 @@ def plot(self, data=None, color=None, marker=None, hide_error=False, transform=T

# Add q-range sliders
if data.show_q_range_sliders:
# Grab existing slider if it exists
existing_slider = self.sliders.pop(data.name, None)
sliders = QRangeSlider(self, self.ax, data=data)
# New sliders should be visible but existing sliders that were turned off should remain off
if existing_slider is not None and not existing_slider.is_visible:
sliders.toggle()
self.sliders[data.name] = sliders

# refresh canvas
Expand Down Expand Up @@ -338,6 +343,11 @@ def addPlotsToContextMenu(self):
self.actionRemovePlot.triggered.connect(
functools.partial(self.onRemovePlot, id))

if plot.show_q_range_sliders:
self.actionToggleSlider = plot_menu.addAction("Toggle Q-Range Slider Visibility")
self.actionToggleSlider.triggered.connect(
functools.partial(self.toggleSlider, id))

if not plot.is_data:
self.actionFreeze = plot_menu.addAction('&Freeze')
self.actionFreeze.triggered.connect(
Expand Down Expand Up @@ -540,7 +550,6 @@ def removePlot(self, id):

# Remove the plot from the list of plots
self.plot_dict.pop(id)
self.sliders.pop(id, None)

# Labels might have been changed
xl = self.ax.xaxis.label.get_text()
Expand All @@ -561,6 +570,11 @@ def removePlot(self, id):
self.ax.set_ylabel(yl)
self.canvas.draw_idle()

def toggleSlider(self, id):
if id in self.sliders.keys():
slider = self.sliders.get(id)
slider.toggle()

def onFreeze(self, id):
"""
Freezes the selected plot to a separate chart
Expand Down
14 changes: 8 additions & 6 deletions src/sas/qtgui/Plotting/PlotterData.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,18 @@ def __init__(self, x=None, y=None, dx=None, dy=None):
# Q-range slider definitions
self.show_q_range_sliders = False # Should sliders be shown?
self.slider_update_on_move = True # Should the gui update during the move?
self.slider_perspective_name = "" # Name of the perspective that this slider is associated with
self.slider_tab_name = "" # Name of the tab where the data set is
# The following q-range slider variables are optional but help tie
# the slider to a GUI element for 2-way updates
self.slider_low_q_input = None # Qt input that is tied to low-Q
self.slider_high_q_input = None # Qt input that is tied to high-Q
self.slider_low_q_input = [] # List of attributes that lead to a Qt input to tie a low Q input to the slider
self.slider_high_q_input = [] # List of attributes that lead to a Qt input to tie a high Q input to the slider
# Setters and getters are only needed for inputs that aren't Q values
# e.g. Invariant perspective nPts
self.slider_low_q_setter = None # Callback method to set the low-Q value
self.slider_low_q_getter = None # Callback method to get the low-Q value
self.slider_high_q_setter = None # Callback method to set the high-Q value
self.slider_high_q_getter = None # Callback method to get the high-Q value
self.slider_low_q_setter = [] # List of attributes that lead to a setter to tie a low Q method to the slider
self.slider_low_q_getter = [] # List of attributes that lead to a getter to tie a low Q method to the slider
self.slider_high_q_setter = [] # List of attributes that lead to a setter to tie a high Q method to the slider
self.slider_high_q_getter = [] # List of attributes that lead to a getter to tie a high Q method to the slider

def copy_from_datainfo(self, data1d):
"""
Expand Down
Loading