Changes in / [44c15fc:21e71f1] in sasview
- Location:
- src/sas
- Files:
-
- 20 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/qtgui/MainWindow/DataExplorer.py
r67346f9 re5ae812 238 238 Called when the "Open Project" menu item chosen. 239 239 """ 240 # check if any items loaded and warn about data deletion 241 if self.model.rowCount() > 0: 242 msg = "This operation will set remove all data, plots and analyses from" 243 msg += " SasView before loading the project. Do you wish to continue?" 244 msgbox = QtWidgets.QMessageBox(self) 245 msgbox.setIcon(QtWidgets.QMessageBox.Warning) 246 msgbox.setText(msg) 247 msgbox.setWindowTitle("Project Load") 248 # custom buttons 249 button_yes = QtWidgets.QPushButton("Yes") 250 msgbox.addButton(button_yes, QtWidgets.QMessageBox.YesRole) 251 button_no = QtWidgets.QPushButton("No") 252 msgbox.addButton(button_no, QtWidgets.QMessageBox.RejectRole) 253 retval = msgbox.exec_() 254 if retval == QtWidgets.QMessageBox.RejectRole: 255 # cancel fit 256 return 257 240 258 kwargs = { 241 259 'parent' : self, 242 260 'caption' : 'Open Project', 243 'filter' : 'Project (*.json);;All files (*.*)', 244 'options' : QtWidgets.QFileDialog.DontUseNativeDialog, 245 'directory' : self.default_project_location 261 'filter' : 'Project Files (*.json);;Old Project Files (*.svs);;All files (*.*)', 262 'options' : QtWidgets.QFileDialog.DontUseNativeDialog 246 263 } 247 264 filename = QtWidgets.QFileDialog.getOpenFileName(**kwargs)[0] 248 265 if filename: 249 266 self.default_project_location = os.path.dirname(filename) 250 load_thread = threads.deferToThread(self.readProject, filename) 251 load_thread.addCallback(self.readProjectComplete) 252 load_thread.addErrback(self.readProjectFailed) 253 254 def loadFailed(self, reason): 255 """ 256 """ 257 print("file load FAILED: ", reason) 258 pass 259 260 def readProjectFailed(self, reason): 261 """ 262 """ 263 print("readProjectFailed FAILED: ", reason) 264 pass 265 266 def readProject(self, filename): 267 self.communicator.statusBarUpdateSignal.emit("Loading Project... %s" % os.path.basename(filename)) 268 try: 269 manager = DataManager() 270 with open(filename, 'r') as infile: 271 manager.load_from_readable(infile) 272 273 self.communicator.statusBarUpdateSignal.emit("Loaded Project: %s" % os.path.basename(filename)) 274 return manager 275 276 except: 277 self.communicator.statusBarUpdateSignal.emit("Failed: %s" % os.path.basename(filename)) 278 raise 279 280 def readProjectComplete(self, manager): 281 self.model.clear() 282 283 self.manager.assign(manager) 284 self.model.beginResetModel() 285 for id, item in self.manager.get_all_data().items(): 286 self.updateModel(item.data, item.path) 287 288 self.model.endResetModel() 267 self.deleteAllItems() 268 self.readProject(filename) 269 270 def loadAnalysis(self): 271 """ 272 Called when the "Open Analysis" menu item chosen. 273 """ 274 kwargs = { 275 'parent' : self, 276 'caption' : 'Open Analysis', 277 'filter' : 'Project (*.fitv);;All files (*.*)', 278 'options' : QtWidgets.QFileDialog.DontUseNativeDialog 279 } 280 filename = QtWidgets.QFileDialog.getOpenFileName(**kwargs)[0] 281 if filename: 282 self.readProject(filename) 289 283 290 284 def saveProject(self): … … 301 295 name_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs) 302 296 filename = name_tuple[0] 303 if filename: 304 self.default_project_location = os.path.dirname(filename) 305 _, extension = os.path.splitext(filename) 306 if not extension: 307 filename = '.'.join((filename, 'json')) 308 self.communicator.statusBarUpdateSignal.emit("Saving Project... %s\n" % os.path.basename(filename)) 309 with open(filename, 'w') as outfile: 310 self.manager.save_to_writable(outfile) 297 if not filename: 298 return 299 self.default_project_location = os.path.dirname(filename) 300 _, extension = os.path.splitext(filename) 301 if not extension: 302 filename = '.'.join((filename, 'json')) 303 self.communicator.statusBarUpdateSignal.emit("Saving Project... %s\n" % os.path.basename(filename)) 304 305 return filename 306 307 def saveAsAnalysisFile(self, tab_id=1): 308 """ 309 Show the save as... dialog and return the chosen filepath 310 """ 311 default_name = "FitPage"+str(tab_id)+".fitv" 312 313 wildcard = "fitv files (*.fitv)" 314 kwargs = { 315 'caption' : 'Save As', 316 'directory' : default_name, 317 'filter' : wildcard, 318 'parent' : None, 319 } 320 # Query user for filename. 321 filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs) 322 filename = filename_tuple[0] 323 return filename 324 325 def saveAnalysis(self, data, tab_id=1): 326 """ 327 Called when the "Save Analysis" menu item chosen. 328 """ 329 filename = self.saveAsAnalysisFile(tab_id) 330 if not filename: 331 return 332 _, extension = os.path.splitext(filename) 333 if not extension: 334 filename = '.'.join((filename, 'fitv')) 335 self.communicator.statusBarUpdateSignal.emit("Saving analysis... %s\n" % os.path.basename(filename)) 336 337 with open(filename, 'w') as outfile: 338 GuiUtils.saveData(outfile, data) 339 340 self.communicator.statusBarUpdateSignal.emit('Analysis saved.') 341 342 def allDataForModel(self, model): 343 # data model 344 all_data = {} 345 for i in range(model.rowCount()): 346 properties = {} 347 item = model.item(i) 348 data = GuiUtils.dataFromItem(item) 349 if data is None: continue 350 # Now, all plots under this item 351 filename = data.filename 352 is_checked = item.checkState() 353 properties['checked'] = is_checked 354 other_datas = [] 355 # no need to save other_datas - things will be refit on read 356 #other_datas = GuiUtils.plotsFromFilename(filename, model) 357 # skip the main plot 358 #other_datas = list(other_datas.values())[1:] 359 all_data[data.id] = [data, properties, other_datas] 360 return all_data 361 362 def getDataForID(self, id): 363 # return the dataset with the given ID 364 all_data = [] 365 for model in (self.model, self.theory_model): 366 for i in range(model.rowCount()): 367 properties = {} 368 item = model.item(i) 369 data = GuiUtils.dataFromItem(item) 370 if data is None: continue 371 if data.id != id: continue 372 # We found the dataset - save it. 373 filename = data.filename 374 is_checked = item.checkState() 375 properties['checked'] = is_checked 376 other_datas = GuiUtils.plotsFromFilename(filename, model) 377 # skip the main plot 378 other_datas = list(other_datas.values())[1:] 379 all_data = [data, properties, other_datas] 380 break 381 return all_data 382 383 def getItemForID(self, id): 384 # return the model item with the given ID 385 item = None 386 for model in (self.model, self.theory_model): 387 for i in range(model.rowCount()): 388 properties = {} 389 item = model.item(i) 390 data = GuiUtils.dataFromItem(item) 391 if data is None: continue 392 if data.id != id: continue 393 # We found the item - return it 394 break 395 return item 396 397 def getAllData(self): 398 """ 399 converts all datasets into serializable dictionary 400 """ 401 data = self.allDataForModel(self.model) 402 theory = self.allDataForModel(self.theory_model) 403 404 all_data = {} 405 all_data['is_batch'] = str(self.chkBatch.isChecked()) 406 407 for key, value in data.items(): 408 all_data[key] = value 409 for key, value in theory.items(): 410 if key in all_data: 411 raise ValueError("Inconsistent data in Project file.") 412 all_data[key] = value 413 return all_data 414 415 def saveDataToFile(self, outfile): 416 """ 417 Save every dataset to a json file 418 """ 419 all_data = self.getAllData() 420 # save datas 421 GuiUtils.saveData(outfile, all_data) 422 423 def readProject(self, filename): 424 """ 425 Read out datasets and fitpages from file 426 """ 427 # Find out the filetype based on extension 428 ext = os.path.splitext(filename)[1] 429 all_data = {} 430 if 'svs' in ext.lower(): 431 # backward compatibility mode. 432 try: 433 datasets = GuiUtils.readProjectFromSVS(filename) 434 except Exception as ex: 435 # disregard malformed SVS and try to recover whatever 436 # is available 437 msg = "Error while reading the project file: "+str(ex) 438 logging.error(msg) 439 pass 440 # Convert fitpage properties and update the dict 441 try: 442 all_data = GuiUtils.convertFromSVS(datasets) 443 except Exception as ex: 444 # disregard malformed SVS and try to recover regardless 445 msg = "Error while converting the project file: "+str(ex) 446 logging.error(msg) 447 pass 448 else: 449 with open(filename, 'r') as infile: 450 try: 451 all_data = GuiUtils.readDataFromFile(infile) 452 except Exception as ex: 453 logging.error("Project load failed with " + str(ex)) 454 return 455 for key, value in all_data.items(): 456 if key=='is_batch': 457 self.chkBatch.setChecked(True if value=='True' else False) 458 continue 459 if 'cs_tab' in key: 460 continue 461 # send newly created items to the perspective 462 self.updatePerspectiveWithProperties(key, value) 463 464 # See if there are any batch pages defined and create them, if so 465 self.updateWithBatchPages(all_data) 466 467 # Only now can we create/assign C&S pages. 468 for key, value in all_data.items(): 469 if 'cs_tab' in key: 470 self.updatePerspectiveWithProperties(key, value) 471 472 def updateWithBatchPages(self, all_data): 473 """ 474 Checks all properties and see if there are any batch pages defined. 475 If so, pull out relevant indices and recreate the batch page(s) 476 """ 477 batch_pages = [] 478 for key, value in all_data.items(): 479 if 'fit_params' not in value: 480 continue 481 params = value['fit_params'] 482 for page in params: 483 if page['is_batch_fitting'][0] != 'True': 484 continue 485 batch_ids = page['data_id'][0] 486 # check for duplicates 487 batch_set = set(batch_ids) 488 if batch_set in batch_pages: 489 continue 490 # Found a unique batch page. Send it away 491 items = [self.getItemForID(i) for i in batch_set] 492 # Update the batch page list 493 batch_pages.append(batch_set) 494 # Assign parameters to the most recent (current) page. 495 self._perspective().setData(data_item=items, is_batch=True) 496 self._perspective().updateFromParameters(page) 497 pass 498 499 def updatePerspectiveWithProperties(self, key, value): 500 """ 501 """ 502 if 'fit_data' in value: 503 data_dict = {key:value['fit_data']} 504 # Create new model items in the data explorer 505 items = self.updateModelFromData(data_dict) 506 507 if 'fit_params' in value: 508 params = value['fit_params'] 509 # Make the perspective read the rest of the read data 510 if not isinstance(params, list): 511 params = [params] 512 for page in params: 513 # Check if this set of parameters is for a batch page 514 # if so, skip the update 515 if page['is_batch_fitting'][0] == 'True': 516 continue 517 # Send current model item to the perspective 518 self.sendItemToPerspective(items[0]) 519 # Assign parameters to the most recent (current) page. 520 self._perspective().updateFromParameters(page) 521 if 'cs_tab' in key and 'is_constraint' in value: 522 # Create a C&S page 523 self._perspective().addConstraintTab() 524 # Modify the tab 525 self._perspective().updateFromParameters(value) 526 527 pass # debugger 528 529 def updateModelFromData(self, data): 530 """ 531 Given data from analysis/project file, 532 create indices and populate data/theory models 533 """ 534 # model items for top level datasets 535 items = [] 536 for key, value in data.items(): 537 # key - cardinal number of dataset 538 # value - main dataset, [dependant filesets] 539 # add the main index 540 if not value: continue 541 #if key=='is_batch': 542 # self.chkBatch.setChecked(True if value=='True' else False) 543 # continue 544 new_data = value[0] 545 from sas.sascalc.dataloader.data_info import Data1D as old_data1d 546 from sas.sascalc.dataloader.data_info import Data2D as old_data2d 547 if isinstance(new_data, (old_data1d, old_data2d)): 548 new_data = self.manager.create_gui_data(value[0], new_data.filename) 549 assert isinstance(new_data, (Data1D, Data2D)) 550 properties = value[1] 551 is_checked = properties['checked'] 552 new_item = GuiUtils.createModelItemWithPlot(new_data, new_data.filename) 553 new_item.setCheckState(is_checked) 554 items.append(new_item) 555 model = self.theory_model 556 if new_data.is_data: 557 model = self.model 558 # Caption for the theories 559 new_item.setChild(2, QtGui.QStandardItem("FIT RESULTS")) 560 561 model.appendRow(new_item) 562 self.manager.add_data(data_list={new_data.id:new_data}) 563 564 # Add the underlying data 565 if not value[2]: 566 continue 567 for plot in value[2]: 568 assert isinstance(plot, (Data1D, Data2D)) 569 GuiUtils.updateModelItemWithPlot(new_item, plot, plot.name) 570 return items 311 571 312 572 def deleteFile(self, event): … … 424 684 retval = msgbox.exec_() 425 685 686 def sendItemToPerspective(self, item): 687 """ 688 Send the passed item data to the current perspective and set the relevant notifiers 689 """ 690 # Set the signal handlers 691 self.communicator.updateModelFromPerspectiveSignal.connect(self.updateModelFromPerspective) 692 selected_items = [item] 693 # Notify the GuiManager about the send request 694 try: 695 self._perspective().setData(data_item=selected_items, is_batch=False) 696 except Exception as ex: 697 msg = "%s perspective returned the following message: \n%s\n" %(self._perspective().name, str(ex)) 698 logging.error(msg) 699 msg = str(ex) 700 msgbox = QtWidgets.QMessageBox() 701 msgbox.setIcon(QtWidgets.QMessageBox.Critical) 702 msgbox.setText(msg) 703 msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok) 704 retval = msgbox.exec_() 426 705 427 706 def freezeCheckedData(self): … … 1059 1338 self.actionQuick3DPlot.triggered.connect(self.quickData3DPlot) 1060 1339 self.actionEditMask.triggered.connect(self.showEditDataMask) 1061 self.actionDelete.triggered.connect(self.delete Item)1340 self.actionDelete.triggered.connect(self.deleteSelectedItem) 1062 1341 self.actionFreezeResults.triggered.connect(self.freezeSelectedItems) 1063 1342 … … 1271 1550 self.freezeItem(item_to_copy) 1272 1551 1273 def deleteItem(self): 1552 def deleteAllItems(self): 1553 """ 1554 Deletes all datasets from both model and theory_model 1555 """ 1556 deleted_items = [self.model.item(row) for row in range(self.model.rowCount()) 1557 if self.model.item(row).isCheckable()] 1558 deleted_names = [item.text() for item in deleted_items] 1559 # Let others know we deleted data 1560 self.communicator.dataDeletedSignal.emit(deleted_items) 1561 # update stored_data 1562 self.manager.update_stored_data(deleted_names) 1563 1564 # Clear the model 1565 self.model.clear() 1566 1567 def deleteSelectedItem(self): 1274 1568 """ 1275 1569 Delete the current item … … 1288 1582 return 1289 1583 1584 indices = self.current_view.selectedIndexes() 1585 self.deleteIndices(indices) 1586 1587 def deleteIndices(self, indices): 1588 """ 1589 Delete model idices from the current view 1590 """ 1591 proxy = self.current_view.model() 1592 model = proxy.sourceModel() 1593 1594 deleted_items = [] 1595 deleted_names = [] 1596 1290 1597 # Every time a row is removed, the indices change, so we'll just remove 1291 1598 # rows and keep calling selectedIndexes until it returns an empty list. 1292 indices = self.current_view.selectedIndexes()1293 1294 proxy = self.current_view.model()1295 model = proxy.sourceModel()1296 1297 deleted_items = []1298 deleted_names = []1299 1300 1599 while len(indices) > 0: 1301 1600 index = indices[0] 1302 row_index = proxy.mapToSource(index) 1303 item_to_delete = model.itemFromIndex(row_index) 1601 #row_index = proxy.mapToSource(index) 1602 #item_to_delete = model.itemFromIndex(row_index) 1603 item_to_delete = model.itemFromIndex(index) 1304 1604 if item_to_delete and item_to_delete.isCheckable(): 1305 row = row_index.row() 1605 #row = row_index.row() 1606 row = index.row() 1306 1607 1307 1608 # store the deleted item details so we can pass them on later … … 1430 1731 checkbox_item.setCheckable(True) 1431 1732 checkbox_item.setCheckState(QtCore.Qt.Checked) 1432 checkbox_item.setText(os.path.basename(p_file)) 1733 if p_file is not None: 1734 checkbox_item.setText(os.path.basename(p_file)) 1433 1735 1434 1736 # Add the actual Data1D/Data2D object -
src/sas/qtgui/MainWindow/DataManager.py
re2e5f3d r345b3b3 29 29 from sas.qtgui.Plotting.PlotterData import Data1D 30 30 from sas.qtgui.Plotting.PlotterData import Data2D 31 from sas.qtgui.Plotting.Plottables import Plottable32 31 from sas.qtgui.Plotting.Plottables import PlottableTheory1D 33 32 from sas.qtgui.Plotting.Plottables import PlottableFit1D -
src/sas/qtgui/MainWindow/GuiManager.py
r67346f9 re5ae812 255 255 if self._current_perspective: 256 256 self._current_perspective.setClosable() 257 #self._workspace.workspace.removeSubWindow(self._current_perspective)258 257 self._current_perspective.close() 259 258 self._workspace.workspace.removeSubWindow(self._current_perspective) … … 471 470 self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project) 472 471 self._workspace.actionOpen_Analysis.triggered.connect(self.actionOpen_Analysis) 473 self._workspace.actionSave.triggered.connect(self.actionSave )472 self._workspace.actionSave.triggered.connect(self.actionSave_Project) 474 473 self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis) 475 474 self._workspace.actionQuit.triggered.connect(self.actionQuit) … … 561 560 """ 562 561 """ 563 print("actionOpen_Analysis TRIGGERED")564 pass 565 566 def actionSave (self):562 self.filesWidget.loadAnalysis() 563 pass 564 565 def actionSave_Project(self): 567 566 """ 568 567 Menu Save Project 569 568 """ 570 self.filesWidget.saveProject() 569 filename = self.filesWidget.saveProject() 570 571 # datasets 572 all_data = self.filesWidget.getAllData() 573 574 # fit tabs 575 params={} 576 perspective = self.perspective() 577 if hasattr(perspective, 'isSerializable') and perspective.isSerializable(): 578 params = perspective.serializeAllFitpage() 579 580 # project dictionary structure: 581 # analysis[data.id] = [{"fit_data":[data, checkbox, child data], 582 # "fit_params":[fitpage_state]} 583 # "fit_params" not present if dataset not sent to fitting 584 analysis = {} 585 586 for id, data in all_data.items(): 587 if id=='is_batch': 588 analysis['is_batch'] = data 589 continue 590 data_content = {"fit_data":data} 591 if id in params.keys(): 592 # this dataset is represented also by the fit tab. Add to it. 593 data_content["fit_params"] = params[id] 594 analysis[id] = data_content 595 596 # standalone constraint pages 597 for keys, values in params.items(): 598 if not 'is_constraint' in values[0]: 599 continue 600 analysis[keys] = values[0] 601 602 with open(filename, 'w') as outfile: 603 GuiUtils.saveData(outfile, analysis) 571 604 572 605 def actionSave_Analysis(self): … … 574 607 Menu File/Save Analysis 575 608 """ 576 self.communicate.saveAnalysisSignal.emit() 609 per = self.perspective() 610 if not isinstance(per, FittingWindow): 611 return 612 # get fit page serialization 613 params = per.serializeCurrentFitpage() 614 # Find dataset ids for the current tab 615 # (can be multiple, if batch) 616 data_id = per.currentTabDataId() 617 tab_id = per.currentTab.tab_id 618 analysis = {} 619 for id in data_id: 620 an = {} 621 data_for_id = self.filesWidget.getDataForID(id) 622 an['fit_data'] = data_for_id 623 an['fit_params'] = [params] 624 analysis[id] = an 625 626 self.filesWidget.saveAnalysis(analysis, tab_id) 577 627 578 628 def actionQuit(self): … … 1056 1106 """ 1057 1107 self._workspace.actionReport.setEnabled(False) 1108 self._workspace.actionOpen_Analysis.setEnabled(False) 1109 self._workspace.actionSave_Analysis.setEnabled(False) 1110 if hasattr(perspective, 'isSerializable') and perspective.isSerializable(): 1111 self._workspace.actionOpen_Analysis.setEnabled(True) 1112 self._workspace.actionSave_Analysis.setEnabled(True) 1113 1058 1114 if isinstance(perspective, Perspectives.PERSPECTIVES["Fitting"]): 1059 1115 self.checkAnalysisOption(self._workspace.actionFitting) … … 1127 1183 out_f.write("#Application appearance custom configuration\n") 1128 1184 for key, item in config.__dict__.items(): 1129 if key[:2] != "__": 1130 if isinstance(item, str): 1131 item = '"' + item + '"' 1132 out_f.write("%s = %s\n" % (key, str(item))) 1185 if key[:2] == "__": 1186 continue 1187 if isinstance(item, str): 1188 item = '"' + item + '"' 1189 out_f.write("%s = %s\n" % (key, str(item))) 1133 1190 pass # debugger anchor -
src/sas/qtgui/MainWindow/UI/AcknowledgementsUI.ui
r7385fec recc5d043 10 10 <x>0</x> 11 11 <y>0</y> 12 <width> 508</width>13 <height> 283</height>12 <width>468</width> 13 <height>316</height> 14 14 </rect> 15 15 </property> … … 27 27 <widget class="QLabel" name="label"> 28 28 <property name="sizePolicy"> 29 <sizepolicy hsizetype="Preferred" vsizetype="Minimum ">29 <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> 30 30 <horstretch>0</horstretch> 31 31 <verstretch>0</verstretch> … … 35 35 <size> 36 36 <width>16777215</width> 37 <height> 41</height>37 <height>50</height> 38 38 </size> 39 39 </property> … … 46 46 <widget class="QTextBrowser" name="textBrowser"> 47 47 <property name="sizePolicy"> 48 <sizepolicy hsizetype="Expanding" vsizetype="Minimum ">48 <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding"> 49 49 <horstretch>0</horstretch> 50 50 <verstretch>0</verstretch> 51 51 </sizepolicy> 52 52 </property> 53 <property name="minimumSize"> 54 <size> 55 <width>0</width> 56 <height>85</height> 57 </size> 58 </property> 53 59 <property name="maximumSize"> 54 60 <size> 55 61 <width>16777215</width> 56 <height> 61</height>62 <height>85</height> 57 63 </size> 58 64 </property> … … 61 67 <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 62 68 p, li { white-space: pre-wrap; } 63 </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size: 8.25pt; font-weight:400; font-style:normal;">64 <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> This work benefited from the use of the SasView application, originally developed under NSF Award DMR-0520547. SasView also contains code developed with funding from the EU Horizon 2020 programme under the SINE2020 project Grant No 654000.</p></body></html></string>69 </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> 70 <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8.25pt;">This work benefited from the use of the SasView application, originally developed under NSF Award DMR-0520547. SasView also contains code developed with funding from the EU Horizon 2020 programme under the SINE2020 project Grant No 654000.</span></p></body></html></string> 65 71 </property> 66 72 </widget> … … 103 109 <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 104 110 p, li { white-space: pre-wrap; } 105 </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size: 8.25pt; font-weight:400; font-style:normal;">106 <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> M. Doucet et al. SasView Version 4.2, Zenodo, 10.5281/zenodo.1412041</p></body></html></string>111 </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> 112 <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8.25pt;">M. Doucet et al. SasView Version 4.2, Zenodo, 10.5281/zenodo.1412041</span></p></body></html></string> 107 113 </property> 108 114 </widget> … … 111 117 <widget class="QLabel" name="label_3"> 112 118 <property name="sizePolicy"> 113 <sizepolicy hsizetype="Preferred" vsizetype="Minimum ">119 <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> 114 120 <horstretch>0</horstretch> 115 121 <verstretch>0</verstretch> … … 119 125 <size> 120 126 <width>16777215</width> 121 <height> 38</height>127 <height>50</height> 122 128 </size> 123 129 </property> -
src/sas/qtgui/MainWindow/UnitTesting/DataExplorerTest.py
ra24eacf rb1b71ad 854 854 855 855 # Attempt at deleting 856 self.form.delete Item()856 self.form.deleteSelectedItem() 857 857 858 858 # Test the warning dialog called once … … 868 868 self.form.current_view.selectionModel().select(select_index, QtCore.QItemSelectionModel.Rows) 869 869 # delete it. now for good 870 self.form.delete Item()870 self.form.deleteSelectedItem() 871 871 872 872 # Test the warning dialog called once -
src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py
r305114c rd72ac57 15 15 from sas.qtgui.Perspectives.Fitting import FittingUtilities 16 16 import sas.qtgui.Utilities.GuiUtils as GuiUtils 17 ALLOWED_OPERATORS = ['=','<','>','>=','<='] 17 from sas.qtgui.Perspectives.Fitting.Constraint import Constraint 18 19 #ALLOWED_OPERATORS = ['=','<','>','>=','<='] 20 ALLOWED_OPERATORS = ['='] 18 21 19 22 # Local UI … … 21 24 22 25 class ComplexConstraint(QtWidgets.QDialog, Ui_ComplexConstraintUI): 26 constraintReadySignal = QtCore.pyqtSignal(tuple) 23 27 def __init__(self, parent=None, tabs=None): 24 28 super(ComplexConstraint, self).__init__() … … 32 36 self.tab_names = None 33 37 self.operator = '=' 38 self._constraint = Constraint() 34 39 35 40 self.warning = self.lblWarning.text() … … 53 58 Signals from various elements 54 59 """ 55 self.cmdOK.clicked.connect(self. accept)60 self.cmdOK.clicked.connect(self.onApply) 56 61 self.cmdHelp.clicked.connect(self.onHelp) 57 62 self.cmdRevert.clicked.connect(self.onRevert) … … 69 74 self.txtName2.setText(self.tab_names[1]) 70 75 71 # Show only parameters not already constrained 76 self.setupParamWidgets() 77 78 # Add menu to the Apply button 79 all_menu = QtWidgets.QMenu() 80 self.actionAddAll = QtWidgets.QAction(self) 81 self.actionAddAll.setObjectName("actionAddAll") 82 self.actionAddAll.setText(QtCore.QCoreApplication.translate("self", "Add all")) 83 ttip = "Add constraints between all identically named parameters in both fitpages" 84 self.actionAddAll.setToolTip(ttip) 85 self.actionAddAll.triggered.connect(self.onSetAll) 86 all_menu.addAction(self.actionAddAll) 87 # https://bugreports.qt.io/browse/QTBUG-13663 88 all_menu.setToolTipsVisible(True) 89 self.cmdOK.setMenu(all_menu) 90 91 def setupParamWidgets(self): 92 """ 93 Fill out comboboxes and set labels with non-constrained parameters 94 """ 72 95 self.cbParam1.clear() 73 items = [param for i,param in enumerate(self.params[0]) if not self.tabs[0].rowHasConstraint(i)] 74 self.cbParam1.addItems(items) 96 items1 = [param for param in self.params[0] if not self.tabs[0].paramHasConstraint(param)] 97 self.cbParam1.addItems(items1) 98 99 # M2 doesn't have to be non-constrained 75 100 self.cbParam2.clear() 76 items = [param for i,param in enumerate(self.params[1]) if not self.tabs[1].rowHasConstraint(i)] 77 self.cbParam2.addItems(items) 101 #items2 = [param for param in self.params[1] if not self.tabs[1].paramHasConstraint(param)] 102 items2 = [param for param in self.params[1]] 103 self.cbParam2.addItems(items2) 78 104 79 105 self.txtParam.setText(self.tab_names[0] + ":" + self.cbParam1.currentText()) … … 84 110 85 111 self.txtConstraint.setText(self.tab_names[1]+"."+self.cbParam2.currentText()) 112 113 # disable Apply if no parameters available 114 if len(items1)==0: 115 self.cmdOK.setEnabled(False) 116 txt = "No parameters in model "+self.tab_names[0] +\ 117 " are available for constraining." 118 self.lblWarning.setText(txt) 119 else: 120 self.cmdOK.setEnabled(True) 121 txt = "" 122 self.lblWarning.setText(txt) 86 123 87 124 def setupTooltip(self): … … 144 181 145 182 # Original indices 183 index2 = index2 if index2 >= 0 else 0 184 index1 = index1 if index1 >= 0 else 0 146 185 self.cbParam1.setCurrentIndex(index2) 147 186 self.cbParam2.setCurrentIndex(index1) … … 205 244 def constraint(self): 206 245 """ 207 Return the generated constraint as tuple (model1, param1, operator, constraint) 208 """ 209 return (self.txtName1.text(), self.cbParam1.currentText(), self.cbOperator.currentText(), self.txtConstraint.text()) 246 Return the generated constraint 247 """ 248 param = self.cbParam1.currentText() 249 value = self.cbParam2.currentText() 250 func = self.txtConstraint.text() 251 value_ex = self.txtName2.text() + "." + self.cbParam2.currentText() 252 model1 = self.txtName1.text() 253 operator = self.cbOperator.currentText() 254 255 con = Constraint(self, 256 param=param, 257 value=value, 258 func=func, 259 value_ex=value_ex, 260 operator=operator) 261 262 return (model1, con) 263 264 def onApply(self): 265 """ 266 Respond to Add constraint action. 267 Send a signal that the constraint is ready to be applied 268 """ 269 cons_tuple = self.constraint() 270 self.constraintReadySignal.emit(cons_tuple) 271 # reload the comboboxes 272 self.setupParamWidgets() 273 274 def onSetAll(self): 275 """ 276 Set constraints on all identically named parameters between two fitpages 277 """ 278 # loop over parameters in constrained model 279 items1 = [param for param in self.params[0] if not self.tabs[0].paramHasConstraint(param)] 280 #items2 = [param for param in self.params[1] if not self.tabs[1].paramHasConstraint(i)] 281 items2 = self.params[1] 282 for item in items1: 283 if item not in items2: continue 284 param = item 285 value = item 286 func = self.txtName2.text() + "." + param 287 value_ex = self.txtName1.text() + "." + param 288 model1 = self.txtName1.text() 289 operator = self.cbOperator.currentText() 290 291 con = Constraint(self, 292 param=param, 293 value=value, 294 func=func, 295 value_ex=value_ex, 296 operator=operator) 297 298 self.constraintReadySignal.emit((model1, con)) 299 300 # reload the comboboxes 301 self.setupParamWidgets() 210 302 211 303 def onHelp(self): -
src/sas/qtgui/Perspectives/Fitting/Constraint.py
r14ec91c5 r09e0c32 5 5 hence made into a class. 6 6 """ 7 def __init__(self, parent=None, param=None, value=0.0, min=None, max=None, func=None): 7 def __init__(self, parent=None, param=None, value=0.0, 8 min=None, max=None, func=None, value_ex=None, 9 operator="="): 8 10 self._value = value 9 11 self._param = param 12 self._value_ex = value_ex 10 13 self._func = func 11 self.active = True12 14 self._min = min 13 15 self._max = max 16 self._operator = operator 17 self.validate = True 18 self.active = True 14 19 15 20 @property 16 21 def value(self): 22 # value/parameter to fit to (e.g. 1.0 or sld) 17 23 return self._value 18 24 … … 22 28 23 29 @property 30 def value_ex(self): 31 # full parameter name to fit to (e.g. M1.sld) 32 return self._value_ex 33 34 @value_ex.setter 35 def value_ex(self, val): 36 self._value_ex = val 37 38 @property 24 39 def param(self): 40 # parameter which is being fitted 25 41 return self._param 26 42 … … 31 47 @property 32 48 def func(self): 49 # Function to be used for constraint 50 # e.g. sqrt(M1.sld+1.0) 33 51 return self._func 34 52 … … 39 57 @property 40 58 def min(self): 59 # min param value for single value constraints 41 60 return self._min 42 61 … … 47 66 @property 48 67 def max(self): 68 # max param value for single value constraints 49 69 return self._max 50 70 … … 53 73 self._max = val 54 74 75 @property 76 def operator(self): 77 # operator to use for constraint 78 return self._operator 79 80 @operator.setter 81 def operator(self, val): 82 self._operator = val 83 -
src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py
rc4c4957 r442a9ae 23 23 Constraints Dialog to select the desired parameter/model constraints. 24 24 """ 25 fitCompleteSignal = QtCore.pyqtSignal(tuple) 26 batchCompleteSignal = QtCore.pyqtSignal(tuple) 27 fitFailedSignal = QtCore.pyqtSignal(tuple) 25 28 26 29 def __init__(self, parent=None): … … 32 35 # To keep with previous SasView values, use 300 as the start offset 33 36 self.page_id = 301 37 self.tab_id = self.page_id 34 38 35 39 # Are we chain fitting? … … 60 64 Set up various widget states 61 65 """ 66 # disable special cases until properly defined 67 self.label.setVisible(False) 68 self.cbCases.setVisible(False) 69 62 70 labels = ['FitPage', 'Model', 'Data', 'Mnemonic'] 63 71 # tab widget - headers … … 79 87 self.tblConstraints.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) 80 88 self.tblConstraints.setEnabled(False) 89 header = self.tblConstraints.horizontalHeaderItem(0) 90 header.setToolTip("Double click a row below to edit the constraint.") 81 91 82 92 self.tblConstraints.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) … … 93 103 self.cmdFit.clicked.connect(self.onFit) 94 104 self.cmdHelp.clicked.connect(self.onHelp) 105 self.cmdAdd.clicked.connect(self.showMultiConstraint) 95 106 self.chkChain.toggled.connect(self.onChainFit) 96 107 … … 100 111 self.tblConstraints.cellChanged.connect(self.onConstraintChange) 101 112 113 # Internal signals 114 self.fitCompleteSignal.connect(self.fitComplete) 115 self.batchCompleteSignal.connect(self.batchComplete) 116 self.fitFailedSignal.connect(self.fitFailed) 117 102 118 # External signals 103 119 self.parent.tabsModifiedSignal.connect(self.initializeFitList) … … 156 172 fitter = Fit() 157 173 fitter.fitter_id = self.page_id 158 159 # Notify the parent about fitting started160 self.parent.fittingStartedSignal.emit(tabs_to_fit)161 174 162 175 # prepare fitting problems for each tab … … 168 181 try: 169 182 for tab in tabs_to_fit: 183 if not self.isTabImportable(tab): continue 170 184 tab_object = ObjectLibrary.getObject(tab) 171 185 if tab_object is None: … … 177 191 except ValueError: 178 192 # No parameters selected in one of the tabs 179 no_params_msg = "Fitting can 193 no_params_msg = "Fitting cannot be performed.\n" +\ 180 194 "Not all tabs chosen for fitting have parameters selected for fitting." 181 195 QtWidgets.QMessageBox.warning(self, … … 200 214 batch_inputs = {} 201 215 batch_outputs = {} 216 217 # Notify the parent about fitting started 218 self.parent.fittingStartedSignal.emit(tabs_to_fit) 202 219 203 220 # new fit thread object … … 223 240 224 241 #disable the Fit button 242 self.cmdFit.setStyleSheet('QPushButton {color: red;}') 225 243 self.cmdFit.setText('Running...') 226 244 self.parent.communicate.statusBarUpdateSignal.emit('Fitting started...') … … 292 310 """ 293 311 item = self.tblConstraints.item(row, column) 294 if column == 0: 295 # Update the tabs for fitting list 296 constraint = self.available_constraints[row] 297 constraint.active = (item.checkState() == QtCore.Qt.Checked) 312 if column != 0: return 313 # Update the tabs for fitting list 314 constraint = self.available_constraints[row] 315 constraint.active = (item.checkState() == QtCore.Qt.Checked) 316 # Update the constraint formula 317 constraint = self.available_constraints[row] 318 function = item.text() 319 # remove anything left of '=' to get the constraint 320 function = function[function.index('=')+1:] 321 # No check on function here - trust the user (R) 322 if function != constraint.func: 323 # This becomes rather difficult to validate now. 324 # Turn off validation for Edit Constraint 325 constraint.func = function 326 constraint.validate = False 298 327 299 328 def onTabCellEntered(self, row, column): … … 308 337 def onFitComplete(self, result): 309 338 """ 339 Send the fit complete signal to main thread 340 """ 341 self.fitCompleteSignal.emit(result) 342 343 def fitComplete(self, result): 344 """ 310 345 Respond to the successful fit complete signal 311 346 """ 312 347 #re-enable the Fit button 348 self.cmdFit.setStyleSheet('QPushButton {color: black;}') 313 349 self.cmdFit.setText("Fit") 314 350 self.cmdFit.setEnabled(True) … … 347 383 def onBatchFitComplete(self, result): 348 384 """ 385 Send the fit complete signal to main thread 386 """ 387 self.batchCompleteSignal.emit(result) 388 389 def batchComplete(self, result): 390 """ 349 391 Respond to the successful batch fit complete signal 350 392 """ 351 393 #re-enable the Fit button 394 self.cmdFit.setStyleSheet('QPushButton {color: black;}') 352 395 self.cmdFit.setText("Fit") 353 396 self.cmdFit.setEnabled(True) … … 375 418 def onFitFailed(self, reason): 376 419 """ 420 Send the fit failed signal to main thread 421 """ 422 self.fitFailedSignal.emit(result) 423 424 def fitFailed(self, reason): 425 """ 377 426 Respond to fitting failure. 378 427 """ 379 428 #re-enable the Fit button 429 self.cmdFit.setStyleSheet('QPushButton {color: black;}') 380 430 self.cmdFit.setText("Fit") 381 431 self.cmdFit.setEnabled(True) … … 386 436 msg = "Fitting failed: %s s.\n" % reason 387 437 self.parent.communicate.statusBarUpdateSignal.emit(msg) 388 438 389 439 def isTabImportable(self, tab): 390 440 """ … … 599 649 # Show the text in the constraint table 600 650 item = self.uneditableItem(label) 651 item = QtWidgets.QTableWidgetItem(label) 601 652 item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable) 602 653 item.setCheckState(QtCore.Qt.Checked) … … 667 718 return None 668 719 720 def onAcceptConstraint(self, con_tuple): 721 """ 722 Receive constraint tuple from the ComplexConstraint dialog and adds contraint 723 """ 724 #"M1, M2, M3" etc 725 model_name, constraint = con_tuple 726 constrained_tab = self.getObjectByName(model_name) 727 if constrained_tab is None: 728 return 729 730 # Find the constrained parameter row 731 constrained_row = constrained_tab.getRowFromName(constraint.param) 732 733 # Update the tab 734 constrained_tab.addConstraintToRow(constraint, constrained_row) 735 736 # Select this parameter for adjusting/fitting 737 constrained_tab.selectCheckbox(constrained_row) 738 739 669 740 def showMultiConstraint(self): 670 741 """ … … 672 743 """ 673 744 selected_rows = self.selectedParameters(self.tblTabList) 674 assert(len(selected_rows)==2) 745 if len(selected_rows)!=2: 746 msg = "Please select two fit pages from the Source Choice table." 747 msgbox = QtWidgets.QMessageBox(self.parent) 748 msgbox.setIcon(QtWidgets.QMessageBox.Warning) 749 msgbox.setText(msg) 750 msgbox.setWindowTitle("2 fit page constraints") 751 retval = msgbox.exec_() 752 return 675 753 676 754 tab_list = [ObjectLibrary.getObject(self.tblTabList.item(s, 0).data(0)) for s in selected_rows] 677 755 # Create and display the widget for param1 and param2 678 756 cc_widget = ComplexConstraint(self, tabs=tab_list) 757 cc_widget.constraintReadySignal.connect(self.onAcceptConstraint) 758 679 759 if cc_widget.exec_() != QtWidgets.QDialog.Accepted: 680 760 return 681 761 682 constraint = Constraint() 683 model1, param1, operator, constraint_text = cc_widget.constraint() 684 685 constraint.func = constraint_text 686 # param1 is the parameter we're constraining 687 constraint.param = param1 688 689 # Find the right tab 690 constrained_tab = self.getObjectByName(model1) 691 if constrained_tab is None: 692 return 693 694 # Find the constrained parameter row 695 constrained_row = constrained_tab.getRowFromName(param1) 696 697 # Update the tab 698 constrained_tab.addConstraintToRow(constraint, constrained_row) 762 def getFitPage(self): 763 """ 764 Retrieves the state of this page 765 """ 766 param_list = [] 767 768 param_list.append(['is_constraint', 'True']) 769 param_list.append(['data_id', "cs_tab"+str(self.page_id)]) 770 param_list.append(['current_type', self.currentType]) 771 param_list.append(['is_chain_fitting', str(self.is_chain_fitting)]) 772 param_list.append(['special_case', self.cbCases.currentText()]) 773 774 return param_list 775 776 def getFitModel(self): 777 """ 778 Retrieves current model 779 """ 780 model_list = [] 781 782 checked_models = {} 783 for row in range(self.tblTabList.rowCount()): 784 model_name = self.tblTabList.item(row,1).data(0) 785 active = self.tblTabList.item(row,0).checkState()# == QtCore.Qt.Checked 786 checked_models[model_name] = str(active) 787 788 checked_constraints = {} 789 for row in range(self.tblConstraints.rowCount()): 790 model_name = self.tblConstraints.item(row,0).data(0) 791 active = self.tblConstraints.item(row,0).checkState()# == QtCore.Qt.Checked 792 checked_constraints[model_name] = str(active) 793 794 model_list.append(['checked_models', checked_models]) 795 model_list.append(['checked_constraints', checked_constraints]) 796 return model_list 797 798 def createPageForParameters(self, parameters=None): 799 """ 800 Update the page with passed parameter values 801 """ 802 # checked models 803 if not 'checked_models' in parameters: 804 return 805 models = parameters['checked_models'][0] 806 for model, check_state in models.items(): 807 for row in range(self.tblTabList.rowCount()): 808 model_name = self.tblTabList.item(row,1).data(0) 809 if model_name != model: 810 continue 811 # check/uncheck item 812 self.tblTabList.item(row,0).setCheckState(int(check_state)) 813 814 if not 'checked_constraints' in parameters: 815 return 816 # checked constraints 817 models = parameters['checked_constraints'][0] 818 for model, check_state in models.items(): 819 for row in range(self.tblConstraints.rowCount()): 820 model_name = self.tblConstraints.item(row,0).data(0) 821 if model_name != model: 822 continue 823 # check/uncheck item 824 self.tblConstraints.item(row,0).setCheckState(int(check_state)) 825 826 # fit/batch radio 827 isBatch = parameters['current_type'][0] == 'BatchPage' 828 if isBatch: 829 self.btnBatch.toggle() 830 831 # chain 832 is_chain = parameters['is_chain_fitting'][0] == 'True' 833 if isBatch: 834 self.chkChain.setChecked(is_chain) -
src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py
rd2007a8 rfb39f28 1 1 import numpy 2 import copy 2 3 3 4 from PyQt5 import QtCore … … 10 11 import sas.qtgui.Utilities.LocalConfig as LocalConfig 11 12 import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary 13 import sas.qtgui.Utilities.GuiUtils as GuiUtils 12 14 13 15 from sas.qtgui.Perspectives.Fitting.FittingWidget import FittingWidget … … 37 39 self.maxIndex = 1 38 40 39 ## Index of the current tab40 #self.currentTab = 041 42 41 # The default optimizer 43 42 self.optimizer = 'Levenberg-Marquardt' … … 85 84 self.updateWindowTitle() 86 85 86 # Add new tab mini-button 87 self.plusButton = QtWidgets.QToolButton(self) 88 self.plusButton.setText("+") 89 self.setCornerWidget(self.plusButton) 90 self.plusButton.setToolTip("Add a new Fit Page") 91 self.plusButton.clicked.connect(lambda: self.addFit(None)) 92 87 93 def updateWindowTitle(self): 88 94 """ … … 112 118 def onLatexCopy(self): 113 119 self.currentTab.onCopyToClipboard("Latex") 120 121 def serializeAllFitpage(self): 122 # serialize all active fitpages and return 123 # a dictionary: {data_id: fitpage_state} 124 params = {} 125 for i, tab in enumerate(self.tabs): 126 tab_data = self.getSerializedFitpage(tab) 127 if 'data_id' not in tab_data: continue 128 id = tab_data['data_id'][0] 129 if isinstance(id, list): 130 for i in id: 131 if i in params: 132 params[i].append(tab_data) 133 else: 134 params[i] = [tab_data] 135 else: 136 if id in params: 137 params[id].append(tab_data) 138 else: 139 params[id] = [tab_data] 140 return params 141 142 def serializeCurrentFitpage(self): 143 # serialize current(active) fitpage 144 return self.getSerializedFitpage(self.currentTab) 145 146 def getSerializedFitpage(self, tab): 147 """ 148 get serialize requested fit tab 149 """ 150 fitpage_state = tab.getFitPage() 151 fitpage_state += tab.getFitModel() 152 # put the text into dictionary 153 line_dict = {} 154 for line in fitpage_state: 155 #content = line.split(',') 156 if len(line) > 1: 157 line_dict[line[0]] = line[1:] 158 return line_dict 159 160 def currentTabDataId(self): 161 """ 162 Returns the data ID of the current tab 163 """ 164 tab_id = [] 165 if not self.currentTab.data: 166 return tab_id 167 for item in self.currentTab.all_data: 168 data = GuiUtils.dataFromItem(item) 169 tab_id.append(data.id) 170 171 return tab_id 172 173 def updateFromParameters(self, parameters): 174 """ 175 Pass the update parameters to the current fit page 176 """ 177 self.currentTab.createPageForParameters(parameters) 114 178 115 179 def closeEvent(self, event): … … 246 310 for index_to_delete in index_list: 247 311 index_to_delete_str = str(index_to_delete) 248 if index_to_delete_str in list(self.dataToFitTab.keys()): 249 for tab_name in self.dataToFitTab[index_to_delete_str]: 250 # delete tab #index after corresponding data got removed 251 self.closeTabByName(tab_name) 252 self.dataToFitTab.pop(index_to_delete_str) 312 orig_dict = copy.deepcopy(self.dataToFitTab) 313 for tab_key in orig_dict.keys(): 314 if index_to_delete_str in tab_key: 315 for tab_name in orig_dict[tab_key]: 316 self.closeTabByName(tab_name) 317 self.dataToFitTab.pop(tab_key) 253 318 254 319 def allowBatch(self): 255 320 """ 256 321 Tell the caller that we accept multiple data instances 322 """ 323 return True 324 325 def isSerializable(self): 326 """ 327 Tell the caller that this perspective writes its state 257 328 """ 258 329 return True … … 337 408 pass 338 409 410 def getCurrentStateAsXml(self): 411 """ 412 Returns an XML version of the current state 413 """ 414 state = {} 415 for tab in self.tabs: 416 pass 417 return state 418 339 419 @property 340 420 def currentTab(self): -
src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
r7f41584 r7f41584 52 52 TAB_POLY = 3 53 53 CATEGORY_DEFAULT = "Choose category..." 54 MODEL_DEFAULT = "Choose model..." 54 55 CATEGORY_STRUCTURE = "Structure Factor" 55 56 CATEGORY_CUSTOM = "Plugin Models" … … 574 575 # Signals from other widgets 575 576 self.communicate.customModelDirectoryChanged.connect(self.onCustomModelChange) 576 self.communicate.saveAnalysisSignal.connect(self.savePageState)577 #self.communicate.loadAnalysisSignal.connect(self.loadPageState)578 577 self.smearing_widget.smearingChangedSignal.connect(self.onSmearingOptionsUpdate) 579 578 … … 623 622 to_string = "to its current value" if num_rows == 1 else "to their current values" 624 623 has_constraints = any([self.rowHasConstraint(i) for i in rows]) 624 has_real_constraints = any([self.rowHasActiveConstraint(i) for i in rows]) 625 625 626 626 self.actionSelect = QtWidgets.QAction(self) … … 640 640 self.actionRemoveConstraint.setText(QtCore.QCoreApplication.translate("self", "Remove constraint")) 641 641 642 self.actionEditConstraint = QtWidgets.QAction(self) 643 self.actionEditConstraint.setObjectName("actionEditConstrain") 644 self.actionEditConstraint.setText(QtCore.QCoreApplication.translate("self", "Edit constraint")) 645 642 646 self.actionMultiConstrain = QtWidgets.QAction(self) 643 647 self.actionMultiConstrain.setObjectName("actionMultiConstrain") … … 654 658 if has_constraints: 655 659 menu.addAction(self.actionRemoveConstraint) 660 if num_rows == 1 and has_real_constraints: 661 menu.addAction(self.actionEditConstraint) 656 662 #if num_rows == 1: 657 663 # menu.addAction(self.actionEditConstraint) … … 664 670 self.actionConstrain.triggered.connect(self.addSimpleConstraint) 665 671 self.actionRemoveConstraint.triggered.connect(self.deleteConstraint) 672 self.actionEditConstraint.triggered.connect(self.editConstraint) 666 673 self.actionMutualMultiConstrain.triggered.connect(self.showMultiConstraint) 667 674 self.actionSelect.triggered.connect(self.selectParameters) … … 701 708 new_func = c_text.replace(param_used, updated_param_used) 702 709 constraint.func = new_func 710 constraint.value_ex = updated_param_used 703 711 # Which row is the constrained parameter in? 704 712 row = self.getRowFromName(constraint.param) 713 714 # what is the parameter to constraint to? 715 constraint.value = param_used 716 717 # Should the new constraint be validated? 718 constraint.validate = mc_widget.validate 705 719 706 720 # Create a new item and add the Constraint object as a child … … 799 813 self.communicate.statusBarUpdateSignal.emit('Constraint added') 800 814 815 def editConstraint(self): 816 """ 817 Delete constraints from selected parameters. 818 """ 819 params_list = [s.data() for s in self.lstParams.selectionModel().selectedRows() 820 if self.isCheckable(s.row())] 821 assert len(params_list) == 1 822 row = self.lstParams.selectionModel().selectedRows()[0].row() 823 constraint = self.getConstraintForRow(row) 824 # Create and display the widget for param1 and param2 825 mc_widget = MultiConstraint(self, params=params_list, constraint=constraint) 826 # Check if any of the parameters are polydisperse 827 if not np.any([FittingUtilities.isParamPolydisperse(p, self.model_parameters, is2D=self.is2D) for p in params_list]): 828 # no parameters are pd - reset the text to not show the warning 829 mc_widget.lblWarning.setText("") 830 if mc_widget.exec_() != QtWidgets.QDialog.Accepted: 831 return 832 833 constraint = Constraint() 834 c_text = mc_widget.txtConstraint.text() 835 836 # widget.params[0] is the parameter we're constraining 837 constraint.param = mc_widget.params[0] 838 # parameter should have the model name preamble 839 model_name = self.kernel_module.name 840 # param_used is the parameter we're using in constraining function 841 param_used = mc_widget.params[1] 842 # Replace param_used with model_name.param_used 843 updated_param_used = model_name + "." + param_used 844 # Update constraint with new values 845 constraint.func = c_text 846 constraint.value_ex = updated_param_used 847 constraint.value = param_used 848 # Should the new constraint be validated? 849 constraint.validate = mc_widget.validate 850 851 # Which row is the constrained parameter in? 852 row = self.getRowFromName(constraint.param) 853 854 # Create a new item and add the Constraint object as a child 855 self.addConstraintToRow(constraint=constraint, row=row) 856 801 857 def deleteConstraint(self): 802 858 """ … … 853 909 return None 854 910 911 def allParamNames(self): 912 """ 913 Returns a list of all parameter names defined on the current model 914 """ 915 all_params = self.kernel_module._model_info.parameters.kernel_parameters 916 all_param_names = [param.name for param in all_params] 917 # Assure scale and background are always included 918 if 'scale' not in all_param_names: 919 all_param_names.append('scale') 920 if 'background' not in all_param_names: 921 all_param_names.append('background') 922 return all_param_names 923 924 def paramHasConstraint(self, param=None): 925 """ 926 Finds out if the given parameter in the main model has a constraint child 927 """ 928 if param is None: return False 929 if param not in self.allParamNames(): return False 930 931 for row in range(self._model_model.rowCount()): 932 if self._model_model.item(row,0).text() != param: continue 933 return self.rowHasConstraint(row) 934 935 # nothing found 936 return False 937 855 938 def rowHasConstraint(self, row): 856 939 """ … … 1018 1101 Checks if the current model has magnetic scattering implemented 1019 1102 """ 1020 has_ params = False1103 has_mag_params = False 1021 1104 if self.kernel_module: 1022 1105 has_mag_params = len(self.kernel_module.magnetic_params) > 0 … … 1029 1112 model = self.cbModel.currentText() 1030 1113 1114 if model == MODEL_DEFAULT: 1115 # if the previous category was not the default, keep it. 1116 # Otherwise, just return 1117 if self._previous_model_index != 0: 1118 # We need to block signals, or else state changes on perceived unchanged conditions 1119 self.cbModel.blockSignals(True) 1120 self.cbModel.setCurrentIndex(self._previous_model_index) 1121 self.cbModel.blockSignals(False) 1122 return 1123 1031 1124 # Assure the control is active 1032 1125 if not self.cbModel.isEnabled(): … … 1035 1128 if not model: 1036 1129 return 1130 1131 self.chkMagnetism.setEnabled(self.canHaveMagnetism()) 1132 self.chkMagnetism.setEnabled(self.canHaveMagnetism()) 1133 self.tabFitting.setTabEnabled(TAB_MAGNETISM, self.chkMagnetism.isChecked() and self.canHaveMagnetism()) 1134 self._previous_model_index = self.cbModel.currentIndex() 1037 1135 1038 1136 # Reset parameters to fit … … 1213 1311 self._model_model.clear() 1214 1312 return 1215 1313 # Wipe out the parameter model 1314 self._model_model.clear() 1216 1315 # Safely clear and enable the model combo 1217 1316 self.cbModel.blockSignals(True) … … 1225 1324 model_list = self.master_category_dict[category] 1226 1325 # Populate the models combobox 1326 self.cbModel.blockSignals(True) 1327 self.cbModel.addItem(MODEL_DEFAULT) 1227 1328 self.cbModel.addItems(sorted([model for (model, _) in model_list])) 1329 self.cbModel.blockSignals(False) 1228 1330 1229 1331 def onPolyModelChange(self, top, bottom): … … 1937 2039 Emits plotRequestedSignal for all plots found in the given model under the provided item name. 1938 2040 """ 1939 fitpage_name = "" if self.tab_id is None else "M"+str(self.tab_id)2041 fitpage_name = self.kernel_module.name 1940 2042 plots = GuiUtils.plotsFromFilename(item_name, item_model) 1941 2043 # Has the fitted data been shown? … … 2425 2527 def isCheckable(self, row): 2426 2528 return self._model_model.item(row, 0).isCheckable() 2529 2530 def selectCheckbox(self, row): 2531 """ 2532 Select the checkbox in given row. 2533 """ 2534 assert 0<= row <= self._model_model.rowCount() 2535 index = self._model_model.index(row, 0) 2536 item = self._model_model.itemFromIndex(index) 2537 item.setCheckState(QtCore.Qt.Checked) 2427 2538 2428 2539 def checkboxSelected(self, item): … … 3430 3541 return report_logic.reportList() 3431 3542 3432 def savePageState(self):3433 """3434 Create and serialize local PageState3435 """3436 filepath = self.saveAsAnalysisFile()3437 if filepath is None or filepath == "":3438 return3439 3440 fitpage_state = self.getFitPage()3441 fitpage_state += self.getFitModel()3442 3443 with open(filepath, 'w') as statefile:3444 for line in fitpage_state:3445 statefile.write(str(line))3446 3447 self.communicate.statusBarUpdateSignal.emit('Analysis saved.')3448 3449 def saveAsAnalysisFile(self):3450 """3451 Show the save as... dialog and return the chosen filepath3452 """3453 default_name = "FitPage"+str(self.tab_id)+".fitv"3454 3455 wildcard = "fitv files (*.fitv)"3456 kwargs = {3457 'caption' : 'Save As',3458 'directory' : default_name,3459 'filter' : wildcard,3460 'parent' : None,3461 }3462 # Query user for filename.3463 filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs)3464 filename = filename_tuple[0]3465 return filename3466 3467 3543 def loadPageStateCallback(self,state=None, datainfo=None, format=None): 3468 3544 """ … … 3549 3625 3550 3626 param_list.append(['is_data', str(self.data_is_loaded)]) 3551 if self.data_is_loaded: 3552 param_list.append(['data_id', str(self.logic.data.id)]) 3553 param_list.append(['data_name', str(self.logic.data.filename)]) 3554 3627 data_ids = [] 3628 filenames = [] 3629 if self.is_batch_fitting: 3630 for item in self.all_data: 3631 # need item->data->data_id 3632 data = GuiUtils.dataFromItem(item) 3633 data_ids.append(data.id) 3634 filenames.append(data.filename) 3635 else: 3636 if self.data_is_loaded: 3637 data_ids = [str(self.logic.data.id)] 3638 filenames = [str(self.logic.data.filename)] 3639 param_list.append(['is_batch_fitting', str(self.is_batch_fitting)]) 3640 param_list.append(['data_name', filenames]) 3641 param_list.append(['data_id', data_ids]) 3642 param_list.append(['tab_name', self.modelName()]) 3555 3643 # option tab 3556 3644 param_list.append(['q_range_min', str(self.q_range_min)]) … … 3583 3671 """ 3584 3672 param_list = [] 3673 if self.kernel_module is None: 3674 return param_list 3675 3585 3676 param_list.append(['model_name', str(self.cbModel.currentText())]) 3586 3677 … … 3622 3713 except: 3623 3714 pass 3624 3625 param_list.append([param_name, param_checked, param_value, param_error, param_min, param_max]) 3715 # Do we have any constraints on this parameter? 3716 constraint = self.getConstraintForRow(row) 3717 cons = () 3718 if constraint is not None: 3719 value = constraint.value 3720 func = constraint.func 3721 value_ex = constraint.value_ex 3722 param = constraint.param 3723 validate = constraint.validate 3724 3725 cons = (value, param, value_ex, validate, func) 3726 3727 param_list.append([param_name, param_checked, param_value,param_error, param_min, param_max, cons]) 3626 3728 3627 3729 def gatherPolyParams(row): … … 3687 3789 cb_text = cb.text() 3688 3790 3689 context = {}3690 3791 lines = cb_text.split(':') 3691 3792 if lines[0] != 'sasview_parameter_values': … … 3699 3800 line_dict[content[0]] = content[1:] 3700 3801 3802 self.updatePageWithParameters(line_dict) 3803 3804 def createPageForParameters(self, line_dict): 3805 """ 3806 Sets up page with requested model/str factor 3807 and fills it up with sent parameters 3808 """ 3809 if 'fitpage_category' in line_dict: 3810 self.cbCategory.setCurrentIndex(self.cbCategory.findText(line_dict['fitpage_category'][0])) 3811 if 'fitpage_model' in line_dict: 3812 self.cbModel.setCurrentIndex(self.cbModel.findText(line_dict['fitpage_model'][0])) 3813 if 'fitpage_structure' in line_dict: 3814 self.cbStructureFactor.setCurrentIndex(self.cbStructureFactor.findText(line_dict['fitpage_structure'][0])) 3815 3816 # Now that the page is ready for parameters, fill it up 3817 self.updatePageWithParameters(line_dict) 3818 3819 def updatePageWithParameters(self, line_dict): 3820 """ 3821 Update FitPage with parameters in line_dict 3822 """ 3823 if 'model_name' not in line_dict.keys(): 3824 return 3701 3825 model = line_dict['model_name'][0] 3702 3703 if 'model_name' not in line_dict.keys(): 3704 return False 3826 context = {} 3705 3827 3706 3828 if 'multiplicity' in line_dict.keys(): … … 3711 3833 self.updateMultiplicityCombo(multip) 3712 3834 3835 if 'tab_name' in line_dict.keys(): 3836 self.kernel_module.name = line_dict['tab_name'][0] 3713 3837 if 'polydisperse_params' in line_dict.keys(): 3714 3838 self.chkPolydispersity.setChecked(line_dict['polydisperse_params'][0]=='True') … … 3833 3957 ioffset = 0 3834 3958 joffset = 0 3835 if len(param_dict[param_name])> 4:3959 if len(param_dict[param_name])>5: 3836 3960 # error values are not editable - no need to update 3837 3961 ioffset = 1 … … 3846 3970 except: 3847 3971 pass 3972 3973 # constraints 3974 cons = param_dict[param_name][4+ioffset] 3975 if cons is not None and len(cons)==5: 3976 value = cons[0] 3977 param = cons[1] 3978 value_ex = cons[2] 3979 validate = cons[3] 3980 function = cons[4] 3981 constraint = Constraint() 3982 constraint.value = value 3983 constraint.func = function 3984 constraint.param = param 3985 constraint.value_ex = value_ex 3986 constraint.validate = validate 3987 self.addConstraintToRow(constraint=constraint, row=row) 3848 3988 3849 3989 self.setFocus() -
src/sas/qtgui/Perspectives/Fitting/MultiConstraint.py
r305114c r09e0c32 20 20 Logic class for interacting with MultiConstrainedUI view 21 21 """ 22 def __init__(self, parent=None, params=None ):22 def __init__(self, parent=None, params=None, constraint=None): 23 23 """ 24 24 parent: ConstraintWidget object … … 31 31 self.params = params 32 32 self.parent = parent 33 # Text of the constraint 34 self.function = None 35 # Should this constraint be validated? 36 self.validate = True 37 38 self.input_constraint = constraint 39 if self.input_constraint is not None: 40 variable = constraint.value 41 self.function = constraint.func 42 self.params.append(variable) 43 self.model_name = constraint.value_ex 44 # Passed constraint may be too complex for simple validation 45 self.validate = constraint.validate 46 else: 47 self.model_name = self.params[1] 33 48 34 49 self.setupLabels() … … 36 51 37 52 # Set param text control to the second parameter passed 38 self.txtConstraint.setText(self.params[1]) 39 53 if self.input_constraint is None: 54 self.txtConstraint.setText(self.params[1]) 55 else: 56 self.txtConstraint.setText(self.function) 40 57 self.cmdOK.clicked.connect(self.accept) 41 58 self.cmdHelp.clicked.connect(self.onHelp) … … 52 69 # Switch parameters 53 70 self.params[1], self.params[0] = self.params[0], self.params[1] 71 # change fully qualified param name (e.g. M1.sld -> M1.sld_solvent) 72 self.model_name = self.model_name.replace(self.params[0], self.params[1]) 54 73 # Try to swap parameter names in the line edit 55 74 current_text = self.txtConstraint.text() … … 64 83 Setup labels based on current parameters 65 84 """ 66 l1 = s elf.params[0]67 l2 = s elf.params[1]85 l1 = str(self.params[0]) 86 l2 = str(self.params[1]) 68 87 self.txtParam1.setText(l1) 69 88 self.txtParam1_2.setText(l1) … … 82 101 Add visual cues when formula is incorrect 83 102 """ 103 # Don't validate if requested 104 if not self.validate: return 105 84 106 formula_is_valid = False 85 107 formula_is_valid = self.validateConstraint(self.txtConstraint.text()) … … 99 121 return False 100 122 101 param_str = str(self.params[1]) 102 constraint_text = constraint_text.strip() 123 param_str = self.model_name 103 124 104 125 # 1. just the parameter -
src/sas/qtgui/Perspectives/Fitting/UI/ComplexConstraintUI.ui
r1738173 recc5d043 7 7 <x>0</x> 8 8 <y>0</y> 9 <width> 367</width>10 <height> 199</height>9 <width>478</width> 10 <height>257</height> 11 11 </rect> 12 12 </property> … … 69 69 </sizepolicy> 70 70 </property> 71 <property name="sizeAdjustPolicy"> 72 <enum>QComboBox::AdjustToContents</enum> 73 </property> 71 74 <item> 72 75 <property name="text"> … … 100 103 </sizepolicy> 101 104 </property> 105 <property name="sizeAdjustPolicy"> 106 <enum>QComboBox::AdjustToContents</enum> 107 </property> 102 108 <item> 103 109 <property name="text"> … … 161 167 <property name="sizeHint" stdset="0"> 162 168 <size> 163 <width> 40</width>169 <width>88</width> 164 170 <height>20</height> 165 171 </size> … … 168 174 </item> 169 175 <item> 170 <widget class="QPushButton" name="cmdOK"> 176 <widget class="QToolButton" name="cmdOK"> 177 <property name="minimumSize"> 178 <size> 179 <width>93</width> 180 <height>28</height> 181 </size> 182 </property> 183 <property name="toolTip"> 184 <string><html><head/><body><p>Add the constraint as defined by the above expression.</p></body></html></string> 185 </property> 171 186 <property name="text"> 172 <string>OK</string> 173 </property> 174 <property name="default"> 187 <string>Add</string> 188 </property> 189 <property name="popupMode"> 190 <enum>QToolButton::MenuButtonPopup</enum> 191 </property> 192 <property name="toolButtonStyle"> 193 <enum>Qt::ToolButtonTextOnly</enum> 194 </property> 195 <property name="autoRaise"> 175 196 <bool>false</bool> 176 197 </property> 198 <property name="arrowType"> 199 <enum>Qt::DownArrow</enum> 200 </property> 177 201 </widget> 178 202 </item> 179 203 <item> 180 204 <widget class="QPushButton" name="cmdCancel"> 205 <property name="toolTip"> 206 <string><html><head/><body><p>Close the window.</p></body></html></string> 207 </property> 181 208 <property name="text"> 182 <string>C ancel</string>209 <string>Close</string> 183 210 </property> 184 211 </widget> … … 197 224 <resources/> 198 225 <connections> 199 <connection>200 <sender>cmdOK</sender>201 <signal>clicked()</signal>202 <receiver>ComplexConstraintUI</receiver>203 <slot>accept()</slot>204 <hints>205 <hint type="sourcelabel">206 <x>149</x>207 <y>144</y>208 </hint>209 <hint type="destinationlabel">210 <x>179</x>211 <y>82</y>212 </hint>213 </hints>214 </connection>215 226 <connection> 216 227 <sender>cmdCancel</sender> -
src/sas/qtgui/Perspectives/Fitting/UI/ConstraintWidgetUI.ui
r91ad45c recc5d043 7 7 <x>0</x> 8 8 <y>0</y> 9 <width> 428</width>10 <height> 457</height>9 <width>597</width> 10 <height>607</height> 11 11 </rect> 12 12 </property> … … 14 14 <string>Constrained and Simultaneous Fit</string> 15 15 </property> 16 <layout class="QGridLayout" name="gridLayout ">16 <layout class="QGridLayout" name="gridLayout_2"> 17 17 <item row="0" column="0"> 18 18 <widget class="QGroupBox" name="groupBox"> … … 89 89 <string>Constraints</string> 90 90 </property> 91 <layout class="QGridLayout" name="gridLayout _2">91 <layout class="QGridLayout" name="gridLayout"> 92 92 <item row="0" column="0"> 93 <layout class="QHBoxLayout" name="horizontalLayout"> 94 <item> 95 <widget class="QLabel" name="label"> 96 <property name="text"> 97 <string>Special cases</string> 98 </property> 99 </widget> 100 </item> 101 <item> 102 <widget class="QComboBox" name="cbCases"> 93 <layout class="QHBoxLayout" name="horizontalLayout_4"> 94 <item> 95 <layout class="QHBoxLayout" name="horizontalLayout"> 103 96 <item> 104 <property name="text"> 105 <string>None</string> 106 </property> 97 <widget class="QLabel" name="label"> 98 <property name="text"> 99 <string>Special cases</string> 100 </property> 101 </widget> 107 102 </item> 103 <item> 104 <widget class="QComboBox" name="cbCases"> 105 <item> 106 <property name="text"> 107 <string>None</string> 108 </property> 109 </item> 110 </widget> 111 </item> 112 </layout> 113 </item> 114 <item> 115 <spacer name="horizontalSpacer_3"> 116 <property name="orientation"> 117 <enum>Qt::Horizontal</enum> 118 </property> 119 <property name="sizeHint" stdset="0"> 120 <size> 121 <width>40</width> 122 <height>20</height> 123 </size> 124 </property> 125 </spacer> 126 </item> 127 <item> 128 <widget class="QPushButton" name="cmdAdd"> 129 <property name="toolTip"> 130 <string>Define constraints between two fit pages.</string> 131 </property> 132 <property name="text"> 133 <string>Add constraints</string> 134 </property> 108 135 </widget> 109 136 </item> … … 160 187 </size> 161 188 </property> 189 <property name="toolTip"> 190 <string>Perform simultaneous fitting of selected fit pages.</string> 191 </property> 162 192 <property name="text"> 163 193 <string>Fit</string> … … 179 209 </size> 180 210 </property> 211 <property name="toolTip"> 212 <string>Display help on constrained and simultaneous fitting.</string> 213 </property> 181 214 <property name="text"> 182 215 <string>Help</string> -
src/sas/qtgui/Perspectives/Fitting/UI/MultiConstraintUI.ui
r1738173 recc5d043 10 10 <x>0</x> 11 11 <y>0</y> 12 <width> 369</width>13 <height>2 01</height>12 <width>435</width> 13 <height>233</height> 14 14 </rect> 15 15 </property> … … 184 184 <connections> 185 185 <connection> 186 <sender>cmdCancel</sender> 187 <signal>clicked()</signal> 188 <receiver>MultiConstraintUI</receiver> 189 <slot>reject()</slot> 190 <hints> 191 <hint type="sourcelabel"> 192 <x>187</x> 193 <y>121</y> 194 </hint> 195 <hint type="destinationlabel"> 196 <x>184</x> 197 <y>71</y> 198 </hint> 199 </hints> 200 </connection> 201 <connection> 186 202 <sender>cmdOK</sender> 187 203 <signal>clicked()</signal> … … 199 215 </hints> 200 216 </connection> 201 <connection>202 <sender>cmdCancel</sender>203 <signal>clicked()</signal>204 <receiver>MultiConstraintUI</receiver>205 <slot>reject()</slot>206 <hints>207 <hint type="sourcelabel">208 <x>187</x>209 <y>121</y>210 </hint>211 <hint type="destinationlabel">212 <x>184</x>213 <y>71</y>214 </hint>215 </hints>216 </connection>217 217 </connections> 218 218 </ui> -
src/sas/qtgui/Perspectives/Fitting/UnitTesting/ComplexConstraintTest.py
r725d9c06 rd72ac57 34 34 category_index = self.tab1.cbCategory.findText("Shape Independent") 35 35 self.tab1.cbCategory.setCurrentIndex(category_index) 36 model_index = self.tab1.cbModel.findText("be_polyelectrolyte") 37 self.tab1.cbModel.setCurrentIndex(model_index) 38 36 39 category_index = self.tab2.cbCategory.findText("Cylinder") 37 40 self.tab2.cbCategory.setCurrentIndex(category_index) 41 model_index = self.tab2.cbModel.findText("barbell") 42 self.tab2.cbModel.setCurrentIndex(model_index) 38 43 39 44 tabs = [self.tab1, self.tab2] … … 127 132 """ 128 133 # default data 129 self.assertEqual(self.widget.constraint(), ('M1', 'scale', '=', 'M1.scale')) 134 c = self.widget.constraint() 135 self.assertEqual(c[0], 'M1') 136 self.assertEqual(c[1].func, 'M1.scale') 130 137 131 138 # Change parameter and operand 132 self.widget.cbOperator.setCurrentIndex(3) 133 self.widget.cbParam1.setCurrentIndex(3) 134 self.assertEqual(self.widget.constraint(), ('M1', 'bjerrum_length', '>=', 'M1.scale')) 139 #self.widget.cbOperator.setCurrentIndex(3) 140 self.widget.cbParam2.setCurrentIndex(3) 141 c = self.widget.constraint() 142 self.assertEqual(c[0], 'M1') 143 self.assertEqual(c[1].func, 'M1.sld_solvent') 144 #self.assertEqual(c[1].operator, '>=') 135 145 136 146 def testOnHelp(self): -
src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py
rec4a143 rbaeac95 175 175 category_index = self.widget.cbCategory.findText("Shape Independent") 176 176 self.widget.cbCategory.setCurrentIndex(category_index) 177 model_index = self.widget.cbModel.findText("be_polyelectrolyte") 178 self.widget.cbModel.setCurrentIndex(model_index) 177 179 178 180 # test the model combo content 179 self.assertEqual(self.widget.cbModel.count(), 28)181 self.assertEqual(self.widget.cbModel.count(), 30) 180 182 181 183 # Try to change back to default … … 183 185 184 186 # Observe no such luck 185 self.assertEqual(self.widget.cbCategory.currentIndex(), 6)186 self.assertEqual(self.widget.cbModel.count(), 29)187 self.assertEqual(self.widget.cbCategory.currentIndex(), 7) 188 self.assertEqual(self.widget.cbModel.count(), 30) 187 189 188 190 # Set the structure factor … … 201 203 category_index = self.widget.cbCategory.findText("Shape Independent") 202 204 self.widget.cbCategory.setCurrentIndex(category_index) 205 model_index = self.widget.cbModel.findText("be_polyelectrolyte") 206 self.widget.cbModel.setCurrentIndex(model_index) 203 207 204 208 # check the enablement of controls … … 215 219 # 216 220 # Now change the model 217 self.widget.cbModel.setCurrentIndex( 2)221 self.widget.cbModel.setCurrentIndex(4) 218 222 self.assertEqual(self.widget.cbModel.currentText(),'dab') 219 223 … … 226 230 self.widget.data_is_loaded = True 227 231 # Reset the sasmodel index 228 self.widget.cbModel.setCurrentIndex( 1)232 self.widget.cbModel.setCurrentIndex(2) 229 233 self.assertEqual(self.widget.cbModel.currentText(),'broad_peak') 230 234 … … 377 381 category_index = self.widget.cbCategory.findText("Shape Independent") 378 382 self.widget.cbCategory.setCurrentIndex(category_index) 383 model_index = self.widget.cbModel.findText("be_polyelectrolyte") 384 self.widget.cbModel.setCurrentIndex(model_index) 385 379 386 # Check the poly model 380 387 self.assertEqual(self.widget._poly_model.rowCount(), 0) … … 383 390 # Change the category index so we have a model available 384 391 self.widget.cbCategory.setCurrentIndex(2) 392 self.widget.cbModel.setCurrentIndex(1) 385 393 386 394 # Check the poly model … … 556 564 category_index = self.widget.cbCategory.findText("Sphere") 557 565 self.widget.cbCategory.setCurrentIndex(category_index) 566 model_index = self.widget.cbModel.findText("adsorbed_layer") 567 self.widget.cbModel.setCurrentIndex(model_index) 558 568 559 569 # Check the magnetic model … … 634 644 category_index = self.widget.cbCategory.findText("Sphere") 635 645 self.widget.cbCategory.setCurrentIndex(category_index) 646 model_index = self.widget.cbModel.findText("adsorbed_layer") 647 self.widget.cbModel.setCurrentIndex(model_index) 636 648 637 649 # Check the enablement/text … … 973 985 category_index = self.widget.cbCategory.findText("Sphere") 974 986 self.widget.cbCategory.setCurrentIndex(category_index) 987 model_index = self.widget.cbModel.findText("adsorbed_layer") 988 self.widget.cbModel.setCurrentIndex(model_index) 975 989 self.widget.main_params_to_fit = ['scale'] 976 990 … … 986 1000 self.assertListEqual(fp.main_params_to_fit, ['scale']) 987 1001 988 def testPushFitPage(self):1002 def notestPushFitPage(self): 989 1003 """ 990 1004 Push current state of fitpage onto stack … … 997 1011 self.widget.data = item 998 1012 category_index = self.widget.cbCategory.findText("Sphere") 1013 model_index = self.widget.cbModel.findText("adsorbed_layer") 1014 self.widget.cbModel.setCurrentIndex(model_index) 999 1015 1000 1016 # Asses the initial state of stack -
src/sas/qtgui/Plotting/Plotter2D.py
rc30822c rc30822c 330 330 self.manager.communicator.plotUpdateSignal.emit([new_plot]) 331 331 332 self.manager.communicator.forcePlotDisplaySignal.emit([item, new_plot]) 333 334 # Show the plot 335 332 336 def setSlicer(self, slicer): 333 337 """ -
src/sas/qtgui/Utilities/GuiUtils.py
r67346f9 r133812c7 11 11 import webbrowser 12 12 import urllib.parse 13 import json 14 from io import BytesIO 13 15 14 16 import numpy as np … … 26 28 from sas.qtgui.Plotting.PlotterData import Data1D 27 29 from sas.qtgui.Plotting.PlotterData import Data2D 30 from sas.qtgui.Plotting.Plottables import Plottable 31 from sas.sascalc.dataloader.data_info import Sample, Source, Vector 32 from sas.qtgui.Plotting.Plottables import View 33 from sas.qtgui.Plotting.Plottables import PlottableTheory1D 34 from sas.qtgui.Plotting.Plottables import PlottableFit1D 35 from sas.qtgui.Plotting.Plottables import Text 36 from sas.qtgui.Plotting.Plottables import Chisq 37 from sas.qtgui.MainWindow.DataState import DataState 38 28 39 from sas.sascalc.dataloader.loader import Loader 29 40 from sas.qtgui.Utilities import CustomDir … … 259 270 sendDataToGridSignal = QtCore.pyqtSignal(list) 260 271 261 # Action Save Analysis triggered262 saveAnalysisSignal = QtCore.pyqtSignal()263 264 272 # Mask Editor requested 265 273 maskEditorSignal = QtCore.pyqtSignal(Data2D) … … 292 300 forcePlotDisplaySignal = QtCore.pyqtSignal(list) 293 301 294 def updateModelItemWithPlot(item, update_data, name="" ):302 def updateModelItemWithPlot(item, update_data, name="", checkbox_state=None): 295 303 """ 296 304 Adds a checkboxed row named "name" to QStandardItem … … 317 325 # Force redisplay 318 326 return 319 320 327 # Create the new item 321 328 checkbox_item = createModelItemWithPlot(update_data, name) 322 329 330 if checkbox_state is not None: 331 checkbox_item.setCheckState(checkbox_state) 323 332 # Append the new row to the main item 324 333 item.appendRow(checkbox_item) … … 571 580 if isinstance(data.process, list) and data.process: 572 581 for process in data.process: 582 if process is None: 583 continue 573 584 process_date = process.date 574 585 process_date_item = QtGui.QStandardItem("Date: " + process_date) … … 1145 1156 return result 1146 1157 1158 def saveData(fp, data): 1159 """ 1160 save content of data to fp (a .write()-supporting file-like object) 1161 """ 1162 1163 def add_type(dict, type): 1164 dict['__type__'] = type.__name__ 1165 return dict 1166 1167 def jdefault(o): 1168 """ 1169 objects that can't otherwise be serialized need to be converted 1170 """ 1171 # tuples and sets (TODO: default JSONEncoder converts tuples to lists, create custom Encoder that preserves tuples) 1172 if isinstance(o, (tuple, set)): 1173 content = { 'data': list(o) } 1174 return add_type(content, type(o)) 1175 1176 # "simple" types 1177 if isinstance(o, (Sample, Source, Vector)): 1178 return add_type(o.__dict__, type(o)) 1179 if isinstance(o, (Plottable, View)): 1180 return add_type(o.__dict__, type(o)) 1181 1182 # DataState 1183 if isinstance(o, (Data1D, Data2D)): 1184 # don't store parent 1185 content = o.__dict__.copy() 1186 #content.pop('parent') 1187 return add_type(content, type(o)) 1188 1189 # ndarray 1190 if isinstance(o, np.ndarray): 1191 buffer = BytesIO() 1192 np.save(buffer, o) 1193 buffer.seek(0) 1194 content = { 'data': buffer.read().decode('latin-1') } 1195 return add_type(content, type(o)) 1196 1197 # not supported 1198 logging.info("data cannot be serialized to json: %s" % type(o)) 1199 return None 1200 1201 json.dump(data, fp, indent=2, sort_keys=True, default=jdefault) 1202 1203 def readDataFromFile(fp): 1204 ''' 1205 Reads in Data1D/Data2 datasets from the file. 1206 Datasets are stored in the JSON format. 1207 ''' 1208 supported = [ 1209 tuple, set, 1210 Sample, Source, Vector, 1211 Plottable, Data1D, Data2D, PlottableTheory1D, PlottableFit1D, Text, Chisq, View, 1212 DataState, np.ndarray] 1213 1214 lookup = dict((cls.__name__, cls) for cls in supported) 1215 1216 class TooComplexException(Exception): 1217 pass 1218 1219 def simple_type(cls, data, level): 1220 class Empty(object): 1221 def __init__(self): 1222 for key, value in data.items(): 1223 setattr(self, key, generate(value, level)) 1224 1225 # create target object 1226 o = Empty() 1227 o.__class__ = cls 1228 1229 return o 1230 1231 def construct(type, data, level): 1232 try: 1233 cls = lookup[type] 1234 except KeyError: 1235 logging.info('unknown type: %s' % type) 1236 return None 1237 1238 # tuples and sets 1239 if cls in (tuple, set): 1240 # convert list to tuple/set 1241 return cls(generate(data['data'], level)) 1242 1243 # "simple" types 1244 if cls in (Sample, Source, Vector): 1245 return simple_type(cls, data, level) 1246 if issubclass(cls, Plottable) or (cls == View): 1247 return simple_type(cls, data, level) 1248 1249 # DataState 1250 if cls == DataState: 1251 o = simple_type(cls, data, level) 1252 o.parent = None # TODO: set to ??? 1253 return o 1254 1255 # ndarray 1256 if cls == np.ndarray: 1257 buffer = BytesIO() 1258 buffer.write(data['data'].encode('latin-1')) 1259 buffer.seek(0) 1260 return np.load(buffer) 1261 1262 logging.info('not implemented: %s, %s' % (type, cls)) 1263 return None 1264 1265 def generate(data, level): 1266 if level > 16: # recursion limit (arbitrary number) 1267 raise TooComplexException() 1268 else: 1269 level += 1 1270 1271 if isinstance(data, dict): 1272 try: 1273 type = data['__type__'] 1274 except KeyError: 1275 # if dictionary doesn't have __type__ then it is assumed to be just an ordinary dictionary 1276 o = {} 1277 for key, value in data.items(): 1278 o[key] = generate(value, level) 1279 return o 1280 1281 return construct(type, data, level) 1282 1283 if isinstance(data, list): 1284 return [generate(item, level) for item in data] 1285 1286 return data 1287 1288 new_stored_data = {} 1289 for id, data in json.load(fp).items(): 1290 try: 1291 new_stored_data[id] = generate(data, 0) 1292 except TooComplexException: 1293 logging.info('unable to load %s' % id) 1294 1295 return new_stored_data 1296 1297 def readProjectFromSVS(filepath): 1298 """ 1299 Read old SVS file and convert to the project dictionary 1300 """ 1301 from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader 1302 from sas.sascalc.fit.pagestate import Reader 1303 1304 loader = Loader() 1305 loader.associate_file_reader('.svs', Reader) 1306 temp = loader.load(filepath) 1307 state_reader = Reader() 1308 data_svs, state_svs = state_reader.read(filepath) 1309 1310 output = [] 1311 if isinstance(temp, list) and isinstance(state_svs, list): 1312 for item, state in zip(temp, state_svs): 1313 output.append([item, state]) 1314 else: 1315 output[temp, state_svs] 1316 return output 1317 1318 def convertFromSVS(datasets): 1319 """ 1320 Read in properties from SVS and convert into a simple dict 1321 """ 1322 content = {} 1323 for dataset in datasets: 1324 # we already have data - interested only in properties 1325 #[[item_1, state_1], [item_2, state_2],...] 1326 data = dataset[0] 1327 params = dataset[1] 1328 content[params.data_id] = {} 1329 content[params.data_id]['fit_data'] = [data, {'checked': 2}, []] 1330 param_dict = {} 1331 param_dict['fitpage_category'] = [params.categorycombobox] 1332 param_dict['fitpage_model'] = [params.formfactorcombobox] 1333 param_dict['fitpage_structure'] = [params.structurecombobox] 1334 param_dict['2D_params'] = [str(params.is_2D)] 1335 param_dict['chainfit_params'] = ["False"] 1336 param_dict['data_id'] = [params.data_id] 1337 param_dict['data_name'] = [params.data_name] 1338 param_dict['is_data'] = [str(params.is_data)] 1339 param_dict['magnetic_params'] = [str(params.magnetic_on)] 1340 param_dict['model_name'] = [params.formfactorcombobox] 1341 param_dict['polydisperse_params'] = [str(params.enable_disp)] 1342 param_dict['q_range_max'] = [str(params.qmax)] 1343 param_dict['q_range_min'] = [str(params.qmin)] 1344 # Smearing is a bit trickier. 4.x has multiple keywords, 1345 # one for each combobox option 1346 if params.enable_smearer: 1347 if params.slit_smearer: 1348 w = 1 1349 elif params.pinhole_smearer: 1350 w = 2 1351 else: 1352 w = 0 1353 param_dict['smearing'] = [str(w)] 1354 # weighting is also tricky. 4.x has multiple keywords, 1355 # one for each radio box. 1356 if params.dI_noweight: 1357 w = 2 1358 elif params.dI_didata: 1359 w = 3 1360 elif params.dI_sqrdata: 1361 w = 4 1362 elif params.dI_idata: 1363 w = 5 1364 else: 1365 w = 2 1366 param_dict['weighting'] = [str(w)] 1367 1368 # 4.x multi_factor is really the multiplicity 1369 if params.multi_factor is not None: 1370 param_dict['multiplicity'] = [str(int(params.multi_factor))] 1371 1372 # playing with titles 1373 data.filename = params.file 1374 data.title = params.data_name 1375 data.name = params.data_name 1376 1377 # main parameters 1378 for p in params.parameters: 1379 p_name = p[1] 1380 param_dict[p_name] = [str(p[0]), str(p[2]), None, str(p[5][1]), str(p[6][1])] 1381 # orientation parameters 1382 if params.is_2D: 1383 for p in params.orientation_params: 1384 p_name = p[1] 1385 p_min = "-360.0" 1386 p_max = "360.0" 1387 if p[5][1] != "": 1388 p_min = p[5][1] 1389 if p[6][1] != "": 1390 p_max = p[6][1] 1391 param_dict[p_name] = [str(p[0]), str(p[2]), None, p_min, p_max] 1392 1393 # disperse parameters 1394 if params.enable_disp: 1395 for p in params.fittable_param: 1396 p_name = p[1] 1397 p_opt = str(p[0]) 1398 p_err = "0" 1399 p_width = str(p[2]) 1400 p_min = str(0) 1401 p_max = "inf" 1402 param_npts = p_name.replace('.width','.npts') 1403 param_nsigmas = p_name.replace('.width', '.nsigmas') 1404 if params.is_2D and p_name in params.disp_obj_dict: 1405 lookup = params.orientation_params_disp 1406 p_min = "-360.0" 1407 p_max = "360.0" 1408 else: 1409 lookup = params.fixed_param 1410 p_npts = [s[2] for s in lookup if s[1] == param_npts][0] 1411 p_nsigmas = [s[2] for s in lookup if s[1] == param_nsigmas][0] 1412 if p_name in params.disp_obj_dict: 1413 p_disp = params.disp_obj_dict[p_name] 1414 else: 1415 p_disp = "gaussian" 1416 param_dict[p_name] = [p_opt, p_width, p_min, p_max, p_npts, p_nsigmas, p_disp] 1417 1418 content[params.data_id]['fit_params'] = param_dict 1419 return content 1147 1420 1148 1421 def enum(*sequential, **named): -
src/sas/sascalc/dataloader/readers/cansas_reader.py
rb8080e1 rb1b71ad 184 184 if CANSAS_NS.get(self.cansas_version).get("ns") == value.rsplit(" ")[0]: 185 185 return True 186 if ext == " svs":186 if ext == ".svs": 187 187 return True # Why is this required? 188 188 # If we get to this point then file isn't valid CanSAS -
src/sas/sascalc/fit/pagestate.py
rb8080e1 rb1b71ad 1249 1249 1250 1250 else: 1251 self.call_back(format=ext)1251 #self.call_back(format=ext) 1252 1252 raise RuntimeError("%s is not a file" % path) 1253 1253 1254 1254 # Return output consistent with the loader's api 1255 1255 if len(output) == 0: 1256 self.call_back(state=None, datainfo=None, format=ext)1256 #self.call_back(state=None, datainfo=None, format=ext) 1257 1257 return None 1258 1258 else: 1259 states=[] 1259 1260 for data in output: 1260 1261 # Call back to post the new state … … 1281 1282 if isinstance(data.run_name, dict): 1282 1283 # Note: key order in dict is not guaranteed, so sort 1283 name = data.run_name.keys()[0]1284 name = list(data.run_name.keys())[0] 1284 1285 else: 1285 1286 name = data.run_name … … 1289 1290 state.version = fitstate.version 1290 1291 # store state in fitting 1291 self.call_back(state=state, datainfo=data, format=ext)1292 #self.call_back(state=state, datainfo=data, format=ext) 1292 1293 self.state = state 1294 states.append(state) 1293 1295 simfitstate = self._parse_simfit_state(entry) 1294 1296 if simfitstate is not None: 1295 self.call_back(state=simfitstate)1296 1297 return output1297 #self.call_back(state=simfitstate) 1298 states.append(simfitstate) 1299 return (output, states) 1298 1300 except: 1299 self.call_back(format=ext)1301 #self.call_back(format=ext) 1300 1302 raise 1301 1303
Note: See TracChangeset
for help on using the changeset viewer.