Changes in / [80468f6:3c6ecd9] in sasview


Ignore:
Files:
3 added
18 edited

Legend:

Unmodified
Added
Removed
  • .gitignore

    r948484f r846a063  
    2929default_categories.json 
    3030/setup.cfg 
     31**/UI/*.py 
     32!**/UI/__init__.py 
    3133 
    3234# doc build 
  • run.py

    ra3221b6 ra3221b6  
    141141    sys.path.append(build_path) 
    142142 
     143    # Run the UI conversion tool 
     144    import sas.qtgui.convertUI 
     145 
     146 
    143147if __name__ == "__main__": 
    144148    # Need to add absolute path before actual prepare call, 
  • src/sas/qtgui/Perspectives/Inversion/DMaxExplorerWidget.py

    rf4480f0 rb0ba43e  
    88 
    99# global 
    10 import sys 
    11 import os 
    1210import logging 
    1311import numpy as np 
     
    4240        super(DmaxWindow, self).__init__() 
    4341        self.setupUi(self) 
     42        self.parent = parent 
    4443 
    4544        self.setWindowTitle("Dₐₓ Explorer") 
     
    5049 
    5150        self.plot = PlotterWidget(self, self) 
    52         self.hasPlot = None 
     51        self.hasPlot = False 
    5352        self.verticalLayout.insertWidget(0, self.plot) 
    5453 
    5554        # Let's choose the Standard Item Model. 
    5655        self.model = QtGui.QStandardItemModel(self) 
    57         self.mapper = None 
     56        self.mapper = QtWidgets.QDataWidgetMapper(self) 
    5857 
    5958        # Add validators on line edits 
    6059        self.setupValidators() 
    6160 
    62         # # Connect buttons to slots. 
    63         # # Needs to be done early so default values propagate properly. 
     61        # Connect buttons to slots. 
     62        # Needs to be done early so default values propagate properly. 
    6463        self.setupSlots() 
    6564 
     
    8281 
    8382    def setupModel(self): 
     83        self.model.blockSignals(True) 
    8484        self.model.setItem(W.NPTS, QtGui.QStandardItem(str(self.nfunc))) 
     85        self.model.blockSignals(False) 
     86        self.model.blockSignals(True) 
    8587        self.model.setItem(W.DMIN, QtGui.QStandardItem("{:.1f}".format(0.9*self.pr_state.d_max))) 
     88        self.model.blockSignals(False) 
     89        self.model.blockSignals(True) 
    8690        self.model.setItem(W.DMAX, QtGui.QStandardItem("{:.1f}".format(1.1*self.pr_state.d_max))) 
     91        self.model.blockSignals(False) 
     92        self.model.blockSignals(True) 
    8793        self.model.setItem(W.VARIABLE, QtGui.QStandardItem( "χ²/dof")) 
     94        self.model.blockSignals(False) 
    8895 
    8996    def setupMapper(self): 
    90         self.mapper = QtWidgets.QDataWidgetMapper(self) 
    9197        self.mapper.setOrientation(QtCore.Qt.Vertical) 
    9298        self.mapper.setModel(self.model) 
     
    111117        chi2 = [] 
    112118 
    113         xs = np.linspace(float(self.model.item(W.DMIN).text()), 
    114                          float(self.model.item(W.DMAX).text()), 
    115                          float(self.model.item(W.NPTS).text())) 
     119        try: 
     120            dmin = float(self.model.item(W.DMIN).text()) 
     121            dmax = float(self.model.item(W.DMAX).text()) 
     122            npts = float(self.model.item(W.NPTS).text()) 
     123            xs = np.linspace(dmin, dmax, npts) 
     124        except ValueError as e: 
     125            msg = ("An input value is not correctly formatted. Please check {}" 
     126                   .format(e.message)) 
     127            logger.error(msg) 
    116128 
    117129        original = self.pr_state.d_max 
     
    132144                msg = "ExploreDialog: inversion failed " 
    133145                msg += "for D_max=%s\n%s" % (str(x), ex) 
    134                 print(msg) 
    135146                logger.error(msg) 
    136147 
     
    142153            msg = "ExploreDialog: inversion failed " 
    143154            msg += "for D_max=%s\n%s" % (str(x), ex) 
    144             print(msg) 
    145155            logger.error(msg) 
    146156 
     
    151161        if plotter == "χ²/dof": 
    152162            ys = chi2 
    153             y_label = "\chi^2/dof" 
     163            y_label = "\\chi^2/dof" 
    154164            y_unit = "a.u." 
    155165        elif plotter == "I(Q=0)": 
    156166            ys = iq0 
    157167            y_label = "I(q=0)" 
    158             y_unit = "\AA^{-1}" 
     168            y_unit = "\\AA^{-1}" 
    159169        elif plotter == "Rg": 
    160170            ys = rg 
    161171            y_label = "R_g" 
    162             y_unit = "\AA" 
     172            y_unit = "\\AA" 
    163173        elif plotter == "Oscillation parameter": 
    164174            ys = osc 
     
    168178            ys = bck 
    169179            y_label = "Bckg" 
    170             y_unit = "\AA^{-1}" 
     180            y_unit = "\\AA^{-1}" 
    171181        elif plotter == "Positive Fraction": 
    172182            ys = pos 
     
    175185        else: 
    176186            ys = pos_err 
    177             y_label = "P^{+}_{1\sigma}" 
     187            y_label = "P^{+}_{1\\sigma}" 
    178188            y_unit = "a.u." 
    179189 
     
    188198        data._yunit = y_unit 
    189199        self.plot.plot(data=data, marker="-") 
     200 
     201    def closeEvent(self, event): 
     202        """Override close event""" 
     203        self.parent.dmaxWindow = None 
     204        event.accept() 
  • src/sas/qtgui/Perspectives/Inversion/InversionLogic.py

    • Property mode changed from 100755 to 100644
    rfa81e94 r6da860a  
    11import math 
    2 import pylab 
     2import logging 
    33import numpy as np 
    44 
     
    1212GROUP_ID_IQ_DATA = r"$I_{obs}(q)$" 
    1313GROUP_ID_PR_FIT = r"$P_{fit}(r)$" 
     14PR_PLOT_PTS = 51 
     15 
     16logger = logging.getLogger(__name__) 
    1417 
    1518 
     
    1922    No QStandardModelIndex here. 
    2023    """ 
    21  
    22     # TODO: Add way to change this value 
    23     _pr_n_pts = 51 
    2424 
    2525    def __init__(self, data=None): 
     
    3737        """ data setter """ 
    3838        self._data = value 
    39         self.data_is_loaded = True 
     39        self.data_is_loaded = (self._data is not None) 
    4040 
    4141    def isLoadedData(self): 
     
    6363            maxq = pr.q_max 
    6464 
    65         x = pylab.arange(minq, maxq, maxq / 301.0) 
     65        x = np.arange(minq, maxq, maxq / 301.0) 
    6666        y = np.zeros(len(x)) 
    6767        err = np.zeros(len(x)) 
     
    7373            except: 
    7474                err[i] = 1.0 
    75                 print(("Error getting error", value, x[i])) 
     75                logger.log(("Error getting error", value, x[i])) 
    7676 
    7777        new_plot = Data1D(x, y) 
     
    8989        # If we have used slit smearing, plot the smeared I(q) too 
    9090        if pr.slit_width > 0 or pr.slit_height > 0: 
    91             x = pylab.arange(minq, maxq, maxq / 301.0) 
     91            x = np.arange(minq, maxq, maxq / 301.0) 
    9292            y = np.zeros(len(x)) 
    9393            err = np.zeros(len(x)) 
     
    9999                except: 
    100100                    err[i] = 1.0 
    101                     print(("Error getting error", value, x[i])) 
     101                    logger.log(("Error getting error", value, x[i])) 
    102102 
    103103            new_plot = Data1D(x, y) 
     
    113113        return new_plot 
    114114 
    115     def update1DPlot(self, plot, out, pr, q=None): 
    116         """ 
    117         Create a new 1D data instance based on fitting results 
    118         """ 
    119  
    120         qtemp = pr.x 
    121         if q is not None: 
    122             qtemp = q 
    123  
    124         # Make a plot 
    125         maxq = max(qtemp) 
    126  
    127         minq = min(qtemp) 
    128  
    129         # Check for user min/max 
    130         if pr.q_min is not None and maxq >= pr.q_min >= minq: 
    131             minq = pr.q_min 
    132         if pr.q_max is not None and maxq >= pr.q_max >= minq: 
    133             maxq = pr.q_max 
    134  
    135         x = pylab.arange(minq, maxq, maxq / 301.0) 
    136         y = np.zeros(len(x)) 
    137         err = np.zeros(len(x)) 
    138         for i in range(len(x)): 
    139             value = pr.iq(out, x[i]) 
    140             y[i] = value 
    141             try: 
    142                 err[i] = math.sqrt(math.fabs(value)) 
    143             except: 
    144                 err[i] = 1.0 
    145                 print(("Error getting error", value, x[i])) 
    146  
    147         plot.x = x 
    148         plot.y = y 
    149  
    150         # If we have used slit smearing, plot the smeared I(q) too 
    151         if pr.slit_width > 0 or pr.slit_height > 0: 
    152             x = pylab.arange(minq, maxq, maxq / 301.0) 
    153             y = np.zeros(len(x)) 
    154             err = np.zeros(len(x)) 
    155             for i in range(len(x)): 
    156                 value = pr.iq_smeared(pr.out, x[i]) 
    157                 y[i] = value 
    158                 try: 
    159                     err[i] = math.sqrt(math.fabs(value)) 
    160                 except: 
    161                     err[i] = 1.0 
    162                     print(("Error getting error", value, x[i])) 
    163  
    164             plot.x = x 
    165             plot.y = y 
    166  
    167         return plot 
    168  
    169115    def newPRPlot(self, out, pr, cov=None): 
    170116        """ 
    171117        """ 
    172118        # Show P(r) 
    173         x = pylab.arange(0.0, pr.d_max, pr.d_max / self._pr_n_pts) 
     119        x = np.arange(0.0, pr.d_max, pr.d_max / PR_PLOT_PTS) 
    174120 
    175121        y = np.zeros(len(x)) 
     
    193139            y[i] = value 
    194140 
    195         # if self._normalize_output == True: 
    196         #     y = y / total 
    197         #     dy = dy / total 
    198         # elif self._scale_output_unity == True: 
    199         #     y = y / pmax 
    200         #     dy = dy / pmax 
    201  
    202141        if cov2 is None: 
    203142            new_plot = Data1D(x, y) 
     
    209148        new_plot.title = "P(r) fit" 
    210149        new_plot.id = PR_FIT_LABEL 
    211         # Make sure that the plot is linear 
    212         new_plot.xtransform = "x" 
    213         new_plot.ytransform = "y" 
     150        new_plot.scale = "linear" 
    214151        new_plot.group_id = GROUP_ID_PR_FIT 
    215152 
    216153        return new_plot 
    217  
    218     def updatePRPlot(self, plot, out, pr, cov=None): 
    219         x = pylab.arange(0.0, pr.d_max, pr.d_max / self._pr_n_pts) 
    220  
    221         y = np.zeros(len(x)) 
    222         dy = np.zeros(len(x)) 
    223  
    224         total = 0.0 
    225         pmax = 0.0 
    226         cov2 = np.ascontiguousarray(cov) 
    227  
    228         for i in range(len(x)): 
    229             if cov2 is None: 
    230                 value = pr.pr(out, x[i]) 
    231             else: 
    232                 (value, dy[i]) = pr.pr_err(out, cov2, x[i]) 
    233             total += value * pr.d_max / len(x) 
    234  
    235             # keep track of the maximum P(r) value 
    236             if value > pmax: 
    237                 pmax = value 
    238  
    239             y[i] = value 
    240  
    241         # if self._normalize_output == True: 
    242         #     y = y / total 
    243         #     dy = dy / total 
    244         # elif self._scale_output_unity == True: 
    245         #     y = y / pmax 
    246         #     dy = dy / pmax 
    247         plot.x = x 
    248         plot.y = y 
    249  
    250         if cov2 is not None: 
    251             plot.dy = dy 
    252  
    253         return plot 
    254154 
    255155    def computeDataRange(self): 
  • src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py

    raed0532 rb0ba43e  
    1 import sys 
    21import logging 
    3 import pylab 
    42import numpy as np 
    53 
     
    1513 
    1614# pr inversion calculation elements 
    17 from sas.sascalc.dataloader.data_info import Data1D 
    1815from sas.sascalc.pr.invertor import Invertor 
     16# Batch calculation display 
     17from sas.qtgui.Utilities.GridPanel import BatchInversionOutputPanel 
     18 
    1919 
    2020def is_float(value): 
     
    2525        return 0.0 
    2626 
     27 
    2728NUMBER_OF_TERMS = 10 
    2829REGULARIZATION = 0.0001 
    2930BACKGROUND_INPUT = 0.0 
    3031MAX_DIST = 140.0 
    31  
    32 # TODO: Modify plot references, don't just send new 
    33 # TODO: Update help with batch capabilities 
    34 # TODO: Method to export results in some meaningful way 
     32DICT_KEYS = ["Calculator", "PrPlot", "DataPlot"] 
     33 
     34logger = logging.getLogger(__name__) 
     35 
     36 
    3537class InversionWindow(QtWidgets.QDialog, Ui_PrInversion): 
    3638    """ 
     
    5052 
    5153        self._manager = parent 
    52         self._model_item = QtGui.QStandardItem() 
    53         self.communicate = GuiUtils.Communicate() 
     54        self.communicate = parent.communicator() 
     55        self.communicate.dataDeletedSignal.connect(self.removeData) 
    5456 
    5557        self.logic = InversionLogic() 
    5658 
    57         # Reference to Dmax window 
    58         self.dmaxWindow = None 
    59  
    6059        # The window should not close 
    61         self._allow_close = False 
    62  
     60        self._allowClose = False 
     61 
     62        # Visible data items 
    6363        # current QStandardItem showing on the panel 
    6464        self._data = None 
    65         # current Data1D as referenced by self._data 
    66         self._data_set = None 
    67  
    68         # p(r) calculator 
     65        # Reference to Dmax window for self._data 
     66        self.dmaxWindow = None 
     67        # p(r) calculator for self._data 
    6968        self._calculator = Invertor() 
    70         self._last_calculator = None 
    71         self.calc_thread = None 
    72         self.estimation_thread = None 
    73  
    74         # Current data object in view 
    75         self._data_index = 0 
    76         # list mapping data to p(r) calculation 
    77         self._data_list = {} 
     69        # Default to background estimate 
     70        self._calculator.est_bck = True 
     71        # plots of self._data 
     72        self.prPlot = None 
     73        self.dataPlot = None 
     74        # suggested nTerms 
     75        self.nTermsSuggested = NUMBER_OF_TERMS 
     76 
     77        # Calculation threads used by all data items 
     78        self.calcThread = None 
     79        self.estimationThread = None 
     80        self.estimationThreadNT = None 
     81        self.isCalculating = False 
     82 
     83        # Mapping for all data items 
     84        # Dictionary mapping data to all parameters 
     85        self._dataList = {} 
    7886        if not isinstance(data, list): 
    7987            data_list = [data] 
    8088        if data is not None: 
    8189            for datum in data_list: 
    82                 self._data_list[datum] = self._calculator.clone() 
    83  
    84         # dict of models for quick update after calculation 
    85         # {item:model} 
    86         self._models = {} 
    87  
    88         self.calculateAllButton.setEnabled(False) 
    89         self.calculateThisButton.setEnabled(False) 
    90  
    91         # plots for current data 
    92         self.pr_plot = None 
    93         self.data_plot = None 
    94         # plot references for all data in perspective 
    95         self.pr_plot_list = {} 
    96         self.data_plot_list = {} 
     90                self.updateDataList(datum) 
     91 
     92        self.dataDeleted = False 
    9793 
    9894        self.model = QtGui.QStandardItemModel(self) 
    9995        self.mapper = QtWidgets.QDataWidgetMapper(self) 
     96 
     97        # Batch fitting parameters 
     98        self.isBatch = False 
     99        self.batchResultsWindow = None 
     100        self.batchResults = {} 
     101        self.batchComplete = [] 
    100102 
    101103        # Add validators 
     
    124126        """ 
    125127        assert isinstance(value, bool) 
    126         self._allow_close = value 
     128        self._allowClose = value 
     129 
     130    def isClosable(self): 
     131        """ 
     132        Allow outsiders close this widget 
     133        """ 
     134        return self._allowClose 
    127135 
    128136    def closeEvent(self, event): 
     
    130138        Overwrite QDialog close method to allow for custom widget close 
    131139        """ 
    132         if self._allow_close: 
     140        # Close report widgets before closing/minimizing main widget 
     141        self.closeDMax() 
     142        self.closeBatchResults() 
     143        if self._allowClose: 
    133144            # reset the closability flag 
    134145            self.setClosable(value=False) 
     
    141152            self.setWindowState(QtCore.Qt.WindowMinimized) 
    142153 
     154    def closeDMax(self): 
     155        if self.dmaxWindow is not None: 
     156            self.dmaxWindow.close() 
     157 
     158    def closeBatchResults(self): 
     159        if self.batchResultsWindow is not None: 
     160            self.batchResultsWindow.close() 
     161 
    143162    ###################################################################### 
    144163    # Initialization routines 
     
    149168        self.calculateAllButton.clicked.connect(self.startThreadAll) 
    150169        self.calculateThisButton.clicked.connect(self.startThread) 
     170        self.stopButton.clicked.connect(self.stopCalculation) 
    151171        self.removeButton.clicked.connect(self.removeData) 
    152172        self.helpButton.clicked.connect(self.help) 
     
    157177        self.explorerButton.clicked.connect(self.openExplorerWindow) 
    158178 
    159         self.backgroundInput.editingFinished.connect( 
    160             lambda: self._calculator.set_est_bck(int(is_float(self.backgroundInput.text())))) 
    161         self.minQInput.editingFinished.connect( 
     179        self.backgroundInput.textChanged.connect( 
     180            lambda: self.set_background(self.backgroundInput.text())) 
     181        self.minQInput.textChanged.connect( 
    162182            lambda: self._calculator.set_qmin(is_float(self.minQInput.text()))) 
    163         self.regularizationConstantInput.editingFinished.connect( 
     183        self.regularizationConstantInput.textChanged.connect( 
    164184            lambda: self._calculator.set_alpha(is_float(self.regularizationConstantInput.text()))) 
    165         self.maxDistanceInput.editingFinished.connect( 
     185        self.maxDistanceInput.textChanged.connect( 
    166186            lambda: self._calculator.set_dmax(is_float(self.maxDistanceInput.text()))) 
    167         self.maxQInput.editingFinished.connect( 
     187        self.maxQInput.textChanged.connect( 
    168188            lambda: self._calculator.set_qmax(is_float(self.maxQInput.text()))) 
    169         self.slitHeightInput.editingFinished.connect( 
     189        self.slitHeightInput.textChanged.connect( 
    170190            lambda: self._calculator.set_slit_height(is_float(self.slitHeightInput.text()))) 
    171         self.slitWidthInput.editingFinished.connect( 
    172             lambda: self._calculator.set_slit_width(is_float(self.slitHeightInput.text()))) 
     191        self.slitWidthInput.textChanged.connect( 
     192            lambda: self._calculator.set_slit_width(is_float(self.slitWidthInput.text()))) 
    173193 
    174194        self.model.itemChanged.connect(self.model_changed) 
     
    227247        Update boxes with initial values 
    228248        """ 
    229         item = QtGui.QStandardItem("") 
    230         self.model.setItem(WIDGETS.W_FILENAME, item) 
    231         item = QtGui.QStandardItem(str(BACKGROUND_INPUT)) 
    232         self.model.setItem(WIDGETS.W_BACKGROUND_INPUT, item) 
    233         item = QtGui.QStandardItem("") 
    234         self.model.setItem(WIDGETS.W_QMIN, item) 
    235         item = QtGui.QStandardItem("") 
    236         self.model.setItem(WIDGETS.W_QMAX, item) 
    237         item = QtGui.QStandardItem("") 
    238         self.model.setItem(WIDGETS.W_SLIT_WIDTH, item) 
    239         item = QtGui.QStandardItem("") 
    240         self.model.setItem(WIDGETS.W_SLIT_HEIGHT, item) 
    241         item = QtGui.QStandardItem(str(NUMBER_OF_TERMS)) 
    242         self.model.setItem(WIDGETS.W_NO_TERMS, item) 
    243         item = QtGui.QStandardItem(str(REGULARIZATION)) 
    244         self.model.setItem(WIDGETS.W_REGULARIZATION, item) 
    245         item = QtGui.QStandardItem(str(MAX_DIST)) 
    246         self.model.setItem(WIDGETS.W_MAX_DIST, item) 
    247         item = QtGui.QStandardItem("") 
    248         self.model.setItem(WIDGETS.W_RG, item) 
    249         item = QtGui.QStandardItem("") 
    250         self.model.setItem(WIDGETS.W_I_ZERO, item) 
    251         item = QtGui.QStandardItem("") 
    252         self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT, item) 
    253         item = QtGui.QStandardItem("") 
    254         self.model.setItem(WIDGETS.W_COMP_TIME, item) 
    255         item = QtGui.QStandardItem("") 
    256         self.model.setItem(WIDGETS.W_CHI_SQUARED, item) 
    257         item = QtGui.QStandardItem("") 
    258         self.model.setItem(WIDGETS.W_OSCILLATION, item) 
    259         item = QtGui.QStandardItem("") 
    260         self.model.setItem(WIDGETS.W_POS_FRACTION, item) 
    261         item = QtGui.QStandardItem("") 
    262         self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION, item) 
     249        bgd_item = QtGui.QStandardItem(str(BACKGROUND_INPUT)) 
     250        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT, bgd_item) 
     251        blank_item = QtGui.QStandardItem("") 
     252        self.model.setItem(WIDGETS.W_QMIN, blank_item) 
     253        blank_item = QtGui.QStandardItem("") 
     254        self.model.setItem(WIDGETS.W_QMAX, blank_item) 
     255        blank_item = QtGui.QStandardItem("") 
     256        self.model.setItem(WIDGETS.W_SLIT_WIDTH, blank_item) 
     257        blank_item = QtGui.QStandardItem("") 
     258        self.model.setItem(WIDGETS.W_SLIT_HEIGHT, blank_item) 
     259        no_terms_item = QtGui.QStandardItem(str(NUMBER_OF_TERMS)) 
     260        self.model.setItem(WIDGETS.W_NO_TERMS, no_terms_item) 
     261        reg_item = QtGui.QStandardItem(str(REGULARIZATION)) 
     262        self.model.setItem(WIDGETS.W_REGULARIZATION, reg_item) 
     263        max_dist_item = QtGui.QStandardItem(str(MAX_DIST)) 
     264        self.model.setItem(WIDGETS.W_MAX_DIST, max_dist_item) 
     265        blank_item = QtGui.QStandardItem("") 
     266        self.model.setItem(WIDGETS.W_RG, blank_item) 
     267        blank_item = QtGui.QStandardItem("") 
     268        self.model.setItem(WIDGETS.W_I_ZERO, blank_item) 
     269        bgd_item = QtGui.QStandardItem(str(BACKGROUND_INPUT)) 
     270        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT, bgd_item) 
     271        blank_item = QtGui.QStandardItem("") 
     272        self.model.setItem(WIDGETS.W_COMP_TIME, blank_item) 
     273        blank_item = QtGui.QStandardItem("") 
     274        self.model.setItem(WIDGETS.W_CHI_SQUARED, blank_item) 
     275        blank_item = QtGui.QStandardItem("") 
     276        self.model.setItem(WIDGETS.W_OSCILLATION, blank_item) 
     277        blank_item = QtGui.QStandardItem("") 
     278        self.model.setItem(WIDGETS.W_POS_FRACTION, blank_item) 
     279        blank_item = QtGui.QStandardItem("") 
     280        self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION, blank_item) 
    263281 
    264282    def setupWindow(self): 
     
    284302        Enable buttons when data is present, else disable them 
    285303        """ 
    286         self.calculateAllButton.setEnabled(self.logic.data_is_loaded) 
    287         self.calculateThisButton.setEnabled(self.logic.data_is_loaded) 
     304        self.calculateAllButton.setEnabled(len(self._dataList) > 1 
     305                                           and not self.isBatch 
     306                                           and not self.isCalculating) 
     307        self.calculateThisButton.setEnabled(self.logic.data_is_loaded 
     308                                            and not self.isBatch 
     309                                            and not self.isCalculating) 
    288310        self.removeButton.setEnabled(self.logic.data_is_loaded) 
    289         self.explorerButton.setEnabled(self.logic.data_is_loaded) 
     311        self.explorerButton.setEnabled(self.logic.data_is_loaded 
     312                                       and np.all(self.logic.data.dy != 0)) 
     313        self.stopButton.setVisible(self.isCalculating) 
     314        self.regConstantSuggestionButton.setEnabled( 
     315            self.logic.data_is_loaded and 
     316            self._calculator.suggested_alpha != self._calculator.alpha) 
     317        self.noOfTermsSuggestionButton.setEnabled( 
     318            self.logic.data_is_loaded and 
     319            self._calculator.nfunc != self.nTermsSuggested) 
    290320 
    291321    def populateDataComboBox(self, filename, data_ref): 
     
    307337            self.regConstantSuggestionButton.text())) 
    308338 
    309     def displayChange(self): 
    310         ref_item = self.dataList.itemData(self.dataList.currentIndex()) 
    311         self._model_item = ref_item 
    312         self.setCurrentData(ref_item) 
    313         self.setCurrentModel(ref_item) 
    314  
    315     def removeData(self): 
    316         """Remove the existing data reference from the P(r) Persepective""" 
    317         self._data_list.pop(self._data) 
    318         self.pr_plot_list.pop(self._data) 
    319         self.data_plot_list.pop(self._data) 
    320         if self.dmaxWindow is not None: 
    321             self.dmaxWindow = None 
    322         self.dataList.removeItem(self.dataList.currentIndex()) 
    323         self.dataList.setCurrentIndex(0) 
    324         # Last file removed 
    325         if not self._data_list: 
    326             self._data = None 
    327             self.pr_plot = None 
    328             self._data_set = None 
    329             self.calculateThisButton.setEnabled(False) 
    330             self.calculateAllButton.setEnabled(False) 
    331             self.explorerButton.setEnabled(False) 
     339    def displayChange(self, data_index=0): 
     340        """Switch to another item in the data list""" 
     341        if self.dataDeleted: 
     342            return 
     343        self.updateDataList(self._data) 
     344        self.setCurrentData(self.dataList.itemData(data_index)) 
    332345 
    333346    ###################################################################### 
    334347    # GUI Interaction Events 
    335348 
    336     def setCurrentModel(self, ref_item): 
    337         '''update the current model with stored values''' 
    338         if ref_item in self._models: 
    339             self.model = self._models[ref_item] 
    340  
    341     def update_calculator(self): 
     349    def updateCalculator(self): 
    342350        """Update all p(r) params""" 
    343         self._calculator.set_x(self._data_set.x) 
    344         self._calculator.set_y(self._data_set.y) 
    345         self._calculator.set_err(self._data_set.dy) 
     351        self._calculator.set_x(self.logic.data.x) 
     352        self._calculator.set_y(self.logic.data.y) 
     353        self._calculator.set_err(self.logic.data.dy) 
     354        self.set_background(self.backgroundInput.text()) 
     355 
     356    def set_background(self, value): 
     357        self._calculator.background = is_float(value) 
    346358 
    347359    def model_changed(self): 
     
    350362            msg = "Unable to update P{r}. The connection between the main GUI " 
    351363            msg += "and P(r) was severed. Attempting to restart P(r)." 
    352             logging.warning(msg) 
     364            logger.warning(msg) 
    353365            self.setClosable(True) 
    354366            self.close() 
    355             InversionWindow.__init__(self.parent(), list(self._data_list.keys())) 
     367            InversionWindow.__init__(self.parent(), list(self._dataList.keys())) 
    356368            exit(0) 
    357         # TODO: Only send plot first time - otherwise, update in complete 
    358         if self.pr_plot is not None: 
    359             title = self.pr_plot.name 
    360             GuiUtils.updateModelItemWithPlot(self._data, self.pr_plot, title) 
    361         if self.data_plot is not None: 
    362             title = self.data_plot.name 
    363             GuiUtils.updateModelItemWithPlot(self._data, self.data_plot, title) 
    364369        if self.dmaxWindow is not None: 
    365              self.dmaxWindow.pr_state = self._calculator 
    366              self.dmaxWindow.nfunc = self.getNFunc() 
    367  
    368         self.mapper.toFirst() 
     370            self.dmaxWindow.nfunc = self.getNFunc() 
     371            self.dmaxWindow.pr_state = self._calculator 
     372        self.mapper.toLast() 
    369373 
    370374    def help(self): 
     
    383387        Toggle the background between manual and estimated 
    384388        """ 
    385         sender = self.sender() 
    386         if sender is self.estimateBgd: 
     389        if self.estimateBgd.isChecked(): 
     390            self.manualBgd.setChecked(False) 
    387391            self.backgroundInput.setEnabled(False) 
     392            self._calculator.set_est_bck = True 
     393        elif self.manualBgd.isChecked(): 
     394            self.estimateBgd.setChecked(False) 
     395            self.backgroundInput.setEnabled(True) 
     396            self._calculator.set_est_bck = False 
    388397        else: 
    389             self.backgroundInput.setEnabled(True) 
     398            pass 
    390399 
    391400    def openExplorerWindow(self): 
     
    394403        """ 
    395404        from .DMaxExplorerWidget import DmaxWindow 
    396         self.dmaxWindow = DmaxWindow(self._calculator, self.getNFunc(), self) 
     405        self.dmaxWindow = DmaxWindow(pr_state=self._calculator, 
     406                                     nfunc=self.getNFunc(), 
     407                                     parent=self) 
    397408        self.dmaxWindow.show() 
     409 
     410    def showBatchOutput(self): 
     411        """ 
     412        Display the batch output in tabular form 
     413        :param output_data: Dictionary mapping filename -> P(r) instance 
     414        """ 
     415        if self.batchResultsWindow is None: 
     416            self.batchResultsWindow = BatchInversionOutputPanel( 
     417                parent=self, output_data=self.batchResults) 
     418        else: 
     419            self.batchResultsWindow.setupTable(self.batchResults) 
     420        self.batchResultsWindow.show() 
     421 
     422    def stopCalculation(self): 
     423        """ Stop all threads, return to the base state and update GUI """ 
     424        self.stopCalcThread() 
     425        self.stopEstimationThread() 
     426        self.stopEstimateNTThread() 
     427        # Show any batch calculations that successfully completed 
     428        if self.isBatch and self.batchResultsWindow is not None: 
     429            self.showBatchOutput() 
     430        self.isBatch = False 
     431        self.isCalculating = False 
     432        self.updateGuiValues() 
    398433 
    399434    ###################################################################### 
     
    410445        if not isinstance(data_item, list): 
    411446            msg = "Incorrect type passed to the P(r) Perspective" 
    412             raise AttributeError 
     447            raise AttributeError(msg) 
    413448 
    414449        for data in data_item: 
    415             if data in self._data_list.keys(): 
     450            if data in self._dataList.keys(): 
    416451                # Don't add data if it's already in 
    417                 return 
     452                continue 
    418453            # Create initial internal mappings 
    419             self._data_list[data] = self._calculator.clone() 
    420             self._data_set = GuiUtils.dataFromItem(data) 
    421             self.data_plot_list[data] = self.data_plot 
    422             self.pr_plot_list[data] = self.pr_plot 
    423             self.populateDataComboBox(self._data_set.filename, data) 
    424             self.setCurrentData(data) 
    425  
    426             # Estimate initial values from data 
    427             self.performEstimate() 
    428             self.logic = InversionLogic(self._data_set) 
    429  
     454            self.logic.data = GuiUtils.dataFromItem(data) 
    430455            # Estimate q range 
    431456            qmin, qmax = self.logic.computeDataRange() 
    432             self.model.setItem(WIDGETS.W_QMIN, QtGui.QStandardItem("{:.4g}".format(qmin))) 
    433             self.model.setItem(WIDGETS.W_QMAX, QtGui.QStandardItem("{:.4g}".format(qmax))) 
    434             self._models[data] = self.model 
    435             self.model_item = data 
    436  
    437         self.enableButtons() 
     457            self._calculator.set_qmin(qmin) 
     458            self._calculator.set_qmax(qmax) 
     459            self.updateDataList(data) 
     460            self.populateDataComboBox(self.logic.data.filename, data) 
     461        self.dataList.setCurrentIndex(len(self.dataList) - 1) 
     462        self.setCurrentData(data) 
     463 
     464    def updateDataList(self, dataRef): 
     465        """Save the current data state of the window into self._data_list""" 
     466        if dataRef is None: 
     467            return 
     468        self._dataList[dataRef] = { 
     469            DICT_KEYS[0]: self._calculator, 
     470            DICT_KEYS[1]: self.prPlot, 
     471            DICT_KEYS[2]: self.dataPlot 
     472        } 
     473        # Update batch results window when finished 
     474        self.batchResults[self.logic.data.filename] = self._calculator 
     475        if self.batchResultsWindow is not None: 
     476            self.showBatchOutput() 
    438477 
    439478    def getNFunc(self): 
     
    442481            nfunc = int(self.noOfTermsInput.text()) 
    443482        except ValueError: 
    444             logging.error("Incorrect number of terms specified: %s" %self.noOfTermsInput.text()) 
     483            logger.error("Incorrect number of terms specified: %s" 
     484                          %self.noOfTermsInput.text()) 
    445485            self.noOfTermsInput.setText(str(NUMBER_OF_TERMS)) 
    446486            nfunc = NUMBER_OF_TERMS 
     
    448488 
    449489    def setCurrentData(self, data_ref): 
    450         """Get the current data and display as necessary""" 
    451  
     490        """Get the data by reference and display as necessary""" 
    452491        if data_ref is None: 
    453492            return 
    454  
    455493        if not isinstance(data_ref, QtGui.QStandardItem): 
    456494            msg = "Incorrect type passed to the P(r) Perspective" 
    457             raise AttributeError 
    458  
     495            raise AttributeError(msg) 
    459496        # Data references 
    460497        self._data = data_ref 
    461         self._data_set = GuiUtils.dataFromItem(data_ref) 
    462         self._calculator = self._data_list[data_ref] 
    463         self.pr_plot = self.pr_plot_list[data_ref] 
    464         self.data_plot = self.data_plot_list[data_ref] 
     498        self.logic.data = GuiUtils.dataFromItem(data_ref) 
     499        self._calculator = self._dataList[data_ref].get(DICT_KEYS[0]) 
     500        self.prPlot = self._dataList[data_ref].get(DICT_KEYS[1]) 
     501        self.dataPlot = self._dataList[data_ref].get(DICT_KEYS[2]) 
     502        self.performEstimate() 
     503 
     504    def updateGuiValues(self): 
     505        pr = self._calculator 
     506        out = self._calculator.out 
     507        cov = self._calculator.cov 
     508        elapsed = self._calculator.elapsed 
     509        alpha = self._calculator.suggested_alpha 
     510        self.model.setItem(WIDGETS.W_QMIN, 
     511                           QtGui.QStandardItem("{:.4g}".format(pr.get_qmin()))) 
     512        self.model.setItem(WIDGETS.W_QMAX, 
     513                           QtGui.QStandardItem("{:.4g}".format(pr.get_qmax()))) 
     514        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT, 
     515                           QtGui.QStandardItem("{:.3g}".format(pr.background))) 
     516        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT, 
     517                           QtGui.QStandardItem("{:.3g}".format(pr.background))) 
     518        self.model.setItem(WIDGETS.W_COMP_TIME, 
     519                           QtGui.QStandardItem("{:.4g}".format(elapsed))) 
     520        self.model.setItem(WIDGETS.W_MAX_DIST, 
     521                           QtGui.QStandardItem("{:.4g}".format(pr.get_dmax()))) 
     522        self.regConstantSuggestionButton.setText("{:-3.2g}".format(alpha)) 
     523        self.noOfTermsSuggestionButton.setText( 
     524            "{:n}".format(self.nTermsSuggested)) 
     525 
     526        if isinstance(pr.chi2, np.ndarray): 
     527            self.model.setItem(WIDGETS.W_CHI_SQUARED, 
     528                               QtGui.QStandardItem("{:.3g}".format(pr.chi2[0]))) 
     529        if out is not None: 
     530            self.model.setItem(WIDGETS.W_RG, 
     531                               QtGui.QStandardItem("{:.3g}".format(pr.rg(out)))) 
     532            self.model.setItem(WIDGETS.W_I_ZERO, 
     533                               QtGui.QStandardItem( 
     534                                   "{:.3g}".format(pr.iq0(out)))) 
     535            self.model.setItem(WIDGETS.W_OSCILLATION, QtGui.QStandardItem( 
     536                "{:.3g}".format(pr.oscillations(out)))) 
     537            self.model.setItem(WIDGETS.W_POS_FRACTION, QtGui.QStandardItem( 
     538                "{:.3g}".format(pr.get_positive(out)))) 
     539            if cov is not None: 
     540                self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION, 
     541                                   QtGui.QStandardItem( 
     542                                       "{:.3g}".format( 
     543                                           pr.get_pos_err(out, cov)))) 
     544        if self.prPlot is not None: 
     545            title = self.prPlot.name 
     546            GuiUtils.updateModelItemWithPlot(self._data, self.prPlot, title) 
     547            self.communicate.plotRequestedSignal.emit([self.prPlot]) 
     548        if self.dataPlot is not None: 
     549            title = self.dataPlot.name 
     550            GuiUtils.updateModelItemWithPlot(self._data, self.dataPlot, title) 
     551            self.communicate.plotRequestedSignal.emit([self.dataPlot]) 
     552        self.enableButtons() 
     553 
     554    def removeData(self, data_list=None): 
     555        """Remove the existing data reference from the P(r) Persepective""" 
     556        self.dataDeleted = True 
     557        self.batchResults = {} 
     558        if not data_list: 
     559            data_list = [self._data] 
     560        self.closeDMax() 
     561        for data in data_list: 
     562            self._dataList.pop(data) 
     563        self._data = None 
     564        length = len(self.dataList) 
     565        for index in reversed(range(length)): 
     566            if self.dataList.itemData(index) in data_list: 
     567                self.dataList.removeItem(index) 
     568        # Last file removed 
     569        self.dataDeleted = False 
     570        if len(self._dataList) == 0: 
     571            self.prPlot = None 
     572            self.dataPlot = None 
     573            self.logic.data = None 
     574            self._calculator = Invertor() 
     575            self.closeBatchResults() 
     576            self.nTermsSuggested = NUMBER_OF_TERMS 
     577            self.noOfTermsSuggestionButton.setText("{:n}".format( 
     578                self.nTermsSuggested)) 
     579            self.regConstantSuggestionButton.setText("{:-3.2g}".format( 
     580                REGULARIZATION)) 
     581            self.updateGuiValues() 
     582            self.setupModel() 
     583        else: 
     584            self.dataList.setCurrentIndex(0) 
     585            self.updateGuiValues() 
    465586 
    466587    ###################################################################### 
    467588    # Thread Creators 
     589 
    468590    def startThreadAll(self): 
    469         for data_ref, pr in list(self._data_list.items()): 
    470             self._data_set = GuiUtils.dataFromItem(data_ref) 
    471             self._calculator = pr 
    472             self.startThread() 
     591        self.isCalculating = True 
     592        self.isBatch = True 
     593        self.batchComplete = [] 
     594        self.calculateAllButton.setText("Calculating...") 
     595        self.enableButtons() 
     596        self.batchResultsWindow = BatchInversionOutputPanel( 
     597            parent=self, output_data=self.batchResults) 
     598        self.performEstimate() 
     599 
     600    def startNextBatchItem(self): 
     601        self.isBatch = False 
     602        for index in range(len(self._dataList)): 
     603            if index not in self.batchComplete: 
     604                self.dataList.setCurrentIndex(index) 
     605                self.isBatch = True 
     606                # Add the index before calculating in case calculation fails 
     607                self.batchComplete.append(index) 
     608                break 
     609        if self.isBatch: 
     610            self.performEstimate() 
     611        else: 
     612            # If no data sets left, end batch calculation 
     613            self.isCalculating = False 
     614            self.batchComplete = [] 
     615            self.calculateAllButton.setText("Calculate All") 
     616            self.showBatchOutput() 
     617            self.enableButtons() 
    473618 
    474619    def startThread(self): 
     
    479624 
    480625        # Set data before running the calculations 
    481         self.update_calculator() 
    482  
    483         # If a thread is already started, stop it 
    484         if self.calc_thread is not None and self.calc_thread.isrunning(): 
    485             self.calc_thread.stop() 
     626        self.isCalculating = True 
     627        self.enableButtons() 
     628        self.updateCalculator() 
     629        # Disable calculation buttons to prevent thread interference 
     630 
     631        # If the thread is already started, stop it 
     632        self.stopCalcThread() 
     633 
    486634        pr = self._calculator.clone() 
    487635        nfunc = self.getNFunc() 
    488         self.calc_thread = CalcPr(pr, nfunc, 
    489                                   error_func=self._threadError, 
    490                                   completefn=self._calculateCompleted, 
    491                                   updatefn=None) 
    492         self.calc_thread.queue() 
    493         self.calc_thread.ready(2.5) 
     636        self.calcThread = CalcPr(pr, nfunc, 
     637                                 error_func=self._threadError, 
     638                                 completefn=self._calculateCompleted, 
     639                                 updatefn=None) 
     640        self.calcThread.queue() 
     641        self.calcThread.ready(2.5) 
     642 
     643    def stopCalcThread(self): 
     644        """ Stops a thread if it exists and is running """ 
     645        if self.calcThread is not None and self.calcThread.isrunning(): 
     646            self.calcThread.stop() 
    494647 
    495648    def performEstimateNT(self): 
     
    499652        from .Thread import EstimateNT 
    500653 
     654        self.updateCalculator() 
     655 
    501656        # If a thread is already started, stop it 
    502         if (self.estimation_thread is not None and 
    503                 self.estimation_thread.isrunning()): 
    504             self.estimation_thread.stop() 
     657        self.stopEstimateNTThread() 
     658 
    505659        pr = self._calculator.clone() 
    506660        # Skip the slit settings for the estimation 
     
    510664        nfunc = self.getNFunc() 
    511665 
    512         self.estimation_thread = EstimateNT(pr, nfunc, 
    513                                             error_func=self._threadError, 
    514                                             completefn=self._estimateNTCompleted, 
    515                                             updatefn=None) 
    516         self.estimation_thread.queue() 
    517         self.estimation_thread.ready(2.5) 
     666        self.estimationThreadNT = EstimateNT(pr, nfunc, 
     667                                             error_func=self._threadError, 
     668                                             completefn=self._estimateNTCompleted, 
     669                                             updatefn=None) 
     670        self.estimationThreadNT.queue() 
     671        self.estimationThreadNT.ready(2.5) 
     672 
     673    def stopEstimateNTThread(self): 
     674        if (self.estimationThreadNT is not None and 
     675                self.estimationThreadNT.isrunning()): 
     676            self.estimationThreadNT.stop() 
    518677 
    519678    def performEstimate(self): 
     
    523682        from .Thread import EstimatePr 
    524683 
    525         self.startThread() 
    526  
    527684        # If a thread is already started, stop it 
    528         if (self.estimation_thread is not None and 
    529                 self.estimation_thread.isrunning()): 
    530             self.estimation_thread.stop() 
    531         pr = self._calculator.clone() 
    532         nfunc = self.getNFunc() 
    533         self.estimation_thread = EstimatePr(pr, nfunc, 
    534                                             error_func=self._threadError, 
    535                                             completefn=self._estimateCompleted, 
    536                                             updatefn=None) 
    537         self.estimation_thread.queue() 
    538         self.estimation_thread.ready(2.5) 
     685        self.stopEstimationThread() 
     686 
     687        self.estimationThread = EstimatePr(self._calculator.clone(), 
     688                                           self.getNFunc(), 
     689                                           error_func=self._threadError, 
     690                                           completefn=self._estimateCompleted, 
     691                                           updatefn=None) 
     692        self.estimationThread.queue() 
     693        self.estimationThread.ready(2.5) 
     694 
     695    def stopEstimationThread(self): 
     696        """ Stop the estimation thread if it exists and is running """ 
     697        if (self.estimationThread is not None and 
     698                self.estimationThread.isrunning()): 
     699            self.estimationThread.stop() 
    539700 
    540701    ###################################################################### 
     
    554715        """ 
    555716        alpha, message, elapsed = output_tuple 
    556         # Save useful info 
    557         self.model.setItem(WIDGETS.W_COMP_TIME, QtGui.QStandardItem("{:.4g}".format(elapsed))) 
    558         self.regConstantSuggestionButton.setText("{:-3.2g}".format(alpha)) 
    559         self.regConstantSuggestionButton.setEnabled(True) 
     717        self._calculator.alpha = alpha 
     718        self._calculator.elapsed += self._calculator.elapsed 
    560719        if message: 
    561             logging.info(message) 
     720            logger.info(message) 
    562721        self.performEstimateNT() 
    563722 
     
    576735        """ 
    577736        nterms, alpha, message, elapsed = output_tuple 
     737        self._calculator.elapsed += elapsed 
     738        self._calculator.suggested_alpha = alpha 
     739        self.nTermsSuggested = nterms 
    578740        # Save useful info 
    579         self.noOfTermsSuggestionButton.setText("{:n}".format(nterms)) 
    580         self.noOfTermsSuggestionButton.setEnabled(True) 
    581         self.regConstantSuggestionButton.setText("{:.3g}".format(alpha)) 
    582         self.regConstantSuggestionButton.setEnabled(True) 
    583         self.model.setItem(WIDGETS.W_COMP_TIME, QtGui.QStandardItem("{:.2g}".format(elapsed))) 
     741        self.updateGuiValues() 
    584742        if message: 
    585             logging.info(message) 
     743            logger.info(message) 
     744        if self.isBatch: 
     745            self.acceptAlpha() 
     746            self.acceptNoTerms() 
     747            self.startThread() 
    586748 
    587749    def _calculateCompleted(self, out, cov, pr, elapsed): 
     
    605767        pr.elapsed = elapsed 
    606768 
    607         # Show result on control panel 
    608         self.model.setItem(WIDGETS.W_RG, QtGui.QStandardItem("{:.3g}".format(pr.rg(out)))) 
    609         self.model.setItem(WIDGETS.W_I_ZERO, QtGui.QStandardItem("{:.3g}".format(pr.iq0(out)))) 
    610         self.model.setItem(WIDGETS.W_BACKGROUND_INPUT, 
    611                            QtGui.QStandardItem("{:.3f}".format(pr.est_bck))) 
    612         self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT, QtGui.QStandardItem("{:.3g}".format(pr.background))) 
    613         self.model.setItem(WIDGETS.W_CHI_SQUARED, QtGui.QStandardItem("{:.3g}".format(pr.chi2[0]))) 
    614         self.model.setItem(WIDGETS.W_COMP_TIME, QtGui.QStandardItem("{:.2g}".format(elapsed))) 
    615         self.model.setItem(WIDGETS.W_OSCILLATION, QtGui.QStandardItem("{:.3g}".format(pr.oscillations(out)))) 
    616         self.model.setItem(WIDGETS.W_POS_FRACTION, QtGui.QStandardItem("{:.3g}".format(pr.get_positive(out)))) 
    617         self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION, 
    618                            QtGui.QStandardItem("{:.3g}".format(pr.get_pos_err(out, cov)))) 
    619  
    620769        # Save Pr invertor 
    621770        self._calculator = pr 
    622         # Append data to data list 
    623         self._data_list[self._data] = self._calculator.clone() 
    624  
    625         # Update model dict 
    626         self._models[self.model_item] = self.model 
    627  
    628         # Create new P(r) and fit plots 
    629         if self.pr_plot is None: 
    630             self.pr_plot = self.logic.newPRPlot(out, self._calculator, cov) 
    631             self.pr_plot_list[self._data] = self.pr_plot 
     771 
     772        # Update P(r) and fit plots 
     773        self.prPlot = self.logic.newPRPlot(out, self._calculator, cov) 
     774        self.prPlot.filename = self.logic.data.filename 
     775        self.dataPlot = self.logic.new1DPlot(out, self._calculator) 
     776        self.dataPlot.filename = self.logic.data.filename 
     777 
     778        # Udpate internals and GUI 
     779        self.updateDataList(self._data) 
     780        if self.isBatch: 
     781            self.batchComplete.append(self.dataList.currentIndex()) 
     782            self.startNextBatchItem() 
    632783        else: 
    633             # FIXME: this should update the existing plot, not create a new one 
    634             self.pr_plot = self.logic.newPRPlot(out, self._calculator, cov) 
    635             self.pr_plot_list[self._data] = self.pr_plot 
    636         if self.data_plot is None: 
    637             self.data_plot = self.logic.new1DPlot(out, self._calculator) 
    638             self.data_plot_list[self._data] = self.data_plot 
     784            self.isCalculating = False 
     785        self.updateGuiValues() 
     786 
     787    def _threadError(self, error): 
     788        """ 
     789            Call-back method for calculation errors 
     790        """ 
     791        logger.error(error) 
     792        if self.isBatch: 
     793            self.startNextBatchItem() 
    639794        else: 
    640             # FIXME: this should update the existing plot, not create a new one 
    641             self.data_plot = self.logic.new1DPlot(out, self._calculator) 
    642             self.data_plot_list[self._data] = self.data_plot 
    643  
    644     def _threadError(self, error): 
    645         """ 
    646             Call-back method for calculation errors 
    647         """ 
    648         logging.warning(error) 
     795            self.stopCalculation() 
  • src/sas/qtgui/Perspectives/Inversion/InversionUtils.py

    • Property mode changed from 100755 to 100644
  • src/sas/qtgui/Perspectives/Inversion/Thread.py

    • Property mode changed from 100755 to 100644
  • src/sas/qtgui/Perspectives/Inversion/UI/TabbedInversionUI.ui

    • Property mode changed from 100755 to 100644
    r8f83719f r72ecbdf2  
    317317           <widget class="QLabel" name="label_22"> 
    318318            <property name="text"> 
    319              <string>      </string> 
     319             <string/> 
    320320            </property> 
    321321           </widget> 
     
    754754     </item> 
    755755     <item> 
     756      <widget class="QPushButton" name="stopButton"> 
     757       <property name="text"> 
     758        <string>Stop P(r)</string> 
     759       </property> 
     760      </widget> 
     761     </item> 
     762     <item> 
    756763      <spacer name="horizontalSpacer"> 
    757764       <property name="orientation"> 
  • src/sas/qtgui/Perspectives/Inversion/UI/__init__.py

    • Property mode changed from 100755 to 100644
  • src/sas/qtgui/Perspectives/Inversion/UnitTesting/InversionPerspectiveTest.py

    r50bfab0 r72ecbdf2  
    1 import sys 
     1import time 
    22import unittest 
    3 import logging 
    43from unittest.mock import MagicMock 
    54 
    65from PyQt5 import QtGui, QtWidgets 
    7 from PyQt5.QtTest import QTest 
    8 from PyQt5 import QtCore 
    9  
    10 import sas.qtgui.path_prepare 
     6 
     7from sas.qtgui.Utilities.GuiUtils import * 
    118from sas.qtgui.Perspectives.Inversion.InversionPerspective import InversionWindow 
    12 from sas.sascalc.dataloader.loader import Loader 
     9from sas.qtgui.Perspectives.Inversion.InversionUtils import WIDGETS 
    1310from sas.qtgui.Plotting.PlotterData import Data1D 
    1411 
    15 from sas.qtgui.MainWindow.DataManager import DataManager 
    16 import sas.qtgui.Utilities.LocalConfig 
    1712import sas.qtgui.Utilities.GuiUtils as GuiUtils 
    1813 
     
    2015app = QtWidgets.QApplication(sys.argv) 
    2116 
     17 
     18class dummy_manager(object): 
     19    HELP_DIRECTORY_LOCATION = "html" 
     20    communicate = Communicate() 
     21 
     22    def communicator(self): 
     23        return self.communicate 
     24 
     25 
    2226class InversionTest(unittest.TestCase): 
    23     '''Test the Inversion Interface''' 
     27    """ Test the Inversion Perspective GUI """ 
     28 
    2429    def setUp(self): 
    25         '''Create the InversionWindow''' 
    26         self.widget = InversionWindow(None) 
     30        """ Create the InversionWindow """ 
     31        self.widget = InversionWindow(dummy_manager()) 
     32        self.widget.show() 
     33        self.fakeData1 = GuiUtils.HashableStandardItem("A") 
     34        self.fakeData2 = GuiUtils.HashableStandardItem("B") 
     35        reference_data1 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0]) 
     36        reference_data1.filename = "Test A" 
     37        reference_data2 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0]) 
     38        reference_data2.filename = "Test B" 
     39        GuiUtils.updateModelItem(self.fakeData1, reference_data1) 
     40        GuiUtils.updateModelItem(self.fakeData2, reference_data2) 
    2741 
    2842    def tearDown(self): 
    29         '''Destroy the InversionWindow''' 
     43        """ Destroy the InversionWindow """ 
     44        self.widget.setClosable(False) 
    3045        self.widget.close() 
    3146        self.widget = None 
    3247 
    33     def testDefaults(self): 
    34         '''Test the GUI in its default state''' 
     48    def removeAllData(self): 
     49        """ Cleanup method to restore widget to its base state """ 
     50        if len(self.widget.dataList) > 0: 
     51            remove_me = list(self.widget._dataList.keys()) 
     52            self.widget.removeData(remove_me) 
     53 
     54    def baseGUIState(self): 
     55        """ Testing base state of Inversion """ 
     56        # base class information 
    3557        self.assertIsInstance(self.widget, QtWidgets.QWidget) 
    3658        self.assertEqual(self.widget.windowTitle(), "P(r) Inversion Perspective") 
    37         self.assertEqual(self.widget.model.columnCount(), 1) 
    38         self.assertEqual(self.widget.model.rowCount(), 22) 
    39         self.assertFalse(self.widget.calculateAllButton.isEnabled()) 
    40         self.assertFalse(self.widget.calculateThisButton.isEnabled()) 
     59        self.assertFalse(self.widget.isClosable()) 
     60        self.assertFalse(self.widget.isCalculating) 
     61        # mapper 
    4162        self.assertIsInstance(self.widget.mapper, QtWidgets.QDataWidgetMapper) 
    42         # make sure the model is assigned and at least the data is mapped 
    43         self.assertEqual(self.widget.mapper.model(), self.widget.model) 
    4463        self.assertNotEqual(self.widget.mapper.mappedSection(self.widget.dataList), -1) 
    45  
    4664        # validators 
    4765        self.assertIsInstance(self.widget.noOfTermsInput.validator(), QtGui.QIntValidator) 
     
    5270        self.assertIsInstance(self.widget.slitHeightInput.validator(), QtGui.QDoubleValidator) 
    5371        self.assertIsInstance(self.widget.slitWidthInput.validator(), QtGui.QDoubleValidator) 
    54  
    5572        # model 
    5673        self.assertEqual(self.widget.model.rowCount(), 22) 
    57  
    58     def testSetData(self): 
    59         ''' Check if sending data works as expected''' 
    60         # Create dummy data 
    61         item1 = GuiUtils.HashableStandardItem("A") 
    62         item2 = GuiUtils.HashableStandardItem("B") 
    63         reference_data = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0]) 
    64         GuiUtils.updateModelItem(item1, [reference_data]) 
    65         GuiUtils.updateModelItem(item2, [reference_data]) 
    66         self.widget.performEstimate = MagicMock() 
    67         self.widget.setData([item1, item2]) 
    68  
    69         # Test the globals 
    70         self.assertEqual(len(self.widget._data_list), 2) 
    71         self.assertEqual(len(self.widget.data_plot_list), 2) 
    72         self.assertEqual(len(self.widget.pr_plot_list), 2) 
    73         self.assertEqual(self.widget.dataList.count(), 2) 
    74  
    75         # See that the buttons are now enabled 
    76         self.assertTrue(self.widget.calculateAllButton.isEnabled()) 
     74        self.assertEqual(self.widget.model.columnCount(), 1) 
     75        self.assertEqual(self.widget.mapper.model(), self.widget.model) 
     76        # buttons 
     77        self.assertFalse(self.widget.calculateThisButton.isEnabled()) 
     78        self.assertFalse(self.widget.removeButton.isEnabled()) 
     79        self.assertTrue(self.widget.stopButton.isEnabled()) 
     80        self.assertFalse(self.widget.stopButton.isVisible()) 
     81        self.assertFalse(self.widget.regConstantSuggestionButton.isEnabled()) 
     82        self.assertFalse(self.widget.noOfTermsSuggestionButton.isEnabled()) 
     83        self.assertFalse(self.widget.explorerButton.isEnabled()) 
     84        self.assertTrue(self.widget.helpButton.isEnabled()) 
     85        # extra windows and charts 
     86        self.assertIsNone(self.widget.dmaxWindow) 
     87        self.assertIsNone(self.widget.prPlot) 
     88        self.assertIsNone(self.widget.dataPlot) 
     89        # threads 
     90        self.assertIsNone(self.widget.calcThread) 
     91        self.assertIsNone(self.widget.estimationThread) 
     92        self.assertIsNone(self.widget.estimationThreadNT) 
     93 
     94    def baseBatchState(self): 
     95        """ Testing the base batch fitting state """ 
     96        self.assertTrue(self.widget.allowBatch()) 
     97        self.assertFalse(self.widget.isBatch) 
     98        self.assertIsNone(self.widget.batchResultsWindow) 
     99        self.assertFalse(self.widget.calculateAllButton.isEnabled()) 
     100        self.assertEqual(len(self.widget.batchResults), 0) 
     101        self.assertEqual(len(self.widget.batchComplete), 0) 
     102        self.widget.closeBatchResults() 
     103        self.assertIsNone(self.widget.batchResultsWindow) 
     104 
     105    def zeroDataSetState(self): 
     106        """ Testing the base data state of the GUI """ 
     107        # data variables 
     108        self.assertIsNone(self.widget._data) 
     109        self.assertEqual(len(self.widget._dataList), 0) 
     110        self.assertEqual(self.widget.nTermsSuggested, 10) 
     111        # inputs 
     112        self.assertEqual(len(self.widget.dataList), 0) 
     113        self.assertEqual(self.widget.backgroundInput.text(), "0.0") 
     114        self.assertEqual(self.widget.minQInput.text(), "") 
     115        self.assertEqual(self.widget.maxQInput.text(), "") 
     116        self.assertEqual(self.widget.regularizationConstantInput.text(), "0.0001") 
     117        self.assertEqual(self.widget.noOfTermsInput.text(), "10") 
     118        self.assertEqual(self.widget.maxDistanceInput.text(), "140.0") 
     119 
     120    def oneDataSetState(self): 
     121        """ Testing the base data state of the GUI """ 
     122        # Test the globals after first sent 
     123        self.assertEqual(len(self.widget._dataList), 1) 
     124        self.assertEqual(self.widget.dataList.count(), 1) 
     125        # See that the buttons are now enabled properly 
     126        self.widget.enableButtons() 
     127        self.assertFalse(self.widget.calculateAllButton.isEnabled()) 
    77128        self.assertTrue(self.widget.calculateThisButton.isEnabled()) 
    78129        self.assertTrue(self.widget.removeButton.isEnabled()) 
    79130        self.assertTrue(self.widget.explorerButton.isEnabled()) 
    80131 
    81     def notestRemoveData(self): 
    82         ''' Test data removal from widget ''' 
    83         # Create dummy data 
    84         item1 = GuiUtils.HashableStandardItem("A") 
    85         item2 = GuiUtils.HashableStandardItem("B") 
    86         reference_data1 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0]) 
    87         reference_data2 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0]) 
    88         GuiUtils.updateModelItem(item1, [reference_data1]) 
    89         GuiUtils.updateModelItem(item2, [reference_data2]) 
    90         self.widget.performEstimate = MagicMock() 
    91         self.widget.setData([item1, item2]) 
    92  
     132    def twoDataSetState(self): 
     133        """ Testing the base data state of the GUI """ 
     134        # Test the globals after first sent 
     135        self.assertEqual(len(self.widget._dataList), 2) 
     136        self.assertEqual(self.widget.dataList.count(), 2) 
     137        # See that the buttons are now enabled properly 
     138        self.widget.enableButtons() 
     139        self.assertTrue(self.widget.calculateThisButton.isEnabled()) 
     140        self.assertTrue(self.widget.calculateAllButton.isEnabled()) 
     141        self.assertTrue(self.widget.removeButton.isEnabled()) 
     142        self.assertTrue(self.widget.explorerButton.isEnabled()) 
     143 
     144    def testDefaults(self): 
     145        """ Test the GUI in its default state """ 
     146        self.baseGUIState() 
     147        self.zeroDataSetState() 
     148        self.baseBatchState() 
     149        self.removeAllData() 
     150 
     151    def testAllowBatch(self): 
     152        """ Batch P(r) Tests """ 
     153        self.baseBatchState() 
     154        self.widget.setData([self.fakeData1]) 
     155        self.oneDataSetState() 
     156        self.widget.setData([self.fakeData2]) 
     157        self.twoDataSetState() 
     158        self.widget.calculateAllButton.click() 
     159        self.assertTrue(self.widget.isCalculating) 
     160        self.assertTrue(self.widget.isBatch) 
     161        self.assertTrue(self.widget.stopButton.isVisible()) 
     162        self.assertTrue(self.widget.stopButton.isEnabled()) 
     163        self.assertIsNotNone(self.widget.batchResultsWindow) 
     164        self.assertTrue(self.widget.batchResultsWindow.cmdHelp.isEnabled()) 
     165        self.assertEqual(self.widget.batchResultsWindow.tblParams.columnCount(), 9) 
     166        self.assertEqual(self.widget.batchResultsWindow.tblParams.rowCount(), 2) 
     167        # Test stop button 
     168        self.widget.stopButton.click() 
     169        self.assertTrue(self.widget.batchResultsWindow.isVisible()) 
     170        self.assertFalse(self.widget.stopButton.isVisible()) 
     171        self.assertTrue(self.widget.stopButton.isEnabled()) 
     172        self.assertFalse(self.widget.isBatch) 
     173        self.assertFalse(self.widget.isCalculating) 
     174        self.widget.batchResultsWindow.close() 
     175        self.assertIsNone(self.widget.batchResultsWindow) 
     176        # Last test 
     177        self.removeAllData() 
     178        self.baseBatchState() 
     179 
     180    def testSetData(self): 
     181        """ Check if sending data works as expected """ 
     182        self.zeroDataSetState() 
     183        self.widget.setData([self.fakeData1]) 
     184        self.oneDataSetState() 
     185        self.widget.setData([self.fakeData1]) 
     186        self.oneDataSetState() 
     187        self.widget.setData([self.fakeData2]) 
     188        self.twoDataSetState() 
     189        self.removeAllData() 
     190        self.zeroDataSetState() 
     191        self.removeAllData() 
     192 
     193    def testRemoveData(self): 
     194        """ Test data removal from widget """ 
     195        self.widget.setData([self.fakeData1, self.fakeData2]) 
     196        self.twoDataSetState() 
    93197        # Remove data 0 
    94198        self.widget.removeData() 
    95  
    96         # Test the globals 
    97         self.assertEqual(len(self.widget._data_list), 1) 
    98         self.assertEqual(len(self.widget.data_plot_list), 1) 
    99         self.assertEqual(len(self.widget.pr_plot_list), 1) 
    100         self.assertEqual(self.widget.dataList.count(), 1) 
    101  
    102  
    103     def testAllowBatch(self): 
    104         ''' Batch is allowed for this perspective''' 
    105         self.assertTrue(self.widget.allowBatch()) 
    106  
    107     def notestModelChanged(self): 
    108         ''' test the model update ''' 
    109         # unfinished functionality 
    110         pass 
    111  
    112     def notestHelp(self): 
    113         ''' test help widget show ''' 
    114         # unfinished functionality 
    115         pass 
    116  
    117     def testOpenExplorerWindow(self): 
    118         ''' open Dx window ''' 
    119         self.widget.openExplorerWindow() 
    120         self.assertTrue(self.widget.dmaxWindow.isVisible()) 
     199        self.oneDataSetState() 
     200        self.removeAllData() 
     201 
     202    def testClose(self): 
     203        """ Test methods related to closing the window """ 
     204        self.assertFalse(self.widget.isClosable()) 
     205        self.widget.close() 
     206        self.assertTrue(self.widget.isMinimized()) 
     207        self.assertIsNone(self.widget.dmaxWindow) 
     208        self.assertIsNone(self.widget.batchResultsWindow) 
     209        self.widget.setClosable(False) 
     210        self.assertFalse(self.widget.isClosable()) 
     211        self.widget.close() 
     212        self.assertTrue(self.widget.isMinimized()) 
     213        self.widget.setClosable(True) 
     214        self.assertTrue(self.widget.isClosable()) 
     215        self.widget.setClosable() 
     216        self.assertTrue(self.widget.isClosable()) 
     217        self.removeAllData() 
    121218 
    122219    def testGetNFunc(self): 
    123         ''' test nfunc getter ''' 
     220        """ test nfunc getter """ 
    124221        # Float 
    125222        self.widget.noOfTermsInput.setText("10.0") 
     
    132229            self.widget.noOfTermsInput.setText("") 
    133230            n = self.widget.getNFunc() 
    134             self.assertEqual(cm.output, ['ERROR:root:Incorrect number of terms specified: ']) 
     231            self.assertEqual(cm.output, ['ERROR:sas.qtgui.Perspectives.Inversion.InversionPerspective:Incorrect number of terms specified: ']) 
    135232        self.assertEqual(self.widget.getNFunc(), 10) 
    136233        # string 
     
    138235            self.widget.noOfTermsInput.setText("Nordvest Pizza") 
    139236            n = self.widget.getNFunc() 
    140             self.assertEqual(cm.output, ['ERROR:root:Incorrect number of terms specified: Nordvest Pizza']) 
     237            self.assertEqual(cm.output, ['ERROR:sas.qtgui.Perspectives.Inversion.InversionPerspective:Incorrect number of terms specified: Nordvest Pizza']) 
    141238        self.assertEqual(self.widget.getNFunc(), 10) 
     239        self.removeAllData() 
    142240 
    143241    def testSetCurrentData(self): 
    144         ''' test current data setter ''' 
    145         # Create dummy data 
    146         item1 = GuiUtils.HashableStandardItem("A") 
    147         item2 = GuiUtils.HashableStandardItem("B") 
    148         reference_data1 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0]) 
    149         reference_data2 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0]) 
    150         GuiUtils.updateModelItem(item1, [reference_data1]) 
    151         GuiUtils.updateModelItem(item2, [reference_data2]) 
    152         self.widget.performEstimate = MagicMock() 
    153         self.widget.setData([item1, item2]) 
     242        """ test current data setter """ 
     243        self.widget.setData([self.fakeData1, self.fakeData2]) 
    154244 
    155245        # Check that the current data is reference2 
    156         self.assertEqual(self.widget._data, item2) 
    157  
     246        self.assertEqual(self.widget._data, self.fakeData2) 
    158247        # Set the ref to none 
    159248        self.widget.setCurrentData(None) 
    160         self.assertEqual(self.widget._data, item2) 
    161  
     249        self.assertEqual(self.widget._data, self.fakeData2) 
    162250        # Set the ref to wrong type 
    163251        with self.assertRaises(AttributeError): 
    164252            self.widget.setCurrentData("Afandi Kebab") 
    165  
    166         # Set the reference to ref2 
    167         self.widget.setCurrentData(item1) 
    168         self.assertEqual(self.widget._data, item1) 
     253        # Set the reference to ref1 
     254        self.widget.setCurrentData(self.fakeData1) 
     255        self.assertEqual(self.widget._data, self.fakeData1) 
     256        self.removeAllData() 
     257 
     258    def testModelChanged(self): 
     259        """ Test setting the input and the model and vice-versa """ 
     260        # Initial values 
     261        self.assertEqual(self.widget._calculator.get_dmax(), 140.0) 
     262        self.assertEqual(self.widget._calculator.get_qmax(), -1.0) 
     263        self.assertEqual(self.widget._calculator.get_qmin(), -1.0) 
     264        self.assertEqual(self.widget._calculator.slit_height, 0.0) 
     265        self.assertEqual(self.widget._calculator.slit_width, 0.0) 
     266        self.assertEqual(self.widget._calculator.alpha, 0.0001) 
     267        # Set new values 
     268        self.widget.maxDistanceInput.setText("1.0") 
     269        self.widget.maxQInput.setText("3.0") 
     270        self.widget.minQInput.setText("5.0") 
     271        self.widget.slitHeightInput.setText("7.0") 
     272        self.widget.slitWidthInput.setText("9.0") 
     273        self.widget.regularizationConstantInput.setText("11.0") 
     274        # Check new values 
     275        self.assertEqual(self.widget._calculator.get_dmax(), 1.0) 
     276        self.assertEqual(self.widget._calculator.get_qmax(), 3.0) 
     277        self.assertEqual(self.widget._calculator.get_qmin(), 5.0) 
     278        self.assertEqual(self.widget._calculator.slit_height, 7.0) 
     279        self.assertEqual(self.widget._calculator.slit_width, 9.0) 
     280        self.assertEqual(self.widget._calculator.alpha, 11.0) 
     281        # Change model directly 
     282        self.widget.model.setItem(WIDGETS.W_MAX_DIST, QtGui.QStandardItem("2.0")) 
     283        self.widget.model.setItem(WIDGETS.W_QMIN, QtGui.QStandardItem("4.0")) 
     284        self.widget.model.setItem(WIDGETS.W_QMAX, QtGui.QStandardItem("6.0")) 
     285        self.widget.model.setItem(WIDGETS.W_SLIT_HEIGHT, QtGui.QStandardItem("8.0")) 
     286        self.widget.model.setItem(WIDGETS.W_SLIT_WIDTH, QtGui.QStandardItem("10.0")) 
     287        self.widget.model.setItem(WIDGETS.W_REGULARIZATION, QtGui.QStandardItem("12.0")) 
     288        # Check values 
     289        self.assertEqual(self.widget._calculator.get_dmax(), 2.0) 
     290        self.assertEqual(self.widget._calculator.get_qmin(), 4.0) 
     291        self.assertEqual(self.widget._calculator.get_qmax(), 6.0) 
     292        self.assertEqual(self.widget._calculator.slit_height, 8.0) 
     293        self.assertEqual(self.widget._calculator.slit_width, 10.0) 
     294        self.assertEqual(self.widget._calculator.alpha, 12.0) 
     295        self.removeAllData() 
     296 
     297    def testOpenExplorerWindow(self): 
     298        """ open Dx window """ 
     299        self.assertIsNone(self.widget.dmaxWindow) 
     300        self.assertFalse(self.widget.explorerButton.isEnabled()) 
     301        self.widget.openExplorerWindow() 
     302        self.assertIsNotNone(self.widget.dmaxWindow) 
     303        self.assertTrue(self.widget.dmaxWindow.isVisible()) 
     304        self.assertTrue(self.widget.dmaxWindow.windowTitle() == "Dₐₓ Explorer") 
     305 
    169306 
    170307if __name__ == "__main__": 
  • src/sas/qtgui/Perspectives/Inversion/__init__.py

    • Property mode changed from 100755 to 100644
  • src/sas/qtgui/Perspectives/Inversion/media/pr_help.rst

    r417c03f r417c03f  
    6868.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 
    6969 
     70.. _Batch_Pr_Mode: 
     71 
     72Batch P(r) inversion 
     73-------------------- 
     74 
     75The p(r) calculator accepts any number of data sets, and supports batch fitting 
     76for all data sets in the p(r) data set combo box. Switching between data sets in 
     77the combo box allows for individual fits and review of fit parameters. The 
     78displayed plots will also update to include the latest fit results for the 
     79selected data set. 
     80 
     81The 'Calculate All' button will run the p(r) calculation for each file, 
     82sequentially. The calculator will estimate the number of terms, background, and 
     83regularization constant for each file prior to running the p(r) calculation. 
     84The final results for all data sets are then presented in a savable data table. 
     85 
     86.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 
     87 
    7088Reference 
    7189--------- 
     
    7694.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 
    7795 
    78 .. note::  This help document was last modified by Paul Butler, 05 September, 2016 
     96.. note::  This help document was last modified by Jeff Krzywon, 18 April 2018 
  • src/sas/qtgui/Perspectives/__init__.py

    • Property mode changed from 100755 to 100644
  • src/sas/qtgui/Plotting/ConvertUnits.py

    • Property mode changed from 100644 to 100755
  • src/sas/qtgui/Plotting/DataTransform.py

    • Property mode changed from 100644 to 100755
  • src/sas/qtgui/Plotting/Slicers/Arc.py

    • Property mode changed from 100644 to 100755
  • src/sas/qtgui/Utilities/GridPanel.py

    raed0532 raed0532  
    410410            index += 1 
    411411 
     412 
     413class BatchInversionOutputPanel(BatchOutputPanel): 
     414    """ 
     415        Class for stateless grid-like printout of P(r) parameters for any number 
     416        of data sets 
     417    """ 
     418    def __init__(self, parent = None, output_data=None): 
     419 
     420        super(BatchInversionOutputPanel, self).__init__(parent, output_data) 
     421        _translate = QtCore.QCoreApplication.translate 
     422        self.setWindowTitle(_translate("GridPanelUI", "Batch P(r) Results")) 
     423 
     424    def setupTable(self, data): 
     425        """ 
     426        Create tablewidget items and show them, based on params 
     427        """ 
     428        # headers 
     429        param_list = ['Filename', 'Rg [à
     430]', 'Chi^2/dof', 'I(Q=0)', 'Oscillations', 
     431                      'Background [à
     432^-1]', 'P+ Fraction', 'P+1-theta Fraction', 
     433                      'Calc. Time [sec]'] 
     434 
     435        keys = data.keys() 
     436        rows = len(keys) 
     437        columns = len(param_list) 
     438        self.tblParams.setColumnCount(columns) 
     439        self.tblParams.setRowCount(rows) 
     440 
     441        for i, param in enumerate(param_list): 
     442            self.tblParams.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param)) 
     443 
     444        # first - Chi2 and data filename 
     445        for i_row, (filename, pr) in enumerate(data.items()): 
     446            out = pr.out 
     447            cov = pr.cov 
     448            if out is None: 
     449                logging.warning("P(r) for {} did not converge.".format(filename)) 
     450                continue 
     451            self.tblParams.setItem(i_row, 0, QtWidgets.QTableWidgetItem( 
     452                "{}".format(filename))) 
     453            self.tblParams.setItem(i_row, 1, QtWidgets.QTableWidgetItem( 
     454                "{:.3g}".format(pr.rg(out)))) 
     455            self.tblParams.setItem(i_row, 2, QtWidgets.QTableWidgetItem( 
     456                "{:.3g}".format(pr.chi2[0]))) 
     457            self.tblParams.setItem(i_row, 3, QtWidgets.QTableWidgetItem( 
     458                "{:.3g}".format(pr.iq0(out)))) 
     459            self.tblParams.setItem(i_row, 4, QtWidgets.QTableWidgetItem( 
     460                "{:.3g}".format(pr.oscillations(out)))) 
     461            self.tblParams.setItem(i_row, 5, QtWidgets.QTableWidgetItem( 
     462                "{:.3g}".format(pr.background))) 
     463            self.tblParams.setItem(i_row, 6, QtWidgets.QTableWidgetItem( 
     464                "{:.3g}".format(pr.get_positive(out)))) 
     465            self.tblParams.setItem(i_row, 7, QtWidgets.QTableWidgetItem( 
     466                "{:.3g}".format(pr.get_pos_err(out, cov)))) 
     467            self.tblParams.setItem(i_row, 8, QtWidgets.QTableWidgetItem( 
     468                "{:.2g}".format(pr.elapsed))) 
     469 
     470        self.tblParams.resizeColumnsToContents() 
     471 
     472    @classmethod 
     473    def onHelp(cls): 
     474        """ 
     475        Open a local url in the default browser 
     476        """ 
     477        location = GuiUtils.HELP_DIRECTORY_LOCATION 
     478        url = "/user/sasgui/perspectives/pr/pr_help.html#batch-pr-mode" 
     479        try: 
     480            webbrowser.open('file://' + os.path.realpath(location + url)) 
     481        except webbrowser.Error as ex: 
     482            logging.warning("Cannot display help. %s" % ex) 
     483 
     484    def closeEvent(self, event): 
     485        """Tell the parent window the window closed""" 
     486        self.parent.batchResultsWindow = None 
     487        event.accept() 
  • src/sas/sascalc/pr/invertor.py

    r8f83719f r6da860a  
    481481            float(chi2) 
    482482        except: 
    483             chi2 = [-1.0] 
     483            chi2 = -1.0 
    484484        self.chi2 = chi2 
    485485 
Note: See TracChangeset for help on using the changeset viewer.