Changes in src/sas/qtgui/MainWindow/DataExplorer.py [67346f9:e5ae812] in sasview
- File:
-
- 1 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
Note: See TracChangeset
for help on using the changeset viewer.