diff --git a/src/sas/logger_config.py b/src/sas/logger_config.py
index 8a182c2b43..03eb004dc9 100644
--- a/src/sas/logger_config.py
+++ b/src/sas/logger_config.py
@@ -31,6 +31,7 @@ def config_production(self):
logging.captureWarnings(True)
logger = logging.getLogger(self.name)
logging.getLogger('matplotlib').setLevel(logging.WARN)
+ logging.getLogger('numba').setLevel(logging.WARN)
return logger
def config_development(self):
@@ -42,6 +43,7 @@ def config_development(self):
logging.captureWarnings(True)
self._disable_debug_from_config()
logging.getLogger('matplotlib').setLevel(logging.WARN)
+ logging.getLogger('numba').setLevel(logging.WARN)
return logger
def _disable_debug_from_config(self):
diff --git a/src/sas/qtgui/Calculators/DataOperationUtilityPanel.py b/src/sas/qtgui/Calculators/DataOperationUtilityPanel.py
index e866dd0fb6..d7938718a9 100644
--- a/src/sas/qtgui/Calculators/DataOperationUtilityPanel.py
+++ b/src/sas/qtgui/Calculators/DataOperationUtilityPanel.py
@@ -160,6 +160,7 @@ def onPrepareOutputData(self):
""" Prepare datasets to be added to DataExplorer and DataManager """
name = self.txtOutputData.text()
self.output.name = name
+ self.output.id = name + str(time.time())
new_item = GuiUtils.createModelItemWithPlot(
self.output,
name=name)
@@ -410,7 +411,6 @@ def updatePlot(self, graph, layout, data):
# plot 2D data
plotter2D = Plotter2DWidget(self, quickplot=True)
plotter2D.scale = 'linear'
-
plotter2D.ax.tick_params(axis='x', labelsize=8)
plotter2D.ax.tick_params(axis='y', labelsize=8)
diff --git a/src/sas/qtgui/Calculators/UI/SldPanel.ui b/src/sas/qtgui/Calculators/UI/SldPanel.ui
index e847c6af92..8686ad55ad 100644
--- a/src/sas/qtgui/Calculators/UI/SldPanel.ui
+++ b/src/sas/qtgui/Calculators/UI/SldPanel.ui
@@ -6,8 +6,8 @@
0
0
- 490
- 446
+ 552
+ 495
@@ -308,10 +308,16 @@
-
+
+
+ 0
+ 0
+
+
466
- 32
+ 50
@@ -320,6 +326,12 @@
true
+
+
+ 0
+ 28
+
+
Recalculate
@@ -340,6 +352,12 @@
-
+
+
+ 0
+ 28
+
+
Close
@@ -347,6 +365,12 @@
-
+
+
+ 0
+ 28
+
+
Help
diff --git a/src/sas/qtgui/MainWindow/DataExplorer.py b/src/sas/qtgui/MainWindow/DataExplorer.py
index 632c55d941..e4cff872cf 100644
--- a/src/sas/qtgui/MainWindow/DataExplorer.py
+++ b/src/sas/qtgui/MainWindow/DataExplorer.py
@@ -118,6 +118,9 @@ def __init__(self, parent=None, guimanager=None, manager=None):
self.cbgraph.editTextChanged.connect(self.enableGraphCombo)
self.cbgraph.currentIndexChanged.connect(self.enableGraphCombo)
+ self.cbgraph_2.editTextChanged.connect(self.enableGraphCombo)
+ self.cbgraph_2.currentIndexChanged.connect(self.enableGraphCombo)
+
# Proxy model for showing a subset of Data1D/Data2D content
self.data_proxy = QtCore.QSortFilterProxyModel(self)
self.data_proxy.setSourceModel(self.model)
@@ -696,6 +699,10 @@ def deleteFile(self, event):
# Delete corresponding open plots
self.closePlotsForItem(item)
+ # Close result panel if results represent the deleted data item
+ # Results panel only stores Data1D/Data2D object
+ # => QStandardItems must still exist for direct comparison
+ self.closeResultPanelOnDelete(GuiUtils.dataFromItem(item))
self.model.removeRow(ind)
# Decrement index since we just deleted it
@@ -940,9 +947,12 @@ def updatePlotName(self, name_tuple):
Modify the name of the current plot
"""
old_name, current_name = name_tuple
- ind = self.cbgraph.findText(old_name)
- self.cbgraph.setCurrentIndex(ind)
- self.cbgraph.setItemText(ind, current_name)
+ graph = self.cbgraph
+ if self.current_view == self.freezeView:
+ graph = self.cbgraph_2
+ ind = graph.findText(old_name)
+ graph.setCurrentIndex(ind)
+ graph.setItemText(ind, current_name)
def add_data(self, data_list):
"""
@@ -972,12 +982,15 @@ def updateGraphCombo(self, graph_list):
"""
Modify Graph combo box on graph add/delete
"""
- orig_text = self.cbgraph.currentText()
- self.cbgraph.clear()
- self.cbgraph.insertItems(0, graph_list)
- ind = self.cbgraph.findText(orig_text)
+ graph = self.cbgraph
+ if self.current_view == self.freezeView:
+ graph = self.cbgraph_2
+ orig_text = graph.currentText()
+ graph.clear()
+ graph.insertItems(0, graph_list)
+ ind = graph.findText(orig_text)
if ind > 0:
- self.cbgraph.setCurrentIndex(ind)
+ graph.setCurrentIndex(ind)
def updatePerspectiveCombo(self, index):
"""
@@ -1207,11 +1220,13 @@ def appendPlot(self):
# new plot data; check which tab is currently active
if self.current_view == self.treeView:
new_plots = GuiUtils.plotsFromCheckedItems(self.model)
+ graph = self.cbgraph
else:
new_plots = GuiUtils.plotsFromCheckedItems(self.theory_model)
+ graph = self.cbgraph_2
# old plot data
- plot_id = str(self.cbgraph.currentText())
+ plot_id = str(graph.currentText())
try:
assert plot_id in PlotHelper.currentPlots(), "No such plot: %s" % (plot_id)
except:
@@ -1883,7 +1898,14 @@ def closePlotsForItem(self, item):
pass # debugger anchor
- def onAnalysisUpdate(self, new_perspective=""):
+ def closeResultPanelOnDelete(self, data):
+ """
+ Given a data1d/2d object, close the fitting results panel if currently populated with the data
+ """
+ # data - Single data1d/2d object to be deleted
+ self.parent.results_panel.onDataDeleted(data)
+
+ def onAnalysisUpdate(self, new_perspective_name: str):
"""
Update the perspective combo index based on passed string
"""
diff --git a/src/sas/qtgui/MainWindow/GuiManager.py b/src/sas/qtgui/MainWindow/GuiManager.py
index dd93bd494d..76ad357bb9 100644
--- a/src/sas/qtgui/MainWindow/GuiManager.py
+++ b/src/sas/qtgui/MainWindow/GuiManager.py
@@ -123,9 +123,18 @@ def addWidgets(self):
"""
Populate the main window with widgets
"""
+ # Add the console window as another docked widget
+ self.logDockWidget = QDockWidget("Log Explorer", self._workspace)
+ self.logDockWidget.setObjectName("LogDockWidget")
+ self.logDockWidget.visibilityChanged.connect(self.updateLogContextMenus)
+
+
+ self.listWidget = QTextBrowser()
+ self.logDockWidget.setWidget(self.listWidget)
+ self._workspace.addDockWidget(Qt.BottomDockWidgetArea, self.logDockWidget)
+
# Preload all perspectives
self.loadAllPerspectives()
-
# Add FileDialog widget as docked
self.filesWidget = DataExplorerWindow(self._parent, self, manager=self._data_manager)
ObjectLibrary.addObject('DataExplorer', self.filesWidget)
@@ -133,23 +142,12 @@ def addWidgets(self):
self.dockedFilesWidget = QDockWidget("Data Explorer", self._workspace)
self.dockedFilesWidget.setFloating(False)
self.dockedFilesWidget.setWidget(self.filesWidget)
-
# Modify menu items on widget visibility change
self.dockedFilesWidget.visibilityChanged.connect(self.updateContextMenus)
self._workspace.addDockWidget(Qt.LeftDockWidgetArea, self.dockedFilesWidget)
self._workspace.resizeDocks([self.dockedFilesWidget], [305], Qt.Horizontal)
- # Add the console window as another docked widget
- self.logDockWidget = QDockWidget("Log Explorer", self._workspace)
- self.logDockWidget.setObjectName("LogDockWidget")
- self.logDockWidget.visibilityChanged.connect(self.updateLogContextMenus)
-
-
- self.listWidget = QTextBrowser()
- self.logDockWidget.setWidget(self.listWidget)
- self._workspace.addDockWidget(Qt.BottomDockWidgetArea, self.logDockWidget)
-
# Add other, minor widgets
self.ackWidget = Acknowledgements()
self.aboutWidget = AboutBox()
@@ -168,8 +166,8 @@ def addWidgets(self):
self.results_frame.setVisible(False)
self.results_panel.windowClosedSignal.connect(lambda: self.results_frame.setVisible(False))
- self._workspace.toolBar.setVisible(LocalConfig.TOOLBAR_SHOW)
- self._workspace.actionHide_Toolbar.setText("Show Toolbar")
+ custom_config = get_custom_config()
+ self._workspace.toolBar.setVisible(custom_config.TOOLBAR_SHOW)
# Add calculators - floating for usability
self.SLDCalculator = SldPanel(self)
diff --git a/src/sas/qtgui/MainWindow/MainWindow.py b/src/sas/qtgui/MainWindow/MainWindow.py
index 8303a75d0d..50d2bff06b 100644
--- a/src/sas/qtgui/MainWindow/MainWindow.py
+++ b/src/sas/qtgui/MainWindow/MainWindow.py
@@ -10,7 +10,7 @@
from PyQt5.QtWidgets import QSplashScreen
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QPixmap
-from PyQt5.QtCore import Qt
+from PyQt5.QtCore import Qt, QTimer
# Local UI
from sas.qtgui.UI import main_resources_rc
@@ -35,7 +35,7 @@ def __init__(self, screen_resolution, parent=None):
self.screen_width = screen_resolution.width()
self.screen_height = screen_resolution.height()
self.setCentralWidget(self.workspace)
-
+ QTimer.singleShot(100, self.showMaximized)
# Temporary solution for problem with menubar on Mac
if sys.platform == "darwin": # Mac
self.menubar.setNativeMenuBar(False)
@@ -110,7 +110,6 @@ def run_sasview():
# Show the main SV window
mainwindow = MainSasViewWindow(screen_resolution)
- mainwindow.showMaximized()
# no more splash screen
splash.finish(mainwindow)
diff --git a/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py b/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py
index 6166bbf713..d5cd844f18 100644
--- a/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py
+++ b/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py
@@ -542,13 +542,17 @@ def residualsData1D(reference_data, current_data, weights):
pass
residuals.x = current_data.x[index][0]
- residuals.dy = numpy.ones(len(residuals.y))
+ residuals.dy = None
residuals.dx = None
residuals.dxl = None
residuals.dxw = None
residuals.ytransform = 'y'
+ if reference_data.isSesans:
+ residuals.xtransform = 'x'
+ residuals.xaxis('\\rm{z} ', 'A')
# For latter scale changes
- residuals.xaxis('\\rm{Q} ', 'A^{-1}')
+ else:
+ residuals.xaxis('\\rm{Q} ', 'A^{-1}')
residuals.yaxis('\\rm{Residuals} ', 'normalized')
return residuals
@@ -572,7 +576,7 @@ def residualsData2D(reference_data, current_data, weight):
residuals.qx_data = current_data.qx_data
residuals.qy_data = current_data.qy_data
residuals.q_data = current_data.q_data
- residuals.err_data = numpy.ones(len(residuals.data))
+ residuals.err_data = None
residuals.xmin = min(residuals.qx_data)
residuals.xmax = max(residuals.qx_data)
residuals.ymin = min(residuals.qy_data)
@@ -604,7 +608,6 @@ def plotResiduals(reference_data, current_data, weights):
res_name = reference_data.name if reference_data.name else reference_data.filename
residuals.name = "Residuals for " + str(theory_name) + "[" + res_name + "]"
residuals.title = residuals.name
- residuals.ytransform = 'y'
# when 2 data have the same id override the 1 st plotted
# include the last part if keeping charts for separate models is required
@@ -632,6 +635,8 @@ def plotPolydispersities(model):
# similar to FittingLogic._create1DPlot() but different data/axes
data1d = Data1D(x=xarr, y=yarr)
xunit = model.details[name][0]
+ data1d.xtransform = 'x'
+ data1d.ytransform = 'y'
data1d.xaxis(r'\rm{{{}}}'.format(name.replace('_', '\_')), xunit)
data1d.yaxis(r'\rm{probability}', 'normalized')
data1d.scale = 'linear'
diff --git a/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py b/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
index b380d0c1a4..658d40cfe6 100644
--- a/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
+++ b/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
@@ -264,9 +264,10 @@ def initializeGlobals(self):
self.q_range_min = OptionsWidget.QMIN_DEFAULT
self.q_range_max = OptionsWidget.QMAX_DEFAULT
self.npts = OptionsWidget.NPTS_DEFAULT
- self.log_points = False
+ self.log_points = True
self.weighting = 0
self.chi2 = None
+
# Does the control support UNDO/REDO
# temporarily off
self.undo_supported = False
@@ -330,6 +331,7 @@ def initializeWidgets(self):
self.options_widget = OptionsWidget(self, self.logic)
layout.addWidget(self.options_widget)
self.tabOptions.setLayout(layout)
+ self.options_widget.setLogScale(self.log_points)
# Smearing widget
layout = QtWidgets.QGridLayout()
@@ -1805,6 +1807,9 @@ def paramDictFromResults(self, results):
logger.error(msg)
return
+ if results.mesg:
+ logger.warning(results.mesg)
+
param_list = results.param_list # ['radius', 'radius.width']
param_values = results.pvec # array([ 0.36221662, 0.0146783 ])
param_stderr = results.stderr # array([ 1.71293015, 1.71294233])
@@ -1879,7 +1884,7 @@ def prepareFitters(self, fitter=None, fit_id=0):
# Data going in
data = self.logic.data
- model = copy.deepcopy(self.kernel_module)
+ model = self.kernel_module
qmin = self.q_range_min
qmax = self.q_range_max
@@ -2237,7 +2242,7 @@ def _requestPlots(self, item_name, item_model):
data_shown = False
item = None
for item, plot in plots.items():
- if fitpage_name in plot.name:
+ if plot.plot_role != Data1D.ROLE_DATA and fitpage_name in plot.name:
data_shown = True
self.communicate.plotRequestedSignal.emit([item, plot], self.tab_id)
# return the last data item seen, if nothing was plotted; supposed to be just data)
@@ -2252,6 +2257,7 @@ def onOptionsUpdate(self):
# set Q range labels on the main tab
self.lblMinRangeDef.setText(GuiUtils.formatNumber(self.q_range_min, high=True))
self.lblMaxRangeDef.setText(GuiUtils.formatNumber(self.q_range_max, high=True))
+ self.recalculatePlotData()
def setDefaultStructureCombo(self):
"""
@@ -4207,12 +4213,12 @@ def updatePageWithParameters(self, line_dict, warn_user=True):
pass
if 'smearing_min' in line_dict.keys():
try:
- self.smearing_widget.dq_l = float(line_dict['smearing_min'][0])
+ self.smearing_widget.dq_r = float(line_dict['smearing_min'][0])
except ValueError:
pass
if 'smearing_max' in line_dict.keys():
try:
- self.smearing_widget.dq_r = float(line_dict['smearing_max'][0])
+ self.smearing_widget.dq_l = float(line_dict['smearing_max'][0])
except ValueError:
pass
diff --git a/src/sas/qtgui/Perspectives/Fitting/ModelThread.py b/src/sas/qtgui/Perspectives/Fitting/ModelThread.py
index eacbdc5350..104777bf71 100644
--- a/src/sas/qtgui/Perspectives/Fitting/ModelThread.py
+++ b/src/sas/qtgui/Perspectives/Fitting/ModelThread.py
@@ -171,10 +171,20 @@ def compute(self):
unsmeared_error = None
##smearer the ouput of the plot
if self.smearer is not None:
- first_bin, last_bin = self.smearer.get_bin_range(self.qmin,
- self.qmax)
- mask = self.data.x[first_bin:last_bin+1]
- unsmeared_output = numpy.zeros((len(self.data.x)))
+ if self.data.isSesans:
+ # For SESANS, data.x, qmin and qmax, and therefore get_bin_range are in real space, and
+ # the Hankel transform from q space to real space is set up as a resolution function, i.e.,
+ # the "unsmeared" data is in q space and the "smeared" data is in real space.
+ # Therefore, q_calc needs to be used here to calculate the unsmeared_out rather than data.x.
+ mask = self.smearer.resolution.q_calc
+ first_bin = 0
+ last_bin = len(mask)
+ unsmeared_output = numpy.zeros((len(mask)))
+ else:
+ first_bin, last_bin = self.smearer.get_bin_range(self.qmin,
+ self.qmax)
+ mask = self.data.x[first_bin:last_bin+1]
+ unsmeared_output = numpy.zeros((len(self.data.x)))
return_data = self.model.calculate_Iq(mask)
if isinstance(return_data, tuple):
@@ -183,11 +193,11 @@ def compute(self):
return_data, intermediate_results = return_data
unsmeared_output[first_bin:last_bin+1] = return_data
output = self.smearer(unsmeared_output, first_bin, last_bin)
-
# Rescale data to unsmeared model
# Check that the arrays are compatible. If we only have a model but no data,
# the length of data.y will be zero.
- if isinstance(self.data.y, numpy.ndarray) and output.shape == self.data.y.shape:
+ # does not apply to SESANS where Hankel was implemented as resolution function
+ if isinstance(self.data.y, numpy.ndarray) and output.shape == self.data.y.shape and not self.data.isSesans:
unsmeared_data = numpy.zeros((len(self.data.x)))
unsmeared_error = numpy.zeros((len(self.data.x)))
unsmeared_data[first_bin:last_bin+1] = self.data.y[first_bin:last_bin+1]\
@@ -253,7 +263,7 @@ def compute(self):
intermediate_results = {}
elapsed = time.time() - self.starttime
-
+
res = dict(x = self.data.x[index], y = output[index],
page_id = self.page_id, state = self.state, weight = self.weight,
fid = self.fid, toggle_mode_on = self.toggle_mode_on,
diff --git a/src/sas/qtgui/Perspectives/Fitting/OptionsWidget.py b/src/sas/qtgui/Perspectives/Fitting/OptionsWidget.py
index 0d69997413..7c95b95774 100644
--- a/src/sas/qtgui/Perspectives/Fitting/OptionsWidget.py
+++ b/src/sas/qtgui/Perspectives/Fitting/OptionsWidget.py
@@ -35,13 +35,12 @@ class OptionsWidget(QtWidgets.QWidget, Ui_tabOptions):
plot_signal = QtCore.pyqtSignal()
QMIN_DEFAULT = 0.0005
QMAX_DEFAULT = 0.5
- NPTS_DEFAULT = 50
+ NPTS_DEFAULT = 150
MODEL = [
'MIN_RANGE',
'MAX_RANGE',
'NPTS',
- 'NPTS_FIT',
- 'LOG_SPACED']
+ 'NPTS_FIT']
def __init__(self, parent=None, logic=None):
super(OptionsWidget, self).__init__()
@@ -125,13 +124,15 @@ def initMapper(self):
self.mapper.addMapping(self.txtMaxRange, self.MODEL.index('MAX_RANGE'))
self.mapper.addMapping(self.txtNpts, self.MODEL.index('NPTS'))
self.mapper.addMapping(self.txtNptsFit, self.MODEL.index('NPTS_FIT'))
- self.mapper.addMapping(self.chkLogData, self.MODEL.index('LOG_SPACED'))
self.mapper.toFirst()
+ def setLogScale(self, log_scale):
+ self.chkLogData.setChecked(log_scale)
+
def toggleLogData(self, isChecked):
""" Toggles between log and linear data sets """
- pass
+ self.plot_signal.emit()
def onMaskEdit(self):
"""
@@ -256,8 +257,7 @@ def state(self):
q_range_max = float(self.model.item(self.MODEL.index('MAX_RANGE')).text())
npts = int(self.model.item(self.MODEL.index('NPTS')).text())
npts_fit = int(self.model.item(self.MODEL.index('NPTS_FIT')).text())
- log_points = str(self.model.item(self.MODEL.index('LOG_SPACED')).text()) == 'true'
-
+ log_points = self.chkLogData.isChecked()
return (q_range_min, q_range_max, npts, log_points, self.weighting)
def npts2fit(self, data=None, qmin=None, qmax=None, npts=None):
diff --git a/src/sas/qtgui/Perspectives/Fitting/UI/OptionsWidgetUI.ui b/src/sas/qtgui/Perspectives/Fitting/UI/OptionsWidgetUI.ui
index e2f9cdf67c..9e6bfe60cc 100755
--- a/src/sas/qtgui/Perspectives/Fitting/UI/OptionsWidgetUI.ui
+++ b/src/sas/qtgui/Perspectives/Fitting/UI/OptionsWidgetUI.ui
@@ -164,6 +164,9 @@
Log spaced points
+
+ true
+
-
diff --git a/src/sas/qtgui/Perspectives/Invariant/UnitTesting/InvariantPerspectiveTest.py b/src/sas/qtgui/Perspectives/Invariant/UnitTesting/InvariantPerspectiveTest.py
index 29794bfafd..06c1254a4a 100644
--- a/src/sas/qtgui/Perspectives/Invariant/UnitTesting/InvariantPerspectiveTest.py
+++ b/src/sas/qtgui/Perspectives/Invariant/UnitTesting/InvariantPerspectiveTest.py
@@ -452,6 +452,9 @@ def checkFakeDataState(self):
self.assertFalse(self.widget.txtNptsLowQ.isReadOnly())
self.assertFalse(self.widget.txtNptsHighQ.isReadOnly())
+ self.assertTrue(self.widget.txtTotalQMin.isReadOnly())
+ self.assertTrue(self.widget.txtTotalQMax.isReadOnly())
+
# content of line edits
self.assertEqual(self.widget.txtName.text(), 'data')
self.assertEqual(self.widget.txtTotalQMin.text(), '0.009')
diff --git a/src/sas/qtgui/Plotting/Plotter.py b/src/sas/qtgui/Plotting/Plotter.py
index 0d929487e5..9f539a7e83 100644
--- a/src/sas/qtgui/Plotting/Plotter.py
+++ b/src/sas/qtgui/Plotting/Plotter.py
@@ -86,6 +86,8 @@ def __init__(self, parent=None, manager=None, quickplot=False):
self.toolbar._actions['pan'].triggered.connect(self._pan)
self.toolbar._actions['zoom'].triggered.connect(self._zoom)
+ self.legendVisible = True
+
parent.geometry()
@property
@@ -159,6 +161,7 @@ def plot(self, data=None, color=None, marker=None, hide_error=False, transform=T
ax = self.ax
x = data.view.x
y = data.view.y
+ label = data.name # was self._title
# Marker symbol. Passed marker is one of matplotlib.markers characters
# Alternatively, picked up from Data1D as an int index of PlotUtilities.SHAPES dict
@@ -202,22 +205,22 @@ def plot(self, data=None, color=None, marker=None, hide_error=False, transform=T
l_width = markersize * 0.4
if marker == '-' or marker == '--':
line = self.ax.plot(x, y, color=color, lw=l_width, marker='',
- linestyle=marker, label=self._title, zorder=10)[0]
+ linestyle=marker, label=label, zorder=10)[0]
elif marker == 'vline':
y_min = min(y)*9.0/10.0 if min(y) < 0 else 0.0
line = self.ax.vlines(x=x, ymin=y_min, ymax=y, color=color,
- linestyle='-', label=self._title, lw=l_width, zorder=1)
+ linestyle='-', label=label, lw=l_width, zorder=1)
elif marker == 'step':
line = self.ax.step(x, y, color=color, marker='', linestyle='-',
- label=self._title, lw=l_width, zorder=1)[0]
+ label=label, lw=l_width, zorder=1)[0]
else:
# plot data with/without errorbars
if hide_error:
line = ax.plot(x, y, marker=marker, color=color, markersize=markersize,
- linestyle='', label=self._title, picker=True)
+ linestyle='', label=label, picker=True)
else:
dy = data.view.dy
# Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
@@ -234,7 +237,7 @@ def plot(self, data=None, color=None, marker=None, hide_error=False, transform=T
markersize=markersize,
lolims=False, uplims=False,
xlolims=False, xuplims=False,
- label=self._title,
+ label=label,
zorder=1,
picker=True)
@@ -256,7 +259,7 @@ def plot(self, data=None, color=None, marker=None, hide_error=False, transform=T
self.legend = ax.legend(loc='upper right', shadow=True)
if self.legend:
self.legend.set_picker(True)
-
+ self.legend.set_visible(self.legendVisible)
# Current labels for axes
if self.yLabel and not is_fit:
ax.set_ylabel(self.yLabel)
@@ -301,7 +304,7 @@ def onResize(self, event):
"""
Resize the legend window/font on canvas resize
"""
- if not self.showLegend:
+ if not self.showLegend or not self.legendVisible:
return
width = _legendResize(event.width, self.parent)
# resize the legend to follow the canvas width.
@@ -574,7 +577,6 @@ def onResetGraphRange(self):
Resets the chart X and Y ranges to their original values
"""
# Clear graph and plot everything again
- mpl.pyplot.cla()
self.ax.cla()
self.setRange = None
for ids in self.plot_dict:
@@ -651,7 +653,6 @@ def removePlot(self, id):
xl = self.ax.xaxis.label.get_text()
yl = self.ax.yaxis.label.get_text()
- mpl.pyplot.cla()
self.ax.cla()
# Recreate Artist bindings after plot clear
@@ -692,7 +693,7 @@ def onModifyPlot(self, id):
marker = selected_plot.symbol
marker_size = selected_plot.markersize
# plot name
- legend = selected_plot.title
+ legend = selected_plot.name
plotPropertiesWidget = PlotProperties(self,
color=color,
marker=marker,
@@ -703,7 +704,7 @@ def onModifyPlot(self, id):
selected_plot.markersize = plotPropertiesWidget.markersize()
selected_plot.custom_color = plotPropertiesWidget.color()
selected_plot.symbol = plotPropertiesWidget.marker()
- selected_plot.title = plotPropertiesWidget.legend()
+ selected_plot.name = plotPropertiesWidget.legend()
# Redraw the plot
self.replacePlot(id, selected_plot)
@@ -721,7 +722,6 @@ def onToggleHideError(self, id):
self.plot_dict = {}
# Clean the canvas
- mpl.pyplot.cla()
self.ax.cla()
# Recreate the plots but reverse the error flag for the current
@@ -794,8 +794,9 @@ def onToggleLegend(self):
if not self.showLegend:
return
- visible = self.legend.get_visible()
- self.legend.set_visible(not visible)
+ #visible = self.legend.get_visible()
+ self.legendVisible = not self.legendVisible
+ self.legend.set_visible(self.legendVisible)
self.canvas.draw_idle()
def onMplMouseDown(self, event):
diff --git a/src/sas/qtgui/Plotting/Slicers/AnnulusSlicer.py b/src/sas/qtgui/Plotting/Slicers/AnnulusSlicer.py
index cca448235d..6a82a929c7 100644
--- a/src/sas/qtgui/Plotting/Slicers/AnnulusSlicer.py
+++ b/src/sas/qtgui/Plotting/Slicers/AnnulusSlicer.py
@@ -28,7 +28,7 @@ def __init__(self, base, axes, item=None, color='black', zorder=3):
self.connect = self.base.connect
# Number of points on the plot
- self.nbins = 36
+ self.nbins = 100
# Cursor position of Rings (Left(-1) or Right(1))
self.xmaxd = self.data.xmax
self.xmind = self.data.xmin
@@ -102,11 +102,7 @@ def _post_data(self, nbins=None):
numpy.fabs(self.outer_circle.get_radius()))
rmax = max(numpy.fabs(self.inner_circle.get_radius()),
numpy.fabs(self.outer_circle.get_radius()))
- # If the user does not specify the numbers of points to plot
- # the default number will be nbins= 36
- if nbins is None:
- self.nbins = 36
- else:
+ if nbins is not None:
self.nbins = nbins
# Create the data1D Q average of data2D
sect = Ring(r_min=rmin, r_max=rmax, nbins=self.nbins)
diff --git a/src/sas/qtgui/Plotting/Slicers/AzimutSlicer.py b/src/sas/qtgui/Plotting/Slicers/AzimutSlicer.py
index 9f8e8fbb58..dc58315657 100644
--- a/src/sas/qtgui/Plotting/Slicers/AzimutSlicer.py
+++ b/src/sas/qtgui/Plotting/Slicers/AzimutSlicer.py
@@ -22,7 +22,7 @@ def __init__(self, base, axes, color='black', zorder=3):
self.connect = self.base.connect
# # Number of points on the plot
- self.nbins = 20
+ self.nbins = 100
theta1 = 2 * np.pi / 3
theta2 = -2 * np.pi / 3
diff --git a/src/sas/qtgui/Plotting/Slicers/BoxSlicer.py b/src/sas/qtgui/Plotting/Slicers/BoxSlicer.py
index 72d9929c16..96c9dcf4d0 100644
--- a/src/sas/qtgui/Plotting/Slicers/BoxSlicer.py
+++ b/src/sas/qtgui/Plotting/Slicers/BoxSlicer.py
@@ -31,7 +31,7 @@ def __init__(self, base, axes, item=None, color='black', zorder=3):
self.qmax = max(self.data.xmax, self.data.xmin,
self.data.ymax, self.data.ymin)
# Number of points on the plot
- self.nbins = 30
+ self.nbins = 100
# If True, I(|Q|) will be return, otherwise,
# negative q-values are allowed
self.fold = True
@@ -494,7 +494,14 @@ def validate(self, param_name, param_value):
Validate input from user.
Values get checked at apply time.
"""
- return True
+ isValid = True
+
+ if param_name == 'nbins':
+ # Can't be 0
+ if param_value < 1:
+ print("Number of bins cannot be less than or equal to 0. Please adjust.")
+ isValid = False
+ return isValid
class BoxInteractorY(BoxInteractor):
@@ -518,4 +525,11 @@ def validate(self, param_name, param_value):
Validate input from user
Values get checked at apply time.
"""
- return True
+ isValid = True
+
+ if param_name == 'nbins':
+ # Can't be 0
+ if param_value < 1:
+ print("Number of bins cannot be less than or equal to 0. Please adjust.")
+ isValid = False
+ return isValid
diff --git a/src/sas/qtgui/Plotting/Slicers/SectorSlicer.py b/src/sas/qtgui/Plotting/Slicers/SectorSlicer.py
index 27ec99f2ff..10c847348d 100644
--- a/src/sas/qtgui/Plotting/Slicers/SectorSlicer.py
+++ b/src/sas/qtgui/Plotting/Slicers/SectorSlicer.py
@@ -34,7 +34,7 @@ def __init__(self, base, axes, item=None, color='black', zorder=3):
numpy.fabs(self.data.ymin)), 2)
self.qmax = numpy.sqrt(x + y)
# Number of points on the plot
- self.nbins = 20
+ self.nbins = 100
# Angle of the middle line
self.theta2 = numpy.pi / 3
# Absolute value of the Angle between the middle line and any side line
@@ -133,7 +133,7 @@ def _post_data(self, nbins=None):
phimin = -self.left_line.phi + self.main_line.theta
phimax = self.left_line.phi + self.main_line.theta
if nbins is None:
- nbins = 20
+ nbins = self.nbins
sect = SectorQ(r_min=0.0, r_max=radius,
phi_min=phimin + numpy.pi,
phi_max=phimax + numpy.pi, nbins=nbins)
diff --git a/src/sas/qtgui/Utilities/GuiUtils.py b/src/sas/qtgui/Utilities/GuiUtils.py
index 287c520758..7838ee900d 100644
--- a/src/sas/qtgui/Utilities/GuiUtils.py
+++ b/src/sas/qtgui/Utilities/GuiUtils.py
@@ -947,7 +947,7 @@ def xyTransform(data, xLabel="", yLabel=""):
xLabel = "%s^{4}(%s)" % (xname, xunits)
if xLabel == "ln(x)":
data.transformX(DataTransform.toLogX, DataTransform.errToLogX)
- xLabel = "\ln{(%s)}(%s)" % (xname, xunits)
+ xLabel = r"\ln{(%s)}(%s)" % (xname, xunits)
if xLabel == "log10(x)":
data.transformX(DataTransform.toX_pos, DataTransform.errToX_pos)
xscale = 'log'
@@ -961,7 +961,7 @@ def xyTransform(data, xLabel="", yLabel=""):
# Y
if yLabel == "ln(y)":
data.transformY(DataTransform.toLogX, DataTransform.errToLogX)
- yLabel = "\ln{(%s)}(%s)" % (yname, yunits)
+ yLabel = r"\ln{(%s)}(%s)" % (yname, yunits)
if yLabel == "y":
data.transformY(DataTransform.toX, DataTransform.errToX)
yLabel = "%s(%s)" % (yname, yunits)
@@ -980,31 +980,31 @@ def xyTransform(data, xLabel="", yLabel=""):
if yLabel == "y*x^(2)":
data.transformY(DataTransform.toYX2, DataTransform.errToYX2)
xunits = convertUnit(2, xunits)
- yLabel = "%s \ \ %s^{2}(%s%s)" % (yname, xname, yunits, xunits)
+ yLabel = r"%s \ \ %s^{2}(%s%s)" % (yname, xname, yunits, xunits)
if yLabel == "y*x^(4)":
data.transformY(DataTransform.toYX4, DataTransform.errToYX4)
xunits = convertUnit(4, xunits)
- yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
+ yLabel = r"%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
if yLabel == "1/sqrt(y)":
data.transformY(DataTransform.toOneOverSqrtX, DataTransform.errOneOverSqrtX)
yunits = convertUnit(-0.5, yunits)
- yLabel = "1/\sqrt{%s}(%s)" % (yname, yunits)
+ yLabel = r"1/\sqrt{%s}(%s)" % (yname, yunits)
if yLabel == "ln(y*x)":
data.transformY(DataTransform.toLogXY, DataTransform.errToLogXY)
- yLabel = "\ln{(%s \ \ %s)}(%s%s)" % (yname, xname, yunits, xunits)
+ yLabel = r"\ln{(%s \ \ %s)}(%s%s)" % (yname, xname, yunits, xunits)
if yLabel == "ln(y*x^(2))":
data.transformY(DataTransform.toLogYX2, DataTransform.errToLogYX2)
xunits = convertUnit(2, xunits)
- yLabel = "\ln (%s \ \ %s^{2})(%s%s)" % (yname, xname, yunits, xunits)
+ yLabel = r"\ln (%s \ \ %s^{2})(%s%s)" % (yname, xname, yunits, xunits)
if yLabel == "ln(y*x^(4))":
data.transformY(DataTransform.toLogYX4, DataTransform.errToLogYX4)
xunits = convertUnit(4, xunits)
- yLabel = "\ln (%s \ \ %s^{4})(%s%s)" % (yname, xname, yunits, xunits)
+ yLabel = r"\ln (%s \ \ %s^{4})(%s%s)" % (yname, xname, yunits, xunits)
if yLabel == "log10(y*x^(4))":
data.transformY(DataTransform.toYX4, DataTransform.errToYX4)
xunits = convertUnit(4, xunits)
yscale = 'log'
- yLabel = "%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
+ yLabel = r"%s \ \ %s^{4}(%s%s)" % (yname, xname, yunits, xunits)
# Perform the transformation of data in data1d->View
data.transformView()
diff --git a/src/sas/qtgui/Utilities/ReportDialog.py b/src/sas/qtgui/Utilities/ReportDialog.py
index 4998b29ba8..e6154607ef 100644
--- a/src/sas/qtgui/Utilities/ReportDialog.py
+++ b/src/sas/qtgui/Utilities/ReportDialog.py
@@ -3,7 +3,6 @@
import re
import logging
import traceback
-from xhtml2pdf import pisa
from PyQt5 import QtWidgets, QtCore
from PyQt5 import QtPrintSupport
@@ -187,6 +186,8 @@ def HTML2PDF(data, filename):
: data: html string
: filename: name of file to be saved
"""
+ # import moved from top due to cost
+ from xhtml2pdf import pisa
try:
# open output file for writing (truncated binary)
with open(filename, "w+b") as resultFile:
diff --git a/src/sas/qtgui/Utilities/ResultPanel.py b/src/sas/qtgui/Utilities/ResultPanel.py
index 5dee6fab2a..80230e02a9 100644
--- a/src/sas/qtgui/Utilities/ResultPanel.py
+++ b/src/sas/qtgui/Utilities/ResultPanel.py
@@ -9,9 +9,6 @@
from PyQt5 import QtGui
from PyQt5 import QtWidgets
-from bumps.dream.stats import var_stats, format_vars
-
-
class ResultPanel(QtWidgets.QTabWidget):
"""
FitPanel class contains fields allowing to fit models and data
@@ -31,6 +28,7 @@ def __init__(self, parent, manager=None, *args, **kwargs):
self.manager = manager
self.communicator = self.manager.communicator()
self.setMinimumSize(400, 400)
+ self.data_id = None
self.updateBumps() # patch bumps ## TEMPORARY ##
@@ -55,18 +53,15 @@ def updateBumps(self):
sys.modules['bumps.gui.plot_view'] = PlotView
def onPlotResults(self, results, optimizer="Unknown"):
- # Clear up previous results
- for view in (self.convergenceView, self.correlationView,
- self.uncertaintyView, self.traceView):
- view.close()
- # close all tabs. REMEMBER TO USE REVERSED RANGE!!!
- for index in reversed(range(self.count())):
- self.removeTab(index)
+ # import moved here due to its cost
+ from bumps.dream.stats import var_stats, format_vars
+ self.clearAnyData()
result = results[0][0]
- filename = result.data.sas_data.filename
+ name = result.data.sas_data.name
current_optimizer = optimizer
- self.setWindowTitle(self.window_name + " - " + filename + " - " + current_optimizer)
+ self.data_id = result.data.sas_data.id
+ self.setWindowTitle(self.window_name + " - " + name + " - " + current_optimizer)
if hasattr(result, 'convergence') and len(result.convergence) > 0:
best, pop = result.convergence[:, 0], result.convergence[:, 1:]
self.convergenceView.update(best, pop)
@@ -95,6 +90,26 @@ def onPlotResults(self, results, optimizer="Unknown"):
if self.count()==0:
self.close()
+ def onDataDeleted(self, data):
+ """ Check if the data set is shown in the window and close tabs as needed. """
+ if not data or not self.isVisible():
+ return
+ if data.id == self.data_id:
+ self.setWindowTitle(self.window_name)
+ self.clearAnyData()
+ self.close()
+
+ def clearAnyData(self):
+ """ Clear any previous results and reset window to its base state. """
+ self.data_id = None
+ # Clear up previous results
+ for view in (self.convergenceView, self.correlationView,
+ self.uncertaintyView, self.traceView):
+ view.close()
+ # close all tabs. REMEMBER TO USE REVERSED RANGE!!!
+ for index in reversed(range(self.count())):
+ self.removeTab(index)
+
def closeEvent(self, event):
"""
Overwrite QDialog close method to allow for custom widget close
diff --git a/src/sas/sascalc/calculator/sas_gen.py b/src/sas/sascalc/calculator/sas_gen.py
index 2ed4351651..c9535e6884 100644
--- a/src/sas/sascalc/calculator/sas_gen.py
+++ b/src/sas/sascalc/calculator/sas_gen.py
@@ -15,8 +15,6 @@
from scipy.spatial.transform import Rotation
from periodictable import formula, nsf
-from .geni import Iq, Iqxy
-
if sys.version_info[0] < 3:
def decode(s):
return s
@@ -179,6 +177,7 @@ def calculate_Iq(self, qx, qy=None):
:Param y: array of y-values
:return: function value
"""
+ from .geni import Iq, Iqxy
# transform position data from sample to beamline coords
x, y, z = self.transform_positions()
sld = self.data_sldn - self.params['solvent_SLD']
diff --git a/src/sas/sascalc/fit/BumpsFitting.py b/src/sas/sascalc/fit/BumpsFitting.py
index 3cebf45e73..4a812b36b5 100644
--- a/src/sas/sascalc/fit/BumpsFitting.py
+++ b/src/sas/sascalc/fit/BumpsFitting.py
@@ -417,6 +417,7 @@ def abort_test():
return True
return False
+ errors = []
fitclass, options = get_fitter()
steps = options.get('steps', 0)
if steps == 0:
@@ -432,16 +433,18 @@ def abort_test():
]
fitdriver = fitters.FitDriver(fitclass, problem=problem,
abort_test=abort_test, **options)
+ clipped = fitdriver.clip()
+ if clipped:
+ errors.append(f"The initial value for {clipped} was outside the fitting range and was coerced.")
omp_threads = int(os.environ.get('OMP_NUM_THREADS', '0'))
mapper = MPMapper if omp_threads == 1 else SerialMapper
fitdriver.mapper = mapper.start_mapper(problem, None)
#import time; T0 = time.time()
try:
best, fbest = fitdriver.fit()
- errors = []
except Exception as exc:
best, fbest = None, np.NaN
- errors = [str(exc), traceback.format_exc()]
+ errors.extend([str(exc), traceback.format_exc()])
finally:
mapper.stop_mapper(fitdriver.mapper)
diff --git a/src/sas/sascalc/pr/p_invertor.py b/src/sas/sascalc/pr/p_invertor.py
index de72132ca6..78e380be4d 100644
--- a/src/sas/sascalc/pr/p_invertor.py
+++ b/src/sas/sascalc/pr/p_invertor.py
@@ -9,7 +9,6 @@
import numpy as np
-from . import calc
logger = logging.getLogger(__name__)
@@ -50,6 +49,7 @@ def residuals(self, pars):
:param pars: input parameters.
:return: residuals - list of residuals.
"""
+ from . import calc
pars = np.float64(pars)
residuals = []
@@ -66,6 +66,7 @@ def pr_residuals(self, pars):
:param pars: input parameters.
:return: residuals - list of residuals.
"""
+ from . import calc
pars = np.float64(pars)
residuals = []
@@ -329,6 +330,7 @@ def iq(self, pars, q):
:return: I(q)
"""
+ from . import calc
q = np.float64(q)
pars = np.float64(pars)
pars = np.atleast_1d(pars)
@@ -349,6 +351,7 @@ def get_iq_smeared(self, pars, q):
:return: I(q), either scalar or vector depending on q.
"""
+ from . import calc
q = np.float64(q)
q = np.atleast_1d(q)
pars = np.float64(pars)
@@ -370,6 +373,7 @@ def pr(self, pars, r):
:return: P(r)
"""
+ from . import calc
r = np.float64(r)
pars = np.float64(pars)
pars = np.atleast_1d(pars)
@@ -390,7 +394,7 @@ def get_pr_err(self, pars, pars_err, r):
:return: (P(r), dP(r))
"""
-
+ from . import calc
pars = np.atleast_1d(np.float64(pars))
r = np.atleast_1d(np.float64(r))
@@ -421,6 +425,7 @@ def basefunc_ft(self, d_max, n, q):
:return: nth Fourier transformed base function, evaluated at q.
"""
+ from . import calc
d_max = np.float64(d_max)
n = int(n)
q = np.float64(q)
@@ -441,6 +446,7 @@ def oscillations(self, pars):
:param pars: c-parameters.
:return: oscillation figure of merit.
"""
+ from . import calc
nslice = 100
pars = np.float64(pars)
pars = np.atleast_1d(pars)
@@ -459,6 +465,7 @@ def get_peaks(self, pars):
:param pars: c-parameters.
:return: number of P(r) peaks.
"""
+ from . import calc
nslice = 100
pars = np.float64(pars)
count = calc.npeaks(pars, self.d_max, nslice)
@@ -473,6 +480,7 @@ def get_positive(self, pars):
:param pars: c-parameters.
:return: fraction of P(r) that is positive.
"""
+ from . import calc
nslice = 100
pars = np.float64(pars)
pars = np.atleast_1d(pars)
@@ -490,6 +498,7 @@ def get_pos_err(self, pars, pars_err):
:return: fraction of P(r) that is positive.
"""
+ from . import calc
nslice = 51
pars = np.float64(pars)
pars = np.atleast_1d(pars)
@@ -505,6 +514,7 @@ def rg(self, pars):
:param pars: c-parameters.
:return: Rg.
"""
+ from . import calc
nslice = 101
pars = np.float64(pars)
pars = np.atleast_1d(pars)
@@ -519,6 +529,7 @@ def iq0(self, pars):
:param pars: c-parameters.
:return: I(q=0)
"""
+ from . import calc
nslice = 101
pars = np.float64(pars)
pars = np.atleast_1d(pars)
@@ -550,6 +561,7 @@ def _get_matrix(self, nfunc, nr):
:return: 0
"""
+ from . import calc
nfunc = int(nfunc)
nr = int(nr)
a_obj = np.zeros([self.npoints + nr, nfunc])