- Timestamp:
- Apr 25, 2018 6:59:33 AM (7 years ago)
- Branches:
- ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
- Children:
- 017b285
- Parents:
- 93c79b5
- git-author:
- Piotr Rozyczko <rozyczko@…> (03/22/18 16:46:19)
- git-committer:
- Piotr Rozyczko <rozyczko@…> (04/25/18 06:59:33)
- Location:
- src/sas/qtgui
- Files:
-
- 15 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/qtgui/MainWindow/DataExplorer.py
rd6e38661 rd4dac80 476 476 self.chkBatch.setEnabled(self.parent.perspective().allowBatch()) 477 477 478 def itemFromFilename(self, filename): 479 """ 480 Retrieves model item corresponding to the given filename 481 """ 482 item = GuiUtils.itemFromFilename(filename, self.model) 483 return item 484 478 485 def displayFile(self, filename=None, is_data=True): 479 486 """ -
src/sas/qtgui/MainWindow/GuiManager.py
r3b8cc00 rd4dac80 21 21 from sas.qtgui.Utilities.TabbedModelEditor import TabbedModelEditor 22 22 from sas.qtgui.Utilities.PluginManager import PluginManager 23 from sas.qtgui.Utilities.GridPanel import BatchOutputPanel 24 23 25 from sas.qtgui.MainWindow.UI.AcknowledgementsUI import Ui_Acknowledgements 24 26 from sas.qtgui.MainWindow.AboutBox import AboutBox … … 130 132 self.aboutWidget = AboutBox() 131 133 self.welcomePanel = WelcomePanel() 134 self.grid_window = None 132 135 133 136 # Add calculators - floating for usability … … 263 266 msg = "Guiframe does not have a current perspective" 264 267 logging.info(msg) 268 269 def findItemFromFilename(self, filename): 270 """ 271 Queries the data explorer for the index corresponding to the filename within 272 """ 273 return self.filesWidget.itemFromFilename(filename) 265 274 266 275 def quitApplication(self): … … 405 414 self._workspace.actionGPU_Options.triggered.connect(self.actionGPU_Options) 406 415 self._workspace.actionFit_Results.triggered.connect(self.actionFit_Results) 407 self._workspace.actionChain_Fitting.triggered.connect(self.actionChain_Fitting)408 416 self._workspace.actionAdd_Custom_Model.triggered.connect(self.actionAdd_Custom_Model) 409 417 self._workspace.actionEdit_Custom_Model.triggered.connect(self.actionEdit_Custom_Model) … … 428 436 self._workspace.actionCheck_for_update.triggered.connect(self.actionCheck_for_update) 429 437 438 self.communicate.sendDataToGridSignal.connect(self.showBatchOutput) 439 430 440 #============ FILE ================= 431 441 def actionLoadData(self): … … 527 537 """ 528 538 """ 529 print("actionShow_Grid_Window TRIGGERED") 530 pass 539 self.showBatchOutput(None) 540 541 def showBatchOutput(self, output_data): 542 """ 543 Display/redisplay the batch fit viewer 544 """ 545 if self.grid_window is None: 546 self.grid_window = BatchOutputPanel(parent=self, output_data=output_data) 547 subwindow = self._workspace.workspace.addSubWindow(self.grid_window) 548 549 #self.grid_window = BatchOutputPanel(parent=self, output_data=output_data) 550 self.grid_window.show() 551 return 552 if output_data: 553 self.grid_window.addFitResults(output_data) 554 self.grid_window.show() 555 if self.grid_window.windowState() == Qt.WindowMinimized: 556 self.grid_window.setWindowState(Qt.WindowActive) 531 557 532 558 def actionHide_Toolbar(self): … … 658 684 """ 659 685 print("actionFit_Results TRIGGERED") 660 pass661 662 def actionChain_Fitting(self):663 """664 """665 print("actionChain_Fitting TRIGGERED")666 686 pass 667 687 -
src/sas/qtgui/MainWindow/UI/MainWindowUI.ui
r01ef3f7 rd4dac80 106 106 <addaction name="actionFit_Results"/> 107 107 <addaction name="separator"/> 108 <addaction name="actionChain_Fitting"/>109 <addaction name="separator"/>110 108 <addaction name="actionAdd_Custom_Model"/> 111 109 <addaction name="actionEdit_Custom_Model"/> -
src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py
r8b480d27 rd4dac80 358 358 elapsed = result[1] 359 359 360 # ADD THE BATCH FIT VIEW HERE 361 # 360 if result is None: 361 msg = "Fitting failed." 362 self.parent.communicate.statusBarUpdateSignal.emit(msg) 363 return 364 365 # Show the grid panel 366 self.parent.communicate.sendDataToGridSignal.emit(result[0]) 362 367 363 368 msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed) -
src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py
rfde5bcd rd4dac80 421 421 weight = numpy.abs(data) 422 422 return weight 423 424 def updateKernelWithResults(kernel, results): 425 """ 426 Takes model kernel and applies results dict to its parameters, 427 returning the modified (deep) copy of the kernel. 428 """ 429 assert(isinstance(results, dict)) 430 local_kernel = copy.deepcopy(kernel) 431 432 for parameter in results.keys(): 433 # Update the parameter value - note: this supports +/-inf as well 434 local_kernel.setParam(parameter, results[parameter][0]) 435 436 return local_kernel 437 438 -
src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
rded5e77 rd4dac80 3 3 from collections import defaultdict 4 4 5 5 import copy 6 6 import logging 7 7 import traceback … … 24 24 import sas.qtgui.Utilities.GuiUtils as GuiUtils 25 25 import sas.qtgui.Utilities.LocalConfig as LocalConfig 26 from sas.qtgui.Utilities.GridPanel import BatchOutputPanel27 26 from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller 28 27 from sas.qtgui.Plotting.PlotterData import Data1D … … 88 87 fittingFinishedSignal = QtCore.pyqtSignal(tuple) 89 88 batchFittingFinishedSignal = QtCore.pyqtSignal(tuple) 89 Calc1DFinishedSignal = QtCore.pyqtSignal(tuple) 90 Calc2DFinishedSignal = QtCore.pyqtSignal(tuple) 90 91 91 92 def __init__(self, parent=None, data=None, tab_id=1): … … 99 100 self.tab_id = tab_id 100 101 101 # Main Data[12]D holder102 self.logic = FittingLogic()103 104 102 # Globals 105 103 self.initializeGlobals() … … 107 105 # Set up desired logging level 108 106 logging.disable(LocalConfig.DISABLE_LOGGING) 107 108 # data index for the batch set 109 self.data_index = 0 110 # Main Data[12]D holders 111 # Logics.data contains a single Data1D/Data2D object 112 self._logic = [FittingLogic()] 109 113 110 114 # Main GUI setup up … … 134 138 self.initializeControls() 135 139 136 # Display HTML content137 #self.setupHelp()140 if data is not None: 141 self.data = data 138 142 139 143 # New font to display angstrom symbol … … 142 146 self.label_19.setStyleSheet(new_font) 143 147 144 self._index = None 145 if data is not None: 146 self.data = data 148 @property 149 def logic(self): 150 # make sure the logic contains at least one element 151 assert(self._logic) 152 # logic connected to the currently shown data 153 return self._logic[self.data_index] 147 154 148 155 @property … … 161 168 162 169 assert isinstance(value[0], QtGui.QStandardItem) 163 # _index contains the QIndex with data164 self._index = value[0]165 170 166 171 # Keep reference to all datasets for batch 167 172 self.all_data = value 168 173 169 # Update logics with data items 174 # Create logics with data items 175 self._logic=[] 170 176 # Logics.data contains only a single Data1D/Data2D object 171 self.logic.data = GuiUtils.dataFromItem(value[0]) 177 for data_item in value: 178 self._logic.append(FittingLogic()) 179 self._logic[-1].data = GuiUtils.dataFromItem(data_item) 172 180 173 181 # Overwrite data type descriptor … … 226 234 # Polydisp widget table default index for function combobox 227 235 self.orig_poly_index = 3 236 # copy of current kernel model 237 self.kernel_module_copy = None 228 238 229 239 # Page id for fitting … … 488 498 self.batchFittingFinishedSignal.connect(self.batchFitComplete) 489 499 self.fittingFinishedSignal.connect(self.fitComplete) 500 self.Calc1DFinishedSignal.connect(self.complete1D) 501 self.Calc2DFinishedSignal.connect(self.complete2D) 490 502 491 503 # Signals from separate tabs asking for replot … … 930 942 Update the logic based on the selected file in batch fitting 931 943 """ 932 self._index = self.all_data[data_index] 933 self.logic.data = GuiUtils.dataFromItem(self.all_data[data_index]) 944 self.data_index = data_index 934 945 self.updateQRange() 935 946 … … 1231 1242 return 1232 1243 1244 # keep local copy of kernel parameters, as they will change during the update 1245 self.kernel_module_copy = copy.deepcopy(self.kernel_module) 1246 1233 1247 # Create the fitting thread, based on the fitter 1234 1248 completefn = self.batchFittingCompleted if self.is_batch_fitting else self.fittingCompleted … … 1297 1311 #re-enable the Fit button 1298 1312 self.setFittingStopped() 1299 # Show the grid panel1300 self.grid_window = BatchOutputPanel(parent=self, output_data=result[0])1301 self.grid_window.show()1302 1303 def fittingCompleted(self, result):1304 """1305 Send the finish message from calculate threads to main thread1306 """1307 self.fittingFinishedSignal.emit(result)1308 1309 def fitComplete(self, result):1310 """1311 Receive and display fitting results1312 "result" is a tuple of actual result list and the fit time in seconds1313 """1314 #re-enable the Fit button1315 self.setFittingStopped()1316 1313 1317 1314 if result is None: … … 1320 1317 return 1321 1318 1319 # Show the grid panel 1320 self.communicate.sendDataToGridSignal.emit(result[0]) 1321 1322 elapsed = result[1] 1323 msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed) 1324 self.communicate.statusBarUpdateSignal.emit(msg) 1325 1326 # Run over the list of results and update the items 1327 for res_index, res_list in enumerate(result[0]): 1328 # results 1329 res = res_list[0] 1330 param_dict = self.paramDictFromResults(res) 1331 1332 # create local kernel_module 1333 kernel_module = FittingUtilities.updateKernelWithResults(self.kernel_module, param_dict) 1334 # pull out current data 1335 data = self._logic[res_index].data 1336 1337 # Switch indexes 1338 self.onSelectBatchFilename(res_index) 1339 1340 method = self.complete1D if isinstance(self.data, Data1D) else self.complete2D 1341 self.calculateQGridForModelExt(data=data, model=kernel_module, completefn=method, use_threads=False) 1342 1343 # Restore original kernel_module, so subsequent fits on the same model don't pick up the new params 1344 if self.kernel_module is not None: 1345 self.kernel_module = copy.deepcopy(self.kernel_module_copy) 1346 1347 def paramDictFromResults(self, results): 1348 """ 1349 Given the fit results structure, pull out optimized parameters and return them as nicely 1350 formatted dict 1351 """ 1352 if results.fitness is None or \ 1353 not np.isfinite(results.fitness) or \ 1354 np.any(results.pvec is None) or \ 1355 not np.all(np.isfinite(results.pvec)): 1356 msg = "Fitting did not converge!" 1357 self.communicate.statusBarUpdateSignal.emit(msg) 1358 msg += results.mesg 1359 logging.error(msg) 1360 return 1361 1362 param_list = results.param_list # ['radius', 'radius.width'] 1363 param_values = results.pvec # array([ 0.36221662, 0.0146783 ]) 1364 param_stderr = results.stderr # array([ 1.71293015, 1.71294233]) 1365 params_and_errors = list(zip(param_values, param_stderr)) 1366 param_dict = dict(zip(param_list, params_and_errors)) 1367 1368 return param_dict 1369 1370 def fittingCompleted(self, result): 1371 """ 1372 Send the finish message from calculate threads to main thread 1373 """ 1374 self.fittingFinishedSignal.emit(result) 1375 1376 def fitComplete(self, result): 1377 """ 1378 Receive and display fitting results 1379 "result" is a tuple of actual result list and the fit time in seconds 1380 """ 1381 #re-enable the Fit button 1382 self.setFittingStopped() 1383 1384 if result is None: 1385 msg = "Fitting failed." 1386 self.communicate.statusBarUpdateSignal.emit(msg) 1387 return 1388 1322 1389 res_list = result[0][0] 1323 1390 res = res_list[0] 1324 if res.fitness is None or \ 1325 not np.isfinite(res.fitness) or \ 1326 np.any(res.pvec is None) or \ 1327 not np.all(np.isfinite(res.pvec)): 1328 msg = "Fitting did not converge!" 1329 self.communicate.statusBarUpdateSignal.emit(msg) 1330 msg += res.mesg 1331 logging.error(msg) 1332 return 1391 self.chi2 = res.fitness 1392 param_dict = self.paramDictFromResults(res) 1333 1393 1334 1394 elapsed = result[1] … … 1339 1399 msg = "Fitting completed successfully in: %s s." % GuiUtils.formatNumber(elapsed) 1340 1400 self.communicate.statusBarUpdateSignal.emit(msg) 1341 1342 self.chi2 = res.fitness1343 param_list = res.param_list # ['radius', 'radius.width']1344 param_values = res.pvec # array([ 0.36221662, 0.0146783 ])1345 param_stderr = res.stderr # array([ 1.71293015, 1.71294233])1346 params_and_errors = list(zip(param_values, param_stderr))1347 param_dict = dict(zip(param_list, params_and_errors))1348 1401 1349 1402 # Dictionary of fitted parameter: value, error … … 2001 2054 fitted_data.symbol = 'Line' 2002 2055 # Notify the GUI manager so it can update the main model in DataExplorer 2003 GuiUtils.updateModelItemWithPlot(self. _index, fitted_data, name)2056 GuiUtils.updateModelItemWithPlot(self.all_data[self.data_index], fitted_data, name) 2004 2057 2005 2058 def createTheoryIndex(self, fitted_data): … … 2031 2084 def methodCompleteForData(self): 2032 2085 '''return the method for result parsin on calc complete ''' 2033 return self.complete1D if isinstance(self.data, Data1D) else self.complete2D 2034 2035 def calculateQGridForModel(self): 2036 """ 2037 Prepare the fitting data object, based on current ModelModel 2038 """ 2039 if self.kernel_module is None: 2040 return 2086 return self.completed1D if isinstance(self.data, Data1D) else self.completed2D 2087 2088 def calculateQGridForModelExt(self, data=None, model=None, completefn=None, use_threads=True): 2089 """ 2090 Wrapper for Calc1D/2D calls 2091 """ 2092 if data is None: 2093 data = self.data 2094 if model is None: 2095 model = self.kernel_module 2096 if completefn is None: 2097 completefn = self.methodCompleteForData() 2098 2041 2099 # Awful API to a backend method. 2042 method = self.methodCalculateForData()(data=self.data,2043 model= self.kernel_module,2100 calc_thread = self.methodCalculateForData()(data=data, 2101 model=model, 2044 2102 page_id=0, 2045 2103 qmin=self.q_range_min, … … 2050 2108 fid=None, 2051 2109 toggle_mode_on=False, 2052 completefn= None,2110 completefn=completefn, 2053 2111 update_chisqr=True, 2054 2112 exception_handler=self.calcException, 2055 2113 source=None) 2056 2057 calc_thread = threads.deferToThread(method.compute) 2058 calc_thread.addCallback(self.methodCompleteForData()) 2059 calc_thread.addErrback(self.calculateDataFailed) 2114 if use_threads: 2115 if LocalConfig.USING_TWISTED: 2116 # start the thread with twisted 2117 thread = threads.deferToThread(calc_thread.compute) 2118 thread.addCallback(completefn) 2119 thread.addErrback(self.calculateDataFailed) 2120 else: 2121 # Use the old python threads + Queue 2122 calc_thread.queue() 2123 calc_thread.ready(2.5) 2124 else: 2125 results = calc_thread.compute() 2126 completefn(results) 2127 2128 def calculateQGridForModel(self): 2129 """ 2130 Prepare the fitting data object, based on current ModelModel 2131 """ 2132 if self.kernel_module is None: 2133 return 2134 self.calculateQGridForModelExt() 2060 2135 2061 2136 def calculateDataFailed(self, reason): … … 2064 2139 """ 2065 2140 print("Calculate Data failed with ", reason) 2141 2142 def completed1D(self, return_data): 2143 self.Calc1DFinishedSignal.emit(return_data) 2144 2145 def completed2D(self, return_data): 2146 self.Calc2DFinishedSignal.emit(return_data) 2066 2147 2067 2148 def complete1D(self, return_data): … … 2107 2188 residuals_plot.id = "Residual " + residuals_plot.id 2108 2189 self.createNewIndex(residuals_plot) 2109 #self.communicate.plotUpdateSignal.emit([residuals_plot])2110 2190 2111 2191 def calcException(self, etype, value, tb): -
src/sas/qtgui/Perspectives/Fitting/ModelThread.py
rcee5c78 rd4dac80 8 8 from sas.sascalc.data_util.calcthread import CalcThread 9 9 from sas.sascalc.fit.MultiplicationModel import MultiplicationModel 10 import sas.qtgui.Utilities.LocalConfig as LocalConfig 10 11 11 12 class Calc2D(CalcThread): … … 99 100 output[index_model] = value 100 101 elapsed = time.time() - self.starttime 101 #self.complete(image=output, 102 # data=self.data, 103 # page_id=self.page_id, 104 # model=self.model, 105 # state=self.state, 106 # toggle_mode_on=self.toggle_mode_on, 107 # elapsed=elapsed, 108 # index=index_model, 109 # fid=self.fid, 110 # qmin=self.qmin, 111 # qmax=self.qmax, 112 # weight=self.weight, 113 # #qstep=self.qstep, 114 # update_chisqr=self.update_chisqr, 115 # source=self.source) 116 return (output, 117 self.data, 118 self.page_id, 119 self.model, 120 self.state, 121 self.toggle_mode_on, 122 elapsed, 123 index_model, 124 self.fid, 125 self.qmin, 126 self.qmax, 127 self.weight, 128 self.update_chisqr, 129 self.source) 102 103 if LocalConfig.USING_TWISTED: 104 return (output, 105 self.data, 106 self.page_id, 107 self.model, 108 self.state, 109 self.toggle_mode_on, 110 elapsed, 111 index_model, 112 self.fid, 113 self.qmin, 114 self.qmax, 115 self.weight, 116 self.update_chisqr, 117 self.source) 118 else: 119 self.complete(image=output, 120 data=self.data, 121 page_id=self.page_id, 122 model=self.model, 123 state=self.state, 124 toggle_mode_on=self.toggle_mode_on, 125 elapsed=elapsed, 126 index=index_model, 127 fid=self.fid, 128 qmin=self.qmin, 129 qmax=self.qmax, 130 weight=self.weight, 131 #qstep=self.qstep, 132 update_chisqr=self.update_chisqr, 133 source=self.source) 130 134 131 135 … … 229 233 elapsed = time.time() - self.starttime 230 234 231 return (self.data.x[index], output[index], 232 self.page_id, 233 self.state, 234 self.weight, 235 self.fid, 236 self.toggle_mode_on, 237 elapsed, index, self.model, 238 self.data, 239 self.update_chisqr, 240 self.source) 241 242 # TODO: as of 4.1, the output contains more items: 243 # unsmeared_* and pq_model/sq_model 244 # Need to add these too 245 246 #self.complete(x=self.data.x[index], y=output[index], 247 # page_id=self.page_id, 248 # state=self.state, 249 # weight=self.weight, 250 # fid=self.fid, 251 # toggle_mode_on=self.toggle_mode_on, 252 # elapsed=elapsed, index=index, model=self.model, 253 # data=self.data, 254 # update_chisqr=self.update_chisqr, 255 # source=self.source, 256 # unsmeared_model=unsmeared_output, 257 # unsmeared_data=unsmeared_data, 258 # unsmeared_error=unsmeared_error, 259 # pq_model=pq_values, 260 # sq_model=sq_values) 235 if LocalConfig.USING_TWISTED: 236 return (self.data.x[index], output[index], 237 self.page_id, 238 self.state, 239 self.weight, 240 self.fid, 241 self.toggle_mode_on, 242 elapsed, index, self.model, 243 self.data, 244 self.update_chisqr, 245 self.source) 246 else: 247 self.complete(x=self.data.x[index], y=output[index], 248 page_id=self.page_id, 249 state=self.state, 250 weight=self.weight, 251 fid=self.fid, 252 toggle_mode_on=self.toggle_mode_on, 253 elapsed=elapsed, index=index, model=self.model, 254 data=self.data, 255 update_chisqr=self.update_chisqr, 256 source=self.source, 257 unsmeared_model=unsmeared_output, 258 unsmeared_data=unsmeared_data, 259 unsmeared_error=unsmeared_error, 260 pq_model=pq_values, 261 sq_model=sq_values) 261 262 262 263 def results(self): -
src/sas/qtgui/Perspectives/Fitting/ModelUtilities.py
r3b3b40b rd4dac80 173 173 return {} 174 174 175 plugin_log("looking for models in: %s" % str(directory))176 # compile_file(directory) #always recompile the folder plugin177 logging.info("plugin model dir: %s" % str(directory))178 179 175 plugins = {} 180 176 for filename in os.listdir(directory): -
src/sas/qtgui/Perspectives/Fitting/UI/FittingWidgetUI.ui
r3b3b40b rd4dac80 216 216 </property> 217 217 <property name="toolTip"> 218 <string><html><head/><body><p>Switch on magnetic scattering parameters.</p><p>This option is available only for 2D models.</p></body></html></string>218 <string><html><head/><body><p>Switch on Chain Fitting (parameter reuse) for batch datasets.</p></body></html></string> 219 219 </property> 220 220 <property name="text"> -
src/sas/qtgui/Utilities/GridPanel.py
r8b480d27 rd4dac80 1 1 import os 2 import sys 2 3 import time 3 4 import logging 4 5 import webbrowser 5 6 6 from PyQt5 import QtCore, QtWidgets 7 from PyQt5 import QtCore, QtWidgets, QtGui 7 8 8 9 import sas.qtgui.Utilities.GuiUtils as GuiUtils 9 10 from sas.qtgui.Plotting.PlotterData import Data1D 10 11 from sas.qtgui.Utilities.UI.GridPanelUI import Ui_GridPanelUI 11 12 … … 15 16 Class for stateless grid-like printout of model parameters for mutiple models 16 17 """ 18 ERROR_COLUMN_CAPTION = " (Err)" 19 IS_WIN = (sys.platform == 'win32') 17 20 def __init__(self, parent = None, output_data=None): 18 21 19 super(BatchOutputPanel, self).__init__( )22 super(BatchOutputPanel, self).__init__(parent._parent) 20 23 self.setupUi(self) 21 24 22 self.data = output_data23 25 self.parent = parent 24 26 if hasattr(self.parent, "communicate"): … … 30 32 self.grid_filename = "" 31 33 34 self.has_data = False if output_data is None else True 35 # Tab numbering 36 self.tab_number = 1 37 38 # System dependent menu items 39 if not self.IS_WIN: 40 self.actionOpen_with_Excel.setVisible(False) 41 42 # list of QTableWidgets, indexed by tab number 43 self.tables = [] 44 self.tables.append(self.tblParams) 45 32 46 # context menu on the table 33 47 self.tblParams.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 34 48 self.tblParams.customContextMenuRequested.connect(self.showContextMenu) 35 49 36 # Fill in the table from input data37 self.setupTable(output_data)38 39 50 # Command buttons 40 51 self.cmdHelp.clicked.connect(self.onHelp) 52 self.cmdPlot.clicked.connect(self.onPlot) 53 54 # Fill in the table from input data 55 self.setupTable(widget=self.tblParams, data=output_data) 56 if output_data is not None: 57 # Set a table tooltip describing the model 58 model_name = output_data[0][0].model.id 59 self.tabWidget.setTabToolTip(0, model_name) 60 61 def closeEvent(self, event): 62 """ 63 Overwrite QDialog close method to allow for custom widget close 64 """ 65 # Maybe we should just minimize 66 self.setWindowState(QtCore.Qt.WindowMinimized) 67 event.ignore() 41 68 42 69 def addToolbarActions(self): … … 64 91 65 92 self.setupTableFromCSV(lines) 93 self.has_data = True 94 95 def currentTable(self): 96 """ 97 Returns the currently shown QTabWidget 98 """ 99 return self.tables[self.tabWidget.currentIndex()] 66 100 67 101 def showContextMenu(self, position): … … 70 104 """ 71 105 menu = QtWidgets.QMenu() 72 rows = [s.row() for s in self. tblParams.selectionModel().selectedRows()]106 rows = [s.row() for s in self.currentTable().selectionModel().selectedRows()] 73 107 num_rows = len(rows) 74 108 if num_rows <= 0: … … 84 118 85 119 # Define the callbacks 86 self.actionPlotResults.triggered.connect(self. plotFits)120 self.actionPlotResults.triggered.connect(self.onPlot) 87 121 try: 88 menu.exec_(self. tblParams.viewport().mapToGlobal(position))122 menu.exec_(self.currentTable().viewport().mapToGlobal(position)) 89 123 except AttributeError as ex: 90 124 logging.error("Error generating context menu: %s" % ex) 91 125 return 126 127 def addTabPage(self): 128 """ 129 Add new tab page with QTableWidget 130 """ 131 layout = QtWidgets.QVBoxLayout() 132 tab_widget = QtWidgets.QTableWidget(parent=self) 133 # Same behaviour as the original tblParams 134 tab_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 135 tab_widget.setAlternatingRowColors(True) 136 tab_widget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) 137 tab_widget.setLayout(layout) 138 # Simple naming here. 139 # One would think naming the tab with current model name would be good. 140 # However, some models have LONG names, which doesn't look well on the tab bar. 141 self.tab_number += 1 142 tab_name = "Tab " + str(self.tab_number) 143 # each table needs separate slots. 144 tab_widget.customContextMenuRequested.connect(self.showContextMenu) 145 self.tables.append(tab_widget) 146 self.tabWidget.addTab(tab_widget, tab_name) 147 # Make the new tab active 148 self.tabWidget.setCurrentIndex(self.tab_number-1) 149 150 def addFitResults(self, results): 151 """ 152 Create a new tab with batch fitting results 153 """ 154 self.addTabPage() 155 # Update the new widget 156 # Fill in the table from input data in the last/newest page 157 assert(self.tables) 158 self.setupTable(widget=self.tables[-1], data=results) 159 self.has_data = True 160 161 # Set a table tooltip describing the model 162 model_name = results[0][0].model.id 163 self.tabWidget.setTabToolTip(self.tabWidget.count()-1, model_name) 164 92 165 93 166 @classmethod … … 103 176 logging.warning("Cannot display help. %s" % ex) 104 177 105 def plotFits(self):178 def onPlot(self): 106 179 """ 107 180 Plot selected fits by sending signal to the parent 108 181 """ 109 rows = [s.row() for s in self.tblParams.selectionModel().selectedRows()] 110 data = self.dataFromTable(self.tblParams) 182 rows = [s.row() for s in self.currentTable().selectionModel().selectedRows()] 183 if not rows: 184 msg = "Nothing to plot!" 185 self.parent.communicate.statusBarUpdateSignal.emit(msg) 186 return 187 data = self.dataFromTable(self.currentTable()) 111 188 # data['Data'] -> ['filename1', 'filename2', ...] 112 189 # look for the 'Data' column and extract the filename … … 141 218 tmpfile = tempfile.NamedTemporaryFile(delete=False, mode="w+", suffix=".csv") 142 219 self.grid_filename = tmpfile.name 143 data = self.dataFromTable(self. tblParams)220 data = self.dataFromTable(self.currentTable()) 144 221 t = time.localtime(time.time()) 145 222 time_str = time.strftime("%b %d %H:%M of %Y", t) … … 181 258 if not filename: 182 259 return 183 data = self.dataFromTable(self. tblParams)260 data = self.dataFromTable(self.currentTable()) 184 261 details = "File generated by SasView\n" 185 262 with open(filename, 'w') as csv_file: … … 190 267 Create tablewidget items and show them, based on params 191 268 """ 192 # Clear existing display 193 self.tblParams.clear() 269 # Is this an empty grid? 270 if self.has_data: 271 # Add a new page 272 self.addTabPage() 273 # Access the newly created QTableWidget 274 current_page = self.tables[-1] 275 else: 276 current_page = self.tblParams 194 277 # headers 195 278 param_list = csv_data[1].rstrip().split(',') 279 # need to remove the 2 header rows to get the total data row number 280 rows = len(csv_data) -2 281 assert(rows > -1) 282 columns = len(param_list) 283 current_page.setColumnCount(columns) 284 current_page.setRowCount(rows) 285 196 286 for i, param in enumerate(param_list): 197 self.tblParams.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param))287 current_page.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param)) 198 288 199 289 # first - Chi2 and data filename 200 290 for i_row, row in enumerate(csv_data[2:]): 201 291 for i_col, col in enumerate(row.rstrip().split(',')): 202 self.tblParams.setItem(i_row, i_col, QtWidgets.QTableWidgetItem(col))203 204 self.tblParams.resizeColumnsToContents()205 206 def setupTable(self, data):292 current_page.setItem(i_row, i_col, QtWidgets.QTableWidgetItem(col)) 293 294 current_page.resizeColumnsToContents() 295 296 def setupTable(self, widget=None, data=None): 207 297 """ 208 298 Create tablewidget items and show them, based on params 209 299 """ 210 # headers 300 # quietly leave is nothing to show 301 if data is None or widget is None: 302 return 303 304 # Figure out the headers 211 305 model = data[0][0] 306 307 # TODO: add a conditional for magnetic models 212 308 param_list = [m for m in model.model.params.keys() if ":" not in m] 213 309 214 310 # Check if 2D model. If not, remove theta/phi 311 if isinstance(model.data.sas_data, Data1D): 312 param_list.remove('theta') 313 param_list.remove('phi') 215 314 216 315 rows = len(data) 217 316 columns = len(param_list) 218 self.tblParams.setColumnCount(columns+2) 219 self.tblParams.setRowCount(rows) 220 317 318 widget.setColumnCount(columns+2) # add 2 initial columns defined below 319 widget.setRowCount(rows) 320 321 # Insert two additional columns 221 322 param_list.insert(0, "Data") 222 323 param_list.insert(0, "Chi2") 223 324 for i, param in enumerate(param_list): 224 self.tblParams.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param)) 225 325 widget.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param)) 326 327 # dictionary of parameter errors for post-processing 328 # [param_name] = [param_column_nr, error_for_row_1, error_for_row_2,...] 329 error_columns = {} 226 330 # first - Chi2 and data filename 227 331 for i_row, row in enumerate(data): … … 231 335 if hasattr(row[0].data, "sas_data"): 232 336 filename = row[0].data.sas_data.filename 233 self.tblParams.setItem(i_row, 0, QtWidgets.QTableWidgetItem(GuiUtils.formatNumber(chi2, high=True)))234 self.tblParams.setItem(i_row, 1, QtWidgets.QTableWidgetItem(str(filename)))337 widget.setItem(i_row, 0, QtWidgets.QTableWidgetItem(GuiUtils.formatNumber(chi2, high=True))) 338 widget.setItem(i_row, 1, QtWidgets.QTableWidgetItem(str(filename))) 235 339 # Now, all the parameters 236 340 for i_col, param in enumerate(param_list[2:]): … … 238 342 # parameter is on the to-optimize list - get the optimized value 239 343 par_value = row[0].pvec[row[0].param_list.index(param)] 240 # should we parse out errors here and store them? 344 # parse out errors and store them for later use 345 err_value = row[0].stderr[row[0].param_list.index(param)] 346 if param in error_columns: 347 error_columns[param].append(err_value) 348 else: 349 error_columns[param] = [i_col, err_value] 241 350 else: 242 351 # parameter was not varied 243 352 par_value = row[0].model.params[param] 244 self.tblParams.setItem(i_row, i_col+2, QtWidgets.QTableWidgetItem( 353 354 widget.setItem(i_row, i_col+2, QtWidgets.QTableWidgetItem( 245 355 GuiUtils.formatNumber(par_value, high=True))) 246 356 247 self.tblParams.resizeColumnsToContents() 357 # Add errors 358 error_list = list(error_columns.keys()) 359 for error_param in error_list[::-1]: # must be reverse to keep column indices 360 # the offset for the err column: +2 from the first two extra columns, +1 to append this column 361 error_column = error_columns[error_param][0]+3 362 error_values = error_columns[error_param][1:] 363 widget.insertColumn(error_column) 364 365 column_name = error_param + self.ERROR_COLUMN_CAPTION 366 widget.setHorizontalHeaderItem(error_column, QtWidgets.QTableWidgetItem(column_name)) 367 368 for i_row, error in enumerate(error_values): 369 item = QtWidgets.QTableWidgetItem(GuiUtils.formatNumber(error, high=True)) 370 # Fancy, italic font for errors 371 font = QtGui.QFont() 372 font.setItalic(True) 373 item.setFont(font) 374 widget.setItem(i_row, error_column, item) 375 376 # resize content 377 widget.resizeColumnsToContents() 248 378 249 379 @classmethod -
src/sas/qtgui/Utilities/GuiUtils.py
r27689dc rd4dac80 245 245 customModelDirectoryChanged = QtCore.pyqtSignal() 246 246 247 # Notify the gui manager about new data to be added to the grid view 248 sendDataToGridSignal = QtCore.pyqtSignal(list) 249 250 247 251 def updateModelItemWithPlot(item, update_data, name=""): 248 252 """ … … 333 337 """ 334 338 assert isinstance(item, QtGui.QStandardItem) 335 #assert isinstance(update_data, list)336 339 337 340 # Add the actual Data1D/Data2D object … … 465 468 466 469 return info_item 470 471 def dataFromItem(item): 472 """ 473 Retrieve Data1D/2D component from QStandardItem. 474 The assumption - data stored in SasView standard, in child 0 475 """ 476 return item.child(0).data() 467 477 468 478 def openLink(url): … … 813 823 return (xLabel, yLabel, xscale, yscale) 814 824 815 def dataFromItem(item):816 """817 Retrieve Data1D/2D component from QStandardItem.818 The assumption - data stored in SasView standard, in child 0819 """820 return item.child(0).data()821 822 825 def formatNumber(value, high=False): 823 826 """ -
src/sas/qtgui/Utilities/LocalConfig.py
r3b3b40b rd4dac80 134 134 135 135 # Default threading model 136 USING_TWISTED = False136 USING_TWISTED = True 137 137 138 138 # Logging levels to disable, if any -
src/sas/qtgui/Utilities/TabbedModelEditor.py
r93c79b5 rd4dac80 35 35 self.edit_only = edit_only 36 36 self.is_modified = False 37 self.label = None 37 38 38 39 self.addWidgets() … … 240 241 # Run the model test in sasmodels 241 242 try: 242 _ = GuiUtils.checkModel(full_path) 243 model_results = self.checkModel(full_path) 244 logging.info(model_results) 243 245 except Exception as ex: 244 246 msg = "Error building model: "+ str(ex) -
src/sas/qtgui/Utilities/UI/GridPanelUI.ui
r3b3b40b rd4dac80 8 8 <y>0</y> 9 9 <width>939</width> 10 <height>3 29</height>10 <height>330</height> 11 11 </rect> 12 12 </property> … … 21 21 </property> 22 22 <widget class="QWidget" name="centralwidget"> 23 <layout class="QGridLayout" name="gridLayout"> 23 <layout class="QGridLayout" name="gridLayout_2"> 24 <item row="0" column="0"> 25 <widget class="QTabWidget" name="tabWidget"> 26 <property name="tabPosition"> 27 <enum>QTabWidget::South</enum> 28 </property> 29 <property name="currentIndex"> 30 <number>0</number> 31 </property> 32 <widget class="QWidget" name="tab"> 33 <attribute name="title"> 34 <string>Tab 1</string> 35 </attribute> 36 <layout class="QGridLayout" name="gridLayout"> 37 <item row="0" column="0"> 38 <widget class="QTableWidget" name="tblParams"> 39 <property name="contextMenuPolicy"> 40 <enum>Qt::CustomContextMenu</enum> 41 </property> 42 <property name="alternatingRowColors"> 43 <bool>true</bool> 44 </property> 45 <property name="selectionBehavior"> 46 <enum>QAbstractItemView::SelectRows</enum> 47 </property> 48 </widget> 49 </item> 50 </layout> 51 </widget> 52 </widget> 53 </item> 24 54 <item row="1" column="0"> 25 55 <layout class="QHBoxLayout" name="horizontalLayout"> … … 36 66 </property> 37 67 </spacer> 68 </item> 69 <item> 70 <widget class="QPushButton" name="cmdPlot"> 71 <property name="text"> 72 <string>Plot</string> 73 </property> 74 </widget> 38 75 </item> 39 76 <item> … … 53 90 </layout> 54 91 </item> 55 <item row="0" column="0">56 <widget class="QTableWidget" name="tblParams">57 <property name="contextMenuPolicy">58 <enum>Qt::CustomContextMenu</enum>59 </property>60 <property name="alternatingRowColors">61 <bool>true</bool>62 </property>63 <property name="selectionBehavior">64 <enum>QAbstractItemView::SelectRows</enum>65 </property>66 </widget>67 </item>68 92 </layout> 69 93 </widget> … … 73 97 <x>0</x> 74 98 <y>0</y> 75 <width> 939</width>99 <width>510</width> 76 100 <height>26</height> 77 101 </rect> -
src/sas/qtgui/Utilities/UnitTesting/GridPanelTest.py
r3b3b40b rd4dac80 70 70 self.assertIsInstance(self.widget, QtWidgets.QMainWindow) 71 71 # Default title 72 self.assertEqual(self.widget.windowTitle(), " Grid Panel")72 self.assertEqual(self.widget.windowTitle(), "Batch Fitting Results") 73 73 74 74 # non-modal window
Note: See TracChangeset
for help on using the changeset viewer.