Changeset 21e71f1 in sasview


Ignore:
Timestamp:
Nov 21, 2018 8:39:24 AM (3 weeks ago)
Author:
Piotr Rozyczko <piotr.rozyczko@…>
Branches:
ESS_GUI, ESS_GUI_Invariant, ESS_GUI_batch_fitting, ESS_GUI_ordering
Children:
f2e199e
Parents:
44c15fc (diff), fb39f28 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge branch 'ESS_GUI_project_save' into ESS_GUI

Files:
3 added
33 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/MainWindow/DataExplorer.py

    r67346f9 re5ae812  
    238238        Called when the "Open Project" menu item chosen. 
    239239        """ 
     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 
    240258        kwargs = { 
    241259            'parent'    : self, 
    242260            '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 
    246263        } 
    247264        filename = QtWidgets.QFileDialog.getOpenFileName(**kwargs)[0] 
    248265        if filename: 
    249266            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) 
    289283 
    290284    def saveProject(self): 
     
    301295        name_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs) 
    302296        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 
    311571 
    312572    def deleteFile(self, event): 
     
    424684            retval = msgbox.exec_() 
    425685 
     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_() 
    426705 
    427706    def freezeCheckedData(self): 
     
    10591338        self.actionQuick3DPlot.triggered.connect(self.quickData3DPlot) 
    10601339        self.actionEditMask.triggered.connect(self.showEditDataMask) 
    1061         self.actionDelete.triggered.connect(self.deleteItem) 
     1340        self.actionDelete.triggered.connect(self.deleteSelectedItem) 
    10621341        self.actionFreezeResults.triggered.connect(self.freezeSelectedItems) 
    10631342 
     
    12711550                self.freezeItem(item_to_copy) 
    12721551 
    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): 
    12741568        """ 
    12751569        Delete the current item 
     
    12881582            return 
    12891583 
     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 
    12901597        # Every time a row is removed, the indices change, so we'll just remove 
    12911598        # 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  
    13001599        while len(indices) > 0: 
    13011600            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) 
    13041604            if item_to_delete and item_to_delete.isCheckable(): 
    1305                 row = row_index.row() 
     1605                #row = row_index.row() 
     1606                row = index.row() 
    13061607 
    13071608                # store the deleted item details so we can pass them on later 
     
    14301731        checkbox_item.setCheckable(True) 
    14311732        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)) 
    14331735 
    14341736        # Add the actual Data1D/Data2D object 
  • src/sas/qtgui/MainWindow/DataManager.py

    re2e5f3d r345b3b3  
    2929from sas.qtgui.Plotting.PlotterData import Data1D 
    3030from sas.qtgui.Plotting.PlotterData import Data2D 
    31 from sas.qtgui.Plotting.Plottables import Plottable 
    3231from sas.qtgui.Plotting.Plottables import PlottableTheory1D 
    3332from sas.qtgui.Plotting.Plottables import PlottableFit1D 
  • src/sas/qtgui/MainWindow/GuiManager.py

    r67346f9 re5ae812  
    255255        if self._current_perspective: 
    256256            self._current_perspective.setClosable() 
    257             #self._workspace.workspace.removeSubWindow(self._current_perspective) 
    258257            self._current_perspective.close() 
    259258            self._workspace.workspace.removeSubWindow(self._current_perspective) 
     
    471470        self._workspace.actionOpen_Project.triggered.connect(self.actionOpen_Project) 
    472471        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) 
    474473        self._workspace.actionSave_Analysis.triggered.connect(self.actionSave_Analysis) 
    475474        self._workspace.actionQuit.triggered.connect(self.actionQuit) 
     
    561560        """ 
    562561        """ 
    563         print("actionOpen_Analysis TRIGGERED") 
    564         pass 
    565  
    566     def actionSave(self): 
     562        self.filesWidget.loadAnalysis() 
     563        pass 
     564 
     565    def actionSave_Project(self): 
    567566        """ 
    568567        Menu Save Project 
    569568        """ 
    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) 
    571604 
    572605    def actionSave_Analysis(self): 
     
    574607        Menu File/Save Analysis 
    575608        """ 
    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) 
    577627 
    578628    def actionQuit(self): 
     
    10561106        """ 
    10571107        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 
    10581114        if isinstance(perspective, Perspectives.PERSPECTIVES["Fitting"]): 
    10591115            self.checkAnalysisOption(self._workspace.actionFitting) 
     
    11271183            out_f.write("#Application appearance custom configuration\n") 
    11281184            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))) 
    11331190        pass # debugger anchor 
  • src/sas/qtgui/MainWindow/UI/AcknowledgementsUI.ui

    r7385fec recc5d043  
    1010    <x>0</x> 
    1111    <y>0</y> 
    12     <width>508</width> 
    13     <height>283</height> 
     12    <width>468</width> 
     13    <height>316</height> 
    1414   </rect> 
    1515  </property> 
     
    2727    <widget class="QLabel" name="label"> 
    2828     <property name="sizePolicy"> 
    29       <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> 
     29      <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> 
    3030       <horstretch>0</horstretch> 
    3131       <verstretch>0</verstretch> 
     
    3535      <size> 
    3636       <width>16777215</width> 
    37        <height>41</height> 
     37       <height>50</height> 
    3838      </size> 
    3939     </property> 
     
    4646    <widget class="QTextBrowser" name="textBrowser"> 
    4747     <property name="sizePolicy"> 
    48       <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> 
     48      <sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding"> 
    4949       <horstretch>0</horstretch> 
    5050       <verstretch>0</verstretch> 
    5151      </sizepolicy> 
    5252     </property> 
     53     <property name="minimumSize"> 
     54      <size> 
     55       <width>0</width> 
     56       <height>85</height> 
     57      </size> 
     58     </property> 
    5359     <property name="maximumSize"> 
    5460      <size> 
    5561       <width>16777215</width> 
    56        <height>61</height> 
     62       <height>85</height> 
    5763      </size> 
    5864     </property> 
     
    6167&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; 
    6268p, li { white-space: pre-wrap; } 
    63 &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt; 
    64 &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     69&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;&quot;&gt; 
     70&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8.25pt;&quot;&gt;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.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
    6571     </property> 
    6672    </widget> 
     
    103109&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; 
    104110p, li { white-space: pre-wrap; } 
    105 &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;&quot;&gt; 
    106 &lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;M. Doucet et al. SasView Version 4.2, Zenodo, 10.5281/zenodo.1412041&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     111&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;&quot;&gt; 
     112&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:8.25pt;&quot;&gt;M. Doucet et al. SasView Version 4.2, Zenodo, 10.5281/zenodo.1412041&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
    107113     </property> 
    108114    </widget> 
     
    111117    <widget class="QLabel" name="label_3"> 
    112118     <property name="sizePolicy"> 
    113       <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> 
     119      <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> 
    114120       <horstretch>0</horstretch> 
    115121       <verstretch>0</verstretch> 
     
    119125      <size> 
    120126       <width>16777215</width> 
    121        <height>38</height> 
     127       <height>50</height> 
    122128      </size> 
    123129     </property> 
  • src/sas/qtgui/MainWindow/UnitTesting/DataExplorerTest.py

    ra24eacf rb1b71ad  
    854854 
    855855        # Attempt at deleting 
    856         self.form.deleteItem() 
     856        self.form.deleteSelectedItem() 
    857857 
    858858        # Test the warning dialog called once 
     
    868868        self.form.current_view.selectionModel().select(select_index, QtCore.QItemSelectionModel.Rows) 
    869869        # delete it. now for good 
    870         self.form.deleteItem() 
     870        self.form.deleteSelectedItem() 
    871871 
    872872        # Test the warning dialog called once 
  • src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py

    r305114c rd72ac57  
    1515from sas.qtgui.Perspectives.Fitting import FittingUtilities 
    1616import sas.qtgui.Utilities.GuiUtils as GuiUtils 
    17 ALLOWED_OPERATORS = ['=','<','>','>=','<='] 
     17from sas.qtgui.Perspectives.Fitting.Constraint import Constraint 
     18 
     19#ALLOWED_OPERATORS = ['=','<','>','>=','<='] 
     20ALLOWED_OPERATORS = ['='] 
    1821 
    1922# Local UI 
     
    2124 
    2225class ComplexConstraint(QtWidgets.QDialog, Ui_ComplexConstraintUI): 
     26    constraintReadySignal = QtCore.pyqtSignal(tuple) 
    2327    def __init__(self, parent=None, tabs=None): 
    2428        super(ComplexConstraint, self).__init__() 
     
    3236        self.tab_names = None 
    3337        self.operator = '=' 
     38        self._constraint = Constraint() 
    3439 
    3540        self.warning = self.lblWarning.text() 
     
    5358        Signals from various elements 
    5459        """ 
    55         self.cmdOK.clicked.connect(self.accept) 
     60        self.cmdOK.clicked.connect(self.onApply) 
    5661        self.cmdHelp.clicked.connect(self.onHelp) 
    5762        self.cmdRevert.clicked.connect(self.onRevert) 
     
    6974        self.txtName2.setText(self.tab_names[1]) 
    7075 
    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        """ 
    7295        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 
    75100        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) 
    78104 
    79105        self.txtParam.setText(self.tab_names[0] + ":" + self.cbParam1.currentText()) 
     
    84110 
    85111        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) 
    86123 
    87124    def setupTooltip(self): 
     
    144181 
    145182        # Original indices 
     183        index2 = index2 if index2 >= 0 else 0 
     184        index1 = index1 if index1 >= 0 else 0 
    146185        self.cbParam1.setCurrentIndex(index2) 
    147186        self.cbParam2.setCurrentIndex(index1) 
     
    205244    def constraint(self): 
    206245        """ 
    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() 
    210302 
    211303    def onHelp(self): 
  • src/sas/qtgui/Perspectives/Fitting/Constraint.py

    r14ec91c5 r09e0c32  
    55    hence made into a class. 
    66    """ 
    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="="): 
    810        self._value = value 
    911        self._param = param 
     12        self._value_ex = value_ex 
    1013        self._func = func 
    11         self.active = True 
    1214        self._min = min 
    1315        self._max = max 
     16        self._operator = operator 
     17        self.validate = True 
     18        self.active = True 
    1419 
    1520    @property 
    1621    def value(self): 
     22        # value/parameter to fit to (e.g. 1.0 or sld) 
    1723        return self._value 
    1824 
     
    2228 
    2329    @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 
    2439    def param(self): 
     40        # parameter which is being fitted 
    2541        return self._param 
    2642 
     
    3147    @property 
    3248    def func(self): 
     49        # Function to be used for constraint 
     50        # e.g. sqrt(M1.sld+1.0) 
    3351        return self._func 
    3452 
     
    3957    @property 
    4058    def min(self): 
     59        # min param value for single value constraints 
    4160        return self._min 
    4261 
     
    4766    @property 
    4867    def max(self): 
     68        # max param value for single value constraints 
    4969        return self._max 
    5070 
     
    5373        self._max = val 
    5474 
     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 r21e71f1  
    2323    Constraints Dialog to select the desired parameter/model constraints. 
    2424    """ 
     25    fitCompleteSignal = QtCore.pyqtSignal(tuple) 
     26    batchCompleteSignal = QtCore.pyqtSignal(tuple) 
     27    fitFailedSignal = QtCore.pyqtSignal(tuple) 
    2528 
    2629    def __init__(self, parent=None): 
     
    3235        # To keep with previous SasView values, use 300 as the start offset 
    3336        self.page_id = 301 
     37        self.tab_id = self.page_id 
    3438 
    3539        # Are we chain fitting? 
     
    6064        Set up various widget states 
    6165        """ 
     66        # disable special cases until properly defined 
     67        self.label.setVisible(False) 
     68        self.cbCases.setVisible(False) 
     69 
    6270        labels = ['FitPage', 'Model', 'Data', 'Mnemonic'] 
    6371        # tab widget - headers 
     
    7987        self.tblConstraints.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch) 
    8088        self.tblConstraints.setEnabled(False) 
     89        header = self.tblConstraints.horizontalHeaderItem(0) 
     90        header.setToolTip("Double click a row below to edit the constraint.") 
    8191 
    8292        self.tblConstraints.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 
     
    93103        self.cmdFit.clicked.connect(self.onFit) 
    94104        self.cmdHelp.clicked.connect(self.onHelp) 
     105        self.cmdAdd.clicked.connect(self.showMultiConstraint) 
    95106        self.chkChain.toggled.connect(self.onChainFit) 
    96107 
     
    100111        self.tblConstraints.cellChanged.connect(self.onConstraintChange) 
    101112 
     113        # Internal signals 
     114        self.fitCompleteSignal.connect(self.fitComplete) 
     115        self.batchCompleteSignal.connect(self.batchComplete) 
     116        self.fitFailedSignal.connect(self.fitFailed) 
     117 
    102118        # External signals 
    103119        self.parent.tabsModifiedSignal.connect(self.initializeFitList) 
     
    156172        fitter = Fit() 
    157173        fitter.fitter_id = self.page_id 
    158  
    159         # Notify the parent about fitting started 
    160         self.parent.fittingStartedSignal.emit(tabs_to_fit) 
    161174 
    162175        # prepare fitting problems for each tab 
     
    168181        try: 
    169182            for tab in tabs_to_fit: 
     183                if not self.isTabImportable(tab): continue 
    170184                tab_object = ObjectLibrary.getObject(tab) 
    171185                if tab_object is None: 
     
    177191        except ValueError: 
    178192            # No parameters selected in one of the tabs 
    179             no_params_msg = "Fitting can not be performed.\n" +\ 
     193            no_params_msg = "Fitting cannot be performed.\n" +\ 
    180194                            "Not all tabs chosen for fitting have parameters selected for fitting." 
    181195            QtWidgets.QMessageBox.warning(self, 
     
    200214        batch_inputs = {} 
    201215        batch_outputs = {} 
     216 
     217        # Notify the parent about fitting started 
     218        self.parent.fittingStartedSignal.emit(tabs_to_fit) 
    202219 
    203220        # new fit thread object 
     
    223240 
    224241        #disable the Fit button 
     242        self.cmdFit.setStyleSheet('QPushButton {color: red;}') 
    225243        self.cmdFit.setText('Running...') 
    226244        self.parent.communicate.statusBarUpdateSignal.emit('Fitting started...') 
     
    292310        """ 
    293311        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 
    298327 
    299328    def onTabCellEntered(self, row, column): 
     
    308337    def onFitComplete(self, result): 
    309338        """ 
     339        Send the fit complete signal to main thread 
     340        """ 
     341        self.fitCompleteSignal.emit(result) 
     342 
     343    def fitComplete(self, result): 
     344        """ 
    310345        Respond to the successful fit complete signal 
    311346        """ 
    312347        #re-enable the Fit button 
     348        self.cmdFit.setStyleSheet('QPushButton {color: black;}') 
    313349        self.cmdFit.setText("Fit") 
    314350        self.cmdFit.setEnabled(True) 
     
    347383    def onBatchFitComplete(self, result): 
    348384        """ 
     385        Send the fit complete signal to main thread 
     386        """ 
     387        self.batchCompleteSignal.emit(result) 
     388 
     389    def batchComplete(self, result): 
     390        """ 
    349391        Respond to the successful batch fit complete signal 
    350392        """ 
    351393        #re-enable the Fit button 
     394        self.cmdFit.setStyleSheet('QPushButton {color: black;}') 
    352395        self.cmdFit.setText("Fit") 
    353396        self.cmdFit.setEnabled(True) 
     
    375418    def onFitFailed(self, reason): 
    376419        """ 
     420        Send the fit failed signal to main thread 
     421        """ 
     422        self.fitFailedSignal.emit(result) 
     423 
     424    def fitFailed(self, reason): 
     425        """ 
    377426        Respond to fitting failure. 
    378427        """ 
    379428        #re-enable the Fit button 
     429        self.cmdFit.setStyleSheet('QPushButton {color: black;}') 
    380430        self.cmdFit.setText("Fit") 
    381431        self.cmdFit.setEnabled(True) 
     
    386436        msg = "Fitting failed: %s s.\n" % reason 
    387437        self.parent.communicate.statusBarUpdateSignal.emit(msg) 
    388   
     438 
    389439    def isTabImportable(self, tab): 
    390440        """ 
     
    599649            # Show the text in the constraint table 
    600650            item = self.uneditableItem(label) 
     651            item = QtWidgets.QTableWidgetItem(label) 
    601652            item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable) 
    602653            item.setCheckState(QtCore.Qt.Checked) 
     
    667718        return None 
    668719 
     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 
    669740    def showMultiConstraint(self): 
    670741        """ 
     
    672743        """ 
    673744        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 
    675753 
    676754        tab_list = [ObjectLibrary.getObject(self.tblTabList.item(s, 0).data(0)) for s in selected_rows] 
    677755        # Create and display the widget for param1 and param2 
    678756        cc_widget = ComplexConstraint(self, tabs=tab_list) 
     757        cc_widget.constraintReadySignal.connect(self.onAcceptConstraint) 
     758 
    679759        if cc_widget.exec_() != QtWidgets.QDialog.Accepted: 
    680760            return 
    681761 
    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  
    11import numpy 
     2import copy 
    23 
    34from PyQt5 import QtCore 
     
    1011import sas.qtgui.Utilities.LocalConfig as LocalConfig 
    1112import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary 
     13import sas.qtgui.Utilities.GuiUtils as GuiUtils 
    1214 
    1315from sas.qtgui.Perspectives.Fitting.FittingWidget import FittingWidget 
     
    3739        self.maxIndex = 1 
    3840 
    39         ## Index of the current tab 
    40         #self.currentTab = 0 
    41  
    4241        # The default optimizer 
    4342        self.optimizer = 'Levenberg-Marquardt' 
     
    8584        self.updateWindowTitle() 
    8685 
     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 
    8793    def updateWindowTitle(self): 
    8894        """ 
     
    112118    def onLatexCopy(self): 
    113119        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) 
    114178 
    115179    def closeEvent(self, event): 
     
    246310        for index_to_delete in index_list: 
    247311            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) 
    253318 
    254319    def allowBatch(self): 
    255320        """ 
    256321        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 
    257328        """ 
    258329        return True 
     
    337408        pass 
    338409 
     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 
    339419    @property 
    340420    def currentTab(self): 
  • src/sas/qtgui/Perspectives/Fitting/FittingWidget.py

    r7f41584 r21e71f1  
    5252TAB_POLY = 3 
    5353CATEGORY_DEFAULT = "Choose category..." 
     54MODEL_DEFAULT = "Choose model..." 
    5455CATEGORY_STRUCTURE = "Structure Factor" 
    5556CATEGORY_CUSTOM = "Plugin Models" 
     
    574575        # Signals from other widgets 
    575576        self.communicate.customModelDirectoryChanged.connect(self.onCustomModelChange) 
    576         self.communicate.saveAnalysisSignal.connect(self.savePageState) 
    577         #self.communicate.loadAnalysisSignal.connect(self.loadPageState) 
    578577        self.smearing_widget.smearingChangedSignal.connect(self.onSmearingOptionsUpdate) 
    579578 
     
    623622        to_string = "to its current value" if num_rows == 1 else "to their current values" 
    624623        has_constraints = any([self.rowHasConstraint(i) for i in rows]) 
     624        has_real_constraints = any([self.rowHasActiveConstraint(i) for i in rows]) 
    625625 
    626626        self.actionSelect = QtWidgets.QAction(self) 
     
    640640        self.actionRemoveConstraint.setText(QtCore.QCoreApplication.translate("self", "Remove constraint")) 
    641641 
     642        self.actionEditConstraint = QtWidgets.QAction(self) 
     643        self.actionEditConstraint.setObjectName("actionEditConstrain") 
     644        self.actionEditConstraint.setText(QtCore.QCoreApplication.translate("self", "Edit constraint")) 
     645 
    642646        self.actionMultiConstrain = QtWidgets.QAction(self) 
    643647        self.actionMultiConstrain.setObjectName("actionMultiConstrain") 
     
    654658        if has_constraints: 
    655659            menu.addAction(self.actionRemoveConstraint) 
     660            if num_rows == 1 and has_real_constraints: 
     661                menu.addAction(self.actionEditConstraint) 
    656662            #if num_rows == 1: 
    657663            #    menu.addAction(self.actionEditConstraint) 
     
    664670        self.actionConstrain.triggered.connect(self.addSimpleConstraint) 
    665671        self.actionRemoveConstraint.triggered.connect(self.deleteConstraint) 
     672        self.actionEditConstraint.triggered.connect(self.editConstraint) 
    666673        self.actionMutualMultiConstrain.triggered.connect(self.showMultiConstraint) 
    667674        self.actionSelect.triggered.connect(self.selectParameters) 
     
    701708        new_func = c_text.replace(param_used, updated_param_used) 
    702709        constraint.func = new_func 
     710        constraint.value_ex = updated_param_used 
    703711        # Which row is the constrained parameter in? 
    704712        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 
    705719 
    706720        # Create a new item and add the Constraint object as a child 
     
    799813        self.communicate.statusBarUpdateSignal.emit('Constraint added') 
    800814 
     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 
    801857    def deleteConstraint(self): 
    802858        """ 
     
    853909            return None 
    854910 
     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 
    855938    def rowHasConstraint(self, row): 
    856939        """ 
     
    10181101        Checks if the current model has magnetic scattering implemented 
    10191102        """ 
    1020         has_params = False 
     1103        has_mag_params = False 
    10211104        if self.kernel_module: 
    10221105            has_mag_params = len(self.kernel_module.magnetic_params) > 0 
     
    10291112        model = self.cbModel.currentText() 
    10301113 
     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 
    10311124        # Assure the control is active 
    10321125        if not self.cbModel.isEnabled(): 
     
    10351128        if not model: 
    10361129            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() 
    10371135 
    10381136        # Reset parameters to fit 
     
    12131311            self._model_model.clear() 
    12141312            return 
    1215  
     1313        # Wipe out the parameter model 
     1314        self._model_model.clear() 
    12161315        # Safely clear and enable the model combo 
    12171316        self.cbModel.blockSignals(True) 
     
    12251324        model_list = self.master_category_dict[category] 
    12261325        # Populate the models combobox 
     1326        self.cbModel.blockSignals(True) 
     1327        self.cbModel.addItem(MODEL_DEFAULT) 
    12271328        self.cbModel.addItems(sorted([model for (model, _) in model_list])) 
     1329        self.cbModel.blockSignals(False) 
    12281330 
    12291331    def onPolyModelChange(self, top, bottom): 
     
    19372039        Emits plotRequestedSignal for all plots found in the given model under the provided item name. 
    19382040        """ 
    1939         fitpage_name = "" if self.tab_id is None else "M"+str(self.tab_id) 
     2041        fitpage_name = self.kernel_module.name 
    19402042        plots = GuiUtils.plotsFromFilename(item_name, item_model) 
    19412043        # Has the fitted data been shown? 
     
    24252527    def isCheckable(self, row): 
    24262528        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) 
    24272538 
    24282539    def checkboxSelected(self, item): 
     
    34303541        return report_logic.reportList() 
    34313542 
    3432     def savePageState(self): 
    3433         """ 
    3434         Create and serialize local PageState 
    3435         """ 
    3436         filepath = self.saveAsAnalysisFile() 
    3437         if filepath is None or filepath == "": 
    3438             return 
    3439  
    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 filepath 
    3452         """ 
    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 filename 
    3466  
    34673543    def loadPageStateCallback(self,state=None, datainfo=None, format=None): 
    34683544        """ 
     
    35493625 
    35503626        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()]) 
    35553643        # option tab 
    35563644        param_list.append(['q_range_min', str(self.q_range_min)]) 
     
    35833671        """ 
    35843672        param_list = [] 
     3673        if self.kernel_module is None: 
     3674            return param_list 
     3675 
    35853676        param_list.append(['model_name', str(self.cbModel.currentText())]) 
    35863677 
     
    36223713            except: 
    36233714                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]) 
    36263728 
    36273729        def gatherPolyParams(row): 
     
    36873789        cb_text = cb.text() 
    36883790 
    3689         context = {} 
    36903791        lines = cb_text.split(':') 
    36913792        if lines[0] != 'sasview_parameter_values': 
     
    36993800                line_dict[content[0]] = content[1:] 
    37003801 
     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 
    37013825        model = line_dict['model_name'][0] 
    3702  
    3703         if 'model_name' not in line_dict.keys(): 
    3704             return False 
     3826        context = {} 
    37053827 
    37063828        if 'multiplicity' in line_dict.keys(): 
     
    37113833                self.updateMultiplicityCombo(multip) 
    37123834 
     3835        if 'tab_name' in line_dict.keys(): 
     3836            self.kernel_module.name = line_dict['tab_name'][0] 
    37133837        if 'polydisperse_params' in line_dict.keys(): 
    37143838            self.chkPolydispersity.setChecked(line_dict['polydisperse_params'][0]=='True') 
     
    38333957            ioffset = 0 
    38343958            joffset = 0 
    3835             if len(param_dict[param_name])>4: 
     3959            if len(param_dict[param_name])>5: 
    38363960                # error values are not editable - no need to update 
    38373961                ioffset = 1 
     
    38463970            except: 
    38473971                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) 
    38483988 
    38493989            self.setFocus() 
  • src/sas/qtgui/Perspectives/Fitting/MultiConstraint.py

    r305114c r09e0c32  
    2020    Logic class for interacting with MultiConstrainedUI view 
    2121    """ 
    22     def __init__(self, parent=None, params=None): 
     22    def __init__(self, parent=None, params=None, constraint=None): 
    2323        """ 
    2424        parent: ConstraintWidget object 
     
    3131        self.params = params 
    3232        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] 
    3348 
    3449        self.setupLabels() 
     
    3651 
    3752        # 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) 
    4057        self.cmdOK.clicked.connect(self.accept) 
    4158        self.cmdHelp.clicked.connect(self.onHelp) 
     
    5269        # Switch parameters 
    5370        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]) 
    5473        # Try to swap parameter names in the line edit 
    5574        current_text = self.txtConstraint.text() 
     
    6483        Setup labels based on current parameters 
    6584        """ 
    66         l1 = self.params[0] 
    67         l2 = self.params[1] 
     85        l1 = str(self.params[0]) 
     86        l2 = str(self.params[1]) 
    6887        self.txtParam1.setText(l1) 
    6988        self.txtParam1_2.setText(l1) 
     
    82101        Add visual cues when formula is incorrect 
    83102        """ 
     103        # Don't validate if requested 
     104        if not self.validate: return 
     105 
    84106        formula_is_valid = False 
    85107        formula_is_valid = self.validateConstraint(self.txtConstraint.text()) 
     
    99121            return False 
    100122 
    101         param_str = str(self.params[1]) 
    102         constraint_text = constraint_text.strip() 
     123        param_str = self.model_name 
    103124 
    104125        # 1. just the parameter 
  • src/sas/qtgui/Perspectives/Fitting/UI/ComplexConstraintUI.ui

    r1738173 recc5d043  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>367</width> 
    10     <height>199</height> 
     9    <width>478</width> 
     10    <height>257</height> 
    1111   </rect> 
    1212  </property> 
     
    6969           </sizepolicy> 
    7070          </property> 
     71          <property name="sizeAdjustPolicy"> 
     72           <enum>QComboBox::AdjustToContents</enum> 
     73          </property> 
    7174          <item> 
    7275           <property name="text"> 
     
    100103           </sizepolicy> 
    101104          </property> 
     105          <property name="sizeAdjustPolicy"> 
     106           <enum>QComboBox::AdjustToContents</enum> 
     107          </property> 
    102108          <item> 
    103109           <property name="text"> 
     
    161167       <property name="sizeHint" stdset="0"> 
    162168        <size> 
    163          <width>40</width> 
     169         <width>88</width> 
    164170         <height>20</height> 
    165171        </size> 
     
    168174     </item> 
    169175     <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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Add the constraint as defined by the above expression.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     185       </property> 
    171186       <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"> 
    175196        <bool>false</bool> 
    176197       </property> 
     198       <property name="arrowType"> 
     199        <enum>Qt::DownArrow</enum> 
     200       </property> 
    177201      </widget> 
    178202     </item> 
    179203     <item> 
    180204      <widget class="QPushButton" name="cmdCancel"> 
     205       <property name="toolTip"> 
     206        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Close the window.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 
     207       </property> 
    181208       <property name="text"> 
    182         <string>Cancel</string> 
     209        <string>Close</string> 
    183210       </property> 
    184211      </widget> 
     
    197224 <resources/> 
    198225 <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> 
    215226  <connection> 
    216227   <sender>cmdCancel</sender> 
  • src/sas/qtgui/Perspectives/Fitting/UI/ConstraintWidgetUI.ui

    r91ad45c recc5d043  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>428</width> 
    10     <height>457</height> 
     9    <width>597</width> 
     10    <height>607</height> 
    1111   </rect> 
    1212  </property> 
     
    1414   <string>Constrained and Simultaneous Fit</string> 
    1515  </property> 
    16   <layout class="QGridLayout" name="gridLayout"> 
     16  <layout class="QGridLayout" name="gridLayout_2"> 
    1717   <item row="0" column="0"> 
    1818    <widget class="QGroupBox" name="groupBox"> 
     
    8989      <string>Constraints</string> 
    9090     </property> 
    91      <layout class="QGridLayout" name="gridLayout_2"> 
     91     <layout class="QGridLayout" name="gridLayout"> 
    9292      <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"> 
    10396          <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> 
    107102          </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> 
    108135         </widget> 
    109136        </item> 
     
    160187        </size> 
    161188       </property> 
     189       <property name="toolTip"> 
     190        <string>Perform simultaneous fitting of selected fit pages.</string> 
     191       </property> 
    162192       <property name="text"> 
    163193        <string>Fit</string> 
     
    179209        </size> 
    180210       </property> 
     211       <property name="toolTip"> 
     212        <string>Display help on constrained and simultaneous fitting.</string> 
     213       </property> 
    181214       <property name="text"> 
    182215        <string>Help</string> 
  • src/sas/qtgui/Perspectives/Fitting/UI/MultiConstraintUI.ui

    r1738173 recc5d043  
    1010    <x>0</x> 
    1111    <y>0</y> 
    12     <width>369</width> 
    13     <height>201</height> 
     12    <width>435</width> 
     13    <height>233</height> 
    1414   </rect> 
    1515  </property> 
     
    184184 <connections> 
    185185  <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> 
    186202   <sender>cmdOK</sender> 
    187203   <signal>clicked()</signal> 
     
    199215   </hints> 
    200216  </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> 
    217217 </connections> 
    218218</ui> 
  • src/sas/qtgui/Perspectives/Fitting/UnitTesting/ComplexConstraintTest.py

    r725d9c06 rd72ac57  
    3434        category_index = self.tab1.cbCategory.findText("Shape Independent") 
    3535        self.tab1.cbCategory.setCurrentIndex(category_index) 
     36        model_index = self.tab1.cbModel.findText("be_polyelectrolyte") 
     37        self.tab1.cbModel.setCurrentIndex(model_index) 
     38 
    3639        category_index = self.tab2.cbCategory.findText("Cylinder") 
    3740        self.tab2.cbCategory.setCurrentIndex(category_index) 
     41        model_index = self.tab2.cbModel.findText("barbell") 
     42        self.tab2.cbModel.setCurrentIndex(model_index) 
    3843 
    3944        tabs = [self.tab1, self.tab2] 
     
    127132        """ 
    128133        # 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') 
    130137 
    131138        # 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, '>=') 
    135145 
    136146    def testOnHelp(self): 
  • src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py

    rec4a143 rbaeac95  
    175175        category_index = self.widget.cbCategory.findText("Shape Independent") 
    176176        self.widget.cbCategory.setCurrentIndex(category_index) 
     177        model_index = self.widget.cbModel.findText("be_polyelectrolyte") 
     178        self.widget.cbModel.setCurrentIndex(model_index) 
    177179 
    178180        # test the model combo content 
    179         self.assertEqual(self.widget.cbModel.count(), 28) 
     181        self.assertEqual(self.widget.cbModel.count(), 30) 
    180182 
    181183        # Try to change back to default 
     
    183185 
    184186        # 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) 
    187189 
    188190        # Set the structure factor 
     
    201203        category_index = self.widget.cbCategory.findText("Shape Independent") 
    202204        self.widget.cbCategory.setCurrentIndex(category_index) 
     205        model_index = self.widget.cbModel.findText("be_polyelectrolyte") 
     206        self.widget.cbModel.setCurrentIndex(model_index) 
    203207 
    204208        # check the enablement of controls 
     
    215219        #  
    216220        # Now change the model 
    217         self.widget.cbModel.setCurrentIndex(2) 
     221        self.widget.cbModel.setCurrentIndex(4) 
    218222        self.assertEqual(self.widget.cbModel.currentText(),'dab') 
    219223 
     
    226230        self.widget.data_is_loaded = True 
    227231        # Reset the sasmodel index 
    228         self.widget.cbModel.setCurrentIndex(1) 
     232        self.widget.cbModel.setCurrentIndex(2) 
    229233        self.assertEqual(self.widget.cbModel.currentText(),'broad_peak') 
    230234 
     
    377381        category_index = self.widget.cbCategory.findText("Shape Independent") 
    378382        self.widget.cbCategory.setCurrentIndex(category_index) 
     383        model_index = self.widget.cbModel.findText("be_polyelectrolyte") 
     384        self.widget.cbModel.setCurrentIndex(model_index) 
     385 
    379386        # Check the poly model 
    380387        self.assertEqual(self.widget._poly_model.rowCount(), 0) 
     
    383390        # Change the category index so we have a model available 
    384391        self.widget.cbCategory.setCurrentIndex(2) 
     392        self.widget.cbModel.setCurrentIndex(1) 
    385393 
    386394        # Check the poly model 
     
    556564        category_index = self.widget.cbCategory.findText("Sphere") 
    557565        self.widget.cbCategory.setCurrentIndex(category_index) 
     566        model_index = self.widget.cbModel.findText("adsorbed_layer") 
     567        self.widget.cbModel.setCurrentIndex(model_index) 
    558568 
    559569        # Check the magnetic model 
     
    634644        category_index = self.widget.cbCategory.findText("Sphere") 
    635645        self.widget.cbCategory.setCurrentIndex(category_index) 
     646        model_index = self.widget.cbModel.findText("adsorbed_layer") 
     647        self.widget.cbModel.setCurrentIndex(model_index) 
    636648 
    637649        # Check the enablement/text 
     
    973985        category_index = self.widget.cbCategory.findText("Sphere") 
    974986        self.widget.cbCategory.setCurrentIndex(category_index) 
     987        model_index = self.widget.cbModel.findText("adsorbed_layer") 
     988        self.widget.cbModel.setCurrentIndex(model_index) 
    975989        self.widget.main_params_to_fit = ['scale'] 
    976990 
     
    9861000        self.assertListEqual(fp.main_params_to_fit, ['scale']) 
    9871001 
    988     def testPushFitPage(self): 
     1002    def notestPushFitPage(self): 
    9891003        """ 
    9901004        Push current state of fitpage onto stack 
     
    9971011        self.widget.data = item 
    9981012        category_index = self.widget.cbCategory.findText("Sphere") 
     1013        model_index = self.widget.cbModel.findText("adsorbed_layer") 
     1014        self.widget.cbModel.setCurrentIndex(model_index) 
    9991015 
    10001016        # Asses the initial state of stack 
  • src/sas/qtgui/Plotting/Plotter2D.py

    rc30822c r21e71f1  
    330330        self.manager.communicator.plotUpdateSignal.emit([new_plot]) 
    331331 
     332        self.manager.communicator.forcePlotDisplaySignal.emit([item, new_plot]) 
     333 
     334        # Show the plot 
     335 
    332336    def setSlicer(self, slicer): 
    333337        """ 
  • src/sas/qtgui/Utilities/GuiUtils.py

    r67346f9 r133812c7  
    1111import webbrowser 
    1212import urllib.parse 
     13import json 
     14from io import BytesIO 
    1315 
    1416import numpy as np 
     
    2628from sas.qtgui.Plotting.PlotterData import Data1D 
    2729from sas.qtgui.Plotting.PlotterData import Data2D 
     30from sas.qtgui.Plotting.Plottables import Plottable 
     31from sas.sascalc.dataloader.data_info import Sample, Source, Vector 
     32from sas.qtgui.Plotting.Plottables import View 
     33from sas.qtgui.Plotting.Plottables import PlottableTheory1D 
     34from sas.qtgui.Plotting.Plottables import PlottableFit1D 
     35from sas.qtgui.Plotting.Plottables import Text 
     36from sas.qtgui.Plotting.Plottables import Chisq 
     37from sas.qtgui.MainWindow.DataState import DataState 
     38 
    2839from sas.sascalc.dataloader.loader import Loader 
    2940from sas.qtgui.Utilities import CustomDir 
     
    259270    sendDataToGridSignal = QtCore.pyqtSignal(list) 
    260271 
    261     # Action Save Analysis triggered 
    262     saveAnalysisSignal = QtCore.pyqtSignal() 
    263  
    264272    # Mask Editor requested 
    265273    maskEditorSignal = QtCore.pyqtSignal(Data2D) 
     
    292300    forcePlotDisplaySignal = QtCore.pyqtSignal(list) 
    293301 
    294 def updateModelItemWithPlot(item, update_data, name=""): 
     302def updateModelItemWithPlot(item, update_data, name="", checkbox_state=None): 
    295303    """ 
    296304    Adds a checkboxed row named "name" to QStandardItem 
     
    317325            # Force redisplay 
    318326            return 
    319  
    320327    # Create the new item 
    321328    checkbox_item = createModelItemWithPlot(update_data, name) 
    322329 
     330    if checkbox_state is not None: 
     331        checkbox_item.setCheckState(checkbox_state) 
    323332    # Append the new row to the main item 
    324333    item.appendRow(checkbox_item) 
     
    571580    if isinstance(data.process, list) and data.process: 
    572581        for process in data.process: 
     582            if process is None: 
     583                continue 
    573584            process_date = process.date 
    574585            process_date_item = QtGui.QStandardItem("Date: " + process_date) 
     
    11451156    return result 
    11461157 
     1158def 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 
     1203def 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 
     1297def 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 
     1318def 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 
    11471420 
    11481421def enum(*sequential, **named): 
  • src/sas/sascalc/dataloader/readers/cansas_reader.py

    rb8080e1 rb1b71ad  
    184184            if CANSAS_NS.get(self.cansas_version).get("ns") == value.rsplit(" ")[0]: 
    185185                return True 
    186         if ext == "svs": 
     186        if ext == ".svs": 
    187187            return True # Why is this required? 
    188188        # If we get to this point then file isn't valid CanSAS 
  • src/sas/sascalc/fit/pagestate.py

    rb8080e1 rb1b71ad  
    12491249 
    12501250            else: 
    1251                 self.call_back(format=ext) 
     1251                #self.call_back(format=ext) 
    12521252                raise RuntimeError("%s is not a file" % path) 
    12531253 
    12541254            # Return output consistent with the loader's api 
    12551255            if len(output) == 0: 
    1256                 self.call_back(state=None, datainfo=None, format=ext) 
     1256                #self.call_back(state=None, datainfo=None, format=ext) 
    12571257                return None 
    12581258            else: 
     1259                states=[] 
    12591260                for data in output: 
    12601261                    # Call back to post the new state 
     
    12811282                        if isinstance(data.run_name, dict): 
    12821283                            # 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] 
    12841285                        else: 
    12851286                            name = data.run_name 
     
    12891290                    state.version = fitstate.version 
    12901291                    # store state in fitting 
    1291                     self.call_back(state=state, datainfo=data, format=ext) 
     1292                    #self.call_back(state=state, datainfo=data, format=ext) 
    12921293                    self.state = state 
     1294                    states.append(state) 
    12931295                simfitstate = self._parse_simfit_state(entry) 
    12941296                if simfitstate is not None: 
    1295                     self.call_back(state=simfitstate) 
    1296  
    1297                 return output 
     1297                    #self.call_back(state=simfitstate) 
     1298                    states.append(simfitstate) 
     1299                return (output, states) 
    12981300        except: 
    1299             self.call_back(format=ext) 
     1301            #self.call_back(format=ext) 
    13001302            raise 
    13011303 
  • docs/sphinx-docs/build_sphinx.py

    rf1b696d r5f9e874  
    3030SPHINX_BUILD = joinpath(SPHINX_ROOT, "build") 
    3131SPHINX_SOURCE = joinpath(SPHINX_ROOT, "source-temp") 
    32 SPHINX_PERSPECTIVES = joinpath(SPHINX_SOURCE, "user", "qtgui", "perspectives") 
     32SPHINX_PERSPECTIVES = joinpath(SPHINX_SOURCE, "user", "qtgui", "Perspectives") 
    3333 
    3434# sasview paths 
     
    5151SASMODELS_DEV_TARGET = joinpath(SPHINX_SOURCE, "dev", "sasmodels-dev") 
    5252SASMODELS_GUIDE_SOURCE = joinpath(SASMODELS_DOCS, "guide") 
    53 SASMODELS_GUIDE_TARGET = joinpath(SPHINX_PERSPECTIVES, "fitting") 
     53SASMODELS_GUIDE_TARGET = joinpath(SPHINX_PERSPECTIVES, "Fitting") 
    5454SASMODELS_GUIDE_EXCLUDE = [ 
    5555    "index.rst", "install.rst", "intro.rst", 
     
    5959BUMPS_DOCS = joinpath(SASVIEW_ROOT, "..", "bumps", "doc") 
    6060BUMPS_SOURCE = joinpath(BUMPS_DOCS, "guide") 
    61 BUMPS_TARGET = joinpath(SPHINX_PERSPECTIVES, "fitting") 
     61BUMPS_TARGET = joinpath(SPHINX_PERSPECTIVES, "Fitting") 
    6262 
    6363run = imp.load_source('run', joinpath(SASVIEW_ROOT, 'run.py')) 
  • src/sas/qtgui/Calculators/GenericScatteringCalculator.py

    r133812c7 r8c85ac1  
    661661            data.xaxis('\\rm{Q_{x}}', '\AA^{-1}') 
    662662            data.yaxis('\\rm{Intensity}', 'cm^{-1}') 
    663             plot1D = Plotter(self, quickplot=True) 
    664             plot1D.plot(data) 
    665             plot1D.show() 
     663 
    666664            self.graph_num += 1 
    667             # TODO 
    668             print('TRANSFER OF DATA TO MAIN PANEL TO BE IMPLEMENTED') 
    669             return plot1D 
    670665        else: 
    671666            numpy.nan_to_num(self.data_to_plot) 
     
    679674            data.title = "GenSAS {}  #{} 2D".format(self.file_name, 
    680675                                                    int(self.graph_num)) 
    681             plot2D = Plotter2D(self, quickplot=True) 
    682             plot2D.plot(data) 
    683             plot2D.show() 
     676            zeros = numpy.ones(data.data.size, dtype=bool) 
     677            data.mask = zeros 
     678 
    684679            self.graph_num += 1 
    685680            # TODO 
    686             print('TRANSFER OF DATA TO MAIN PANEL TO BE IMPLEMENTED') 
    687             return plot2D 
    688  
     681        new_item = GuiUtils.createModelItemWithPlot(data, name=data.title) 
     682        self.communicator.updateModelFromPerspectiveSignal.emit(new_item) 
     683        self.communicator.forcePlotDisplaySignal.emit([new_item, data]) 
    689684 
    690685class Plotter3DWidget(PlotterBase): 
  • src/sas/qtgui/MainWindow/DroppableDataLoadWidget.py

    r53c771e rba400d1  
    11# global 
     2import os 
    23from PyQt5 import QtCore 
    34from PyQt5 import QtGui 
     
    5859            filenames=[] 
    5960            for url in event.mimeData().urls(): 
    60                 filenames.append(url.toLocalFile()) 
     61                files = url.toLocalFile() 
     62                if os.path.isdir(files): 
     63                # get content of dir into a list 
     64                    content = [os.path.join(os.path.abspath(files), filename) 
     65                                for filename in os.listdir(files)] 
     66                    filenames += content 
     67                else: 
     68                    filenames.append(files) 
    6169            self.communicator.fileReadSignal.emit(filenames) 
    6270            event.accept() 
  • src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py

    r65759c7 r7f41584  
    368368            cbox = createFixedChoiceComboBox(par, row) 
    369369 
     370            # Apply combobox if required 
     371            if None not in (view, cbox): 
     372                # set the min/max cell to be empty 
     373                item3.setText("") 
     374                item4.setText("") 
     375 
    370376            # Always add to the model 
    371377            if row_num is None: 
     
    375381                row_num += 1 
    376382 
    377             # Apply combobox if required 
    378             if None not in (view, cbox): 
     383            if cbox is not None: 
    379384                view.setIndexWidget(item2.index(), cbox) 
    380385 
  • src/sas/qtgui/Perspectives/Fitting/ViewDelegate.py

    r722b7d6 r7f41584  
    9595            # Set some columns uneditable 
    9696            return None 
     97        if index.column() in (self.param_min, self.param_max): 
     98            # Check if the edit role is set 
     99            if not (index.flags() & QtCore.Qt.ItemIsEditable): 
     100                return None 
    97101 
    98102        return super(ModelViewDelegate, self).createEditor(widget, option, index) 
  • src/sas/qtgui/Perspectives/Fitting/media/fitting_help.rst

    r00a40bd r3b0b17e  
    125125 
    126126For a complete list of all the library models available in SasView, see 
    127 the `Model Documentation <../../../index.html>`_ . 
     127the `Model Documentation <models/index.html>`_ . 
    128128 
    129129It is also possible to add your own models. 
  • src/sas/qtgui/Plotting/Plotter.py

    r3b95b3b rc30822c  
    600600        except: 
    601601            self.position = None 
     602 
     603        x_str = GuiUtils.formatNumber(self.x_click) 
     604        y_str = GuiUtils.formatNumber(self.y_click) 
     605        coord_str = "x: {}, y: {}".format(x_str, y_str) 
     606        self.manager.communicator.statusBarUpdateSignal.emit(coord_str) 
    602607 
    603608    def onMplMouseUp(self, event): 
  • src/sas/qtgui/Utilities/GridPanel.py

    r75906a1 rc4c4957  
    126126        return 
    127127 
    128     def addTabPage(self): 
     128    def addTabPage(self, name=None): 
    129129        """ 
    130130        Add new tab page with QTableWidget 
     
    141141        # However, some models have LONG names, which doesn't look well on the tab bar. 
    142142        self.tab_number += 1 
    143         tab_name = "Tab " + str(self.tab_number) 
     143        if name is not None: 
     144            tab_name = name 
     145        else: 
     146            tab_name = "Tab " + str(self.tab_number) 
    144147        # each table needs separate slots. 
    145148        tab_widget.customContextMenuRequested.connect(self.showContextMenu) 
     
    153156        Create a new tab with batch fitting results 
    154157        """ 
     158        # pull out page name from results 
     159        page_name = None 
     160        if len(results)>=2: 
     161            if isinstance(results[-1], str): 
     162                page_name = results[-1] 
     163                _ = results.pop(-1) 
     164 
    155165        if self.has_data: 
    156             self.addTabPage() 
     166            self.addTabPage(name=page_name) 
     167        else: 
     168            self.tabWidget.setTabText(0, page_name) 
    157169        # Update the new widget 
    158170        # Fill in the table from input data in the last/newest page 
  • src/sas/sascalc/dataloader/file_reader_base_class.py

    rb8080e1 rbe7c981  
    276276                    dataset.xmax = np.max(dataset.qx_data) 
    277277                    dataset.ymin = np.min(dataset.qy_data) 
    278                     dataset.ymax = np.max(dataset.qx_data) 
     278                    dataset.ymax = np.max(dataset.qy_data) 
    279279 
    280280    def format_unit(self, unit=None): 
  • src/sas/sascalc/pr/fit/BumpsFitting.py

    r9a5097c r57a91fc  
    22BumpsFitting module runs the bumps optimizer. 
    33""" 
     4from __future__ import division 
     5 
    46import os 
    57from datetime import timedelta, datetime 
     
    3436class Progress(object): 
    3537    def __init__(self, history, max_step, pars, dof): 
    36         remaining_time = int(history.time[0]*(float(max_step)/history.step[0]-1)) 
     38        remaining_time = int(history.time[0]*(max_step/history.step[0]-1)) 
    3739        # Depending on the time remaining, either display the expected 
    3840        # time of completion, or the amount of time remaining.  Use precision 
  • src/sas/sascalc/pr/fit/Loader.py

    r574adc7 r57a91fc  
     1""" 
     2class Loader  to load any kind of file 
     3""" 
     4 
    15from __future__ import print_function 
    26 
    3 # class Loader  to load any king of file 
    4 #import wx 
    5 #import string 
    67import numpy as np 
    78 
  • src/sas/sascalc/pr/invertor.py

    raed159f r57a91fc  
    66FIXME: The way the Invertor interacts with its C component should be cleaned up 
    77""" 
     8from __future__ import division 
    89 
    910import numpy as np 
     
    423424            A[i][j] = (Fourier transformed base function for point j) 
    424425 
    425         We them choose a number of r-points, n_r, to evaluate the second 
     426        We then choose a number of r-points, n_r, to evaluate the second 
    426427        derivative of P(r) at. This is used as our regularization term. 
    427428        For a vector r of length n_r, the following n_r rows are set to :: 
     
    480481 
    481482        # Perform the inversion (least square fit) 
    482         c, chi2, _, _ = lstsq(a, b) 
     483        c, chi2, _, _ = lstsq(a, b, rcond=-1) 
    483484        # Sanity check 
    484485        try: 
     
    503504        try: 
    504505            cov = np.linalg.pinv(inv_cov) 
    505             err = math.fabs(chi2 / float(npts - nfunc)) * cov 
    506         except: 
     506            err = math.fabs(chi2 / (npts - nfunc)) * cov 
     507        except Exception as exc: 
    507508            # We were not able to estimate the errors 
    508509            # Return an empty error matrix 
     
    548549        try: 
    549550            return estimator.num_terms(isquit_func) 
    550         except: 
     551        except Exception as exc: 
    551552            # If we fail, estimate alpha and return the default 
    552553            # number of terms 
  • src/sas/sascalc/pr/num_term.py

    rb8080e1 r57a91fc  
    1 from __future__ import print_function 
     1from __future__ import print_function, division 
    22 
    33import math 
     
    5151        osc = self.sort_osc() 
    5252        dv = len(osc) 
    53         med = float(dv) / 2.0 
     53        med = 0.5*dv 
    5454        odd = self.is_odd(dv) 
    5555        medi = 0 
     
    140140            nts = self.compare_err() 
    141141            div = len(nts) 
    142             tem = float(div) / 2.0 
     142            tem = 0.5*div 
    143143            if self.is_odd(div): 
    144144                nt = nts[int(tem)] 
Note: See TracChangeset for help on using the changeset viewer.