Exemplo n.º 1
0
Arquivo: blits.py Projeto: ubenu/Blits
 def on_create(self):  
     if self.current_state in (self.FUNCTION_ONLY, ):
         self.create_data_set_dialog = DataCreationDialog(None, self.current_function)
         if self.create_data_set_dialog.exec() == widgets.QDialog.Accepted:
             self.blits_data = BlitsData()
             self.blits_data.series_names = self.create_data_set_dialog.get_series_names()
             self.blits_data.axis_names = self.create_data_set_dialog.get_axes()
             self.blits_data.series_dict = self.create_data_set_dialog.get_series_dict()
             df_pars = self.create_data_set_dialog.get_parameters()
             self.current_state = self.ST_READY
             self.current_xaxis = self.blits_data.get_axes_names()[0]
             try:
                 self.set_axis_selector()
                 self.draw_current_data_set()
                 self.init_fit_spec()
                 for pname, row in df_pars.iterrows():
                     for sname, val in row.iteritems():
                         self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES], pname, sname] = val                                 
                 self.init_ui()
             except Exception as e:
                 print(e)
             self.update_controls()
             self.on_select_function()
         pass
     pass
Exemplo n.º 2
0
Arquivo: blits.py Projeto: ubenu/Blits
 def on_select_function(self):
     if self.current_state in range(self.N_STATES):  # should work from all states
         name, n_axes = "", 0
         if not self.current_state in (self.ST_START, self.ST_DATA_ONLY):  # a current function exists
             name = self.current_function.name
         if self.current_state in (self.ST_DATA_ONLY, self.ST_READY, ):
             n_axes = len(self.blits_data.get_axes_names())
         self.function_dialog = FunctionSelectionDialog(self, n_axes=n_axes, selected_fn_name=name)
         if self.function_dialog.exec() == widgets.QDialog.Accepted:
             self.current_function = self.function_dialog.get_selected_function()
             self.blits_fitted = BlitsData()
             self.blits_residuals = BlitsData()
             if self.current_state in (self.ST_START, self.FUNCTION_ONLY):
                 self.current_state = self.FUNCTION_ONLY
             else:
                 self.current_state = self.ST_READY
             self.init_fit_spec()
             self.init_ui()
             self.draw_current_data_set()
             self.update_controls()
Exemplo n.º 3
0
Arquivo: blits.py Projeto: ubenu/Blits
 def set_residual_curves(self):
     selected_series = self.get_selected_series_names()
     params = self.get_param_values_for_fitting(selected_series)
     data = self.get_data_for_fitting(selected_series)
     axes = self.blits_data.get_axes_names()
     
     series_dict = {}
     for series_name, series_params, i in zip(selected_series, params, range(len(selected_series))):
         x = data[i][:-1]
         y_obs = data[i][-1]
         y_fit = self.current_function.func(x, series_params)
         y_res = np.atleast_2d(y_obs - y_fit)
         # create the y values and put them in a DataFrame, transpose for easy concatenation
         df_x = pd.DataFrame(x, index=axes)
         df_y = pd.DataFrame(y_res, index=[series_name])
         df_data = pd.concat((df_x, df_y)).transpose()
         series_dict[series_name] = df_data
     self.blits_residuals = BlitsData()
     self.blits_residuals.series_names = np.array(selected_series)
     self.blits_residuals.axis_names = cp.deepcopy(axes)
     self.blits_residuals.series_dict = series_dict
Exemplo n.º 4
0
Arquivo: blits.py Projeto: ubenu/Blits
    def set_calculated_curves(self):
        selected_series = self.get_selected_series_names()
        params = self.get_param_values_for_fitting(selected_series)
        data = self.get_data_for_fitting(selected_series)
        axes = self.blits_data.get_axes_names()

        series_dict = {}
        for series_name, series_params, i in zip(selected_series, params, range(len(selected_series))):
            x_all = data[i][:-1]
            x = np.zeros((x_all.shape[0], self.nfitted_points))
            for i in range(x_all.shape[0]):
                start, stop = x_all[i][0], x_all[i][-1]
                x[i] = np.linspace(start, stop, self.nfitted_points)                
            y_fit = np.atleast_2d(self.current_function.func(x, series_params))
            # create the y values and put them in a DataFrame, transpose for easy concatenation
            df_x = pd.DataFrame(x, index=axes)
            df_y = pd.DataFrame(y_fit, index=[series_name])
            df_data = pd.concat((df_x, df_y)).transpose()
            series_dict[series_name] = df_data
        self.blits_fitted = BlitsData()
        self.blits_fitted.series_names= np.array(selected_series)
        self.blits_fitted.axis_names = cp.deepcopy(axes)
        self.blits_fitted.series_dict = series_dict
Exemplo n.º 5
0
Arquivo: blits.py Projeto: ubenu/Blits
 def on_close_data(self):
     if self.current_state in (self.ST_DATA_ONLY, self.ST_READY, ):
         self.current_xaxis = None
         self.set_axis_selector()
         self.canvas.clear_plots()
         
         self.blits_data = BlitsData()
         self.blits_fitted = BlitsData()
         self.blits_residuals = BlitsData()
         
         if self.current_state == self.ST_DATA_ONLY:
             self.current_state = self.ST_START
         else:
             self.current_state = self.FUNCTION_ONLY
             
         self.init_fit_spec()
         self.init_ui()
         self.update_controls()
     pass
Exemplo n.º 6
0
Arquivo: blits.py Projeto: ubenu/Blits
    def __init__(self, ):
        super(Main, self).__init__()
        self.setupUi(self)

        self.scrutinize_dialog = None
        self.function_dialog = None
        self.create_data_set_dialog = None
                
        self.canvas = MplCanvas(self.mpl_window)
        self.plot_toolbar = NavigationToolbar(self.canvas, self.mpl_window)
        self.mpl_layout.addWidget(self.canvas)
        self.grp_show_axis = widgets.QGroupBox()
        self.axis_layout = widgets.QHBoxLayout()
        self.grp_show_axis.setLayout(self.axis_layout)
        self.grp_show_axis.setSizePolicy(widgets.QSizePolicy.Maximum, widgets.QSizePolicy.Maximum)
        self.axisgrp_layout = widgets.QHBoxLayout()
        self.axisgrp_layout.addWidget(self.grp_show_axis)
        self.mpl_layout.addLayout(self.axisgrp_layout)
        self.mpl_layout.addWidget(self.plot_toolbar)

        ft = gui.QFont('Calibri', 14)
        self.btn_est = widgets.QPushButton("Estimate")
        self.btn_est.setFont(ft)
        self.btn_apply = widgets.QPushButton("Calculate")
        self.btn_apply.setFont(ft)
        self.btn_fit = widgets.QPushButton("Fit")
        self.btn_fit.setFont(ft)
        self.bbox_fit.addButton(self.btn_apply, widgets.QDialogButtonBox.ActionRole)
        self.bbox_fit.addButton(self.btn_est, widgets.QDialogButtonBox.ActionRole)
        self.bbox_fit.addButton(self.btn_fit, widgets.QDialogButtonBox.ActionRole)

        self.action_open.triggered.connect(self.on_open)
        self.action_create.triggered.connect(self.on_create)
        self.action_close.triggered.connect(self.on_close_data)
        self.action_save.triggered.connect(self.on_save)
        self.action_select_function.triggered.connect(self.on_select_function)
        self.action_analyze.triggered.connect(self.on_analyze)
        self.action_quit.triggered.connect(self.close) 
        self.action_apply.triggered.connect(self.on_calculate) 
        self.action_estimate.triggered.connect(self.on_estimate) 

        self.btn_est.clicked.connect(self.on_estimate)
        self.btn_apply.clicked.connect(self.on_calculate)
        self.btn_fit.clicked.connect(self.on_analyze)
        
        self.chk_global.stateChanged.connect(self.on_global_changed)
        
        self.blits_data = BlitsData()
        self.blits_fitted = BlitsData()
        self.blits_residuals = BlitsData()
        
        self.pn_fit_spec = None
        self.df_params_spec = None
        self.df_series_spec = None
        self.df_xlimits = None

        self.current_xaxis = None
        self.axis_selector_buttons = None
        self.current_function = None
        
        self.nfitted_points = 100
        self.npoints_max = 1000
        
        self.current_state = self.ST_START        
        self.update_controls()
Exemplo n.º 7
0
Arquivo: blits.py Projeto: ubenu/Blits
class Main(widgets.QMainWindow, ui.Ui_MainWindow):
    
    N_STATES = 5
    ST_START, ST_DATA_ONLY, FUNCTION_ONLY, ST_READY, REJECT = range(N_STATES)
    
    N_PS_SPECTYPES = 7
    PS_VALUES, PS_LEDITS, PS_VALUE_FIXED, PS_FIX_CBOXES, PS_GROUPS, PS_COMBOS, PS_SIGMAS = range(N_PS_SPECTYPES)
    N_P_SPECTYPES = 4
    P_ALL_FIXED, P_FIX_CBOXES, P_ALL_LINKED, P_LINK_CBOXES = range(N_P_SPECTYPES)
    N_S_SPECTYPES = 3
    S_INCLUDED, S_INCLUDE_CBOXES, S_FTOL = range(N_S_SPECTYPES)
    
    ps_types = ['param_values', 'param_line_edits', 'param_values_fixed', 'param_fix_cboxes', 'series_groups', 'series_combos', 'sigmas']
    s_types = ['included', 'included_cboxes', 'ftol']
    p_types = ['all_fixed', 'all_fixed_cboxes', 'all_linked', 'all_linked_cboxes']

    def __init__(self, ):
        super(Main, self).__init__()
        self.setupUi(self)

        self.scrutinize_dialog = None
        self.function_dialog = None
        self.create_data_set_dialog = None
                
        self.canvas = MplCanvas(self.mpl_window)
        self.plot_toolbar = NavigationToolbar(self.canvas, self.mpl_window)
        self.mpl_layout.addWidget(self.canvas)
        self.grp_show_axis = widgets.QGroupBox()
        self.axis_layout = widgets.QHBoxLayout()
        self.grp_show_axis.setLayout(self.axis_layout)
        self.grp_show_axis.setSizePolicy(widgets.QSizePolicy.Maximum, widgets.QSizePolicy.Maximum)
        self.axisgrp_layout = widgets.QHBoxLayout()
        self.axisgrp_layout.addWidget(self.grp_show_axis)
        self.mpl_layout.addLayout(self.axisgrp_layout)
        self.mpl_layout.addWidget(self.plot_toolbar)

        ft = gui.QFont('Calibri', 14)
        self.btn_est = widgets.QPushButton("Estimate")
        self.btn_est.setFont(ft)
        self.btn_apply = widgets.QPushButton("Calculate")
        self.btn_apply.setFont(ft)
        self.btn_fit = widgets.QPushButton("Fit")
        self.btn_fit.setFont(ft)
        self.bbox_fit.addButton(self.btn_apply, widgets.QDialogButtonBox.ActionRole)
        self.bbox_fit.addButton(self.btn_est, widgets.QDialogButtonBox.ActionRole)
        self.bbox_fit.addButton(self.btn_fit, widgets.QDialogButtonBox.ActionRole)

        self.action_open.triggered.connect(self.on_open)
        self.action_create.triggered.connect(self.on_create)
        self.action_close.triggered.connect(self.on_close_data)
        self.action_save.triggered.connect(self.on_save)
        self.action_select_function.triggered.connect(self.on_select_function)
        self.action_analyze.triggered.connect(self.on_analyze)
        self.action_quit.triggered.connect(self.close) 
        self.action_apply.triggered.connect(self.on_calculate) 
        self.action_estimate.triggered.connect(self.on_estimate) 

        self.btn_est.clicked.connect(self.on_estimate)
        self.btn_apply.clicked.connect(self.on_calculate)
        self.btn_fit.clicked.connect(self.on_analyze)
        
        self.chk_global.stateChanged.connect(self.on_global_changed)
        
        self.blits_data = BlitsData()
        self.blits_fitted = BlitsData()
        self.blits_residuals = BlitsData()
        
        self.pn_fit_spec = None
        self.df_params_spec = None
        self.df_series_spec = None
        self.df_xlimits = None

        self.current_xaxis = None
        self.axis_selector_buttons = None
        self.current_function = None
        
        self.nfitted_points = 100
        self.npoints_max = 1000
        
        self.current_state = self.ST_START        
        self.update_controls()
        
    def init_fit_spec(self):      
        self.df_xlimits = None  
        self.pn_fit_spec = None
        self.df_series_spec = None 
        self.df_params_spec = None
        if self.current_state in (self.ST_READY, ):
            series_names = self.blits_data.get_series_names()
            param_names = self.current_function.get_parameter_names()
            axis_names = self.blits_data.get_axes_names()
            
            self.df_xlimits = pd.DataFrame(columns=['min', 'max'], index=axis_names)
            mins, maxs = self.blits_data.series_extremes()
            xmins, xmaxs = mins.iloc[:, :-1].min(axis=0), maxs.iloc[:, :-1].max(axis=0)
            self.df_xlimits.loc[:, 'min'] = xmins
            self.df_xlimits.loc[:, 'max'] = xmaxs

            self.pn_fit_spec = pd.Panel(major_axis=param_names, minor_axis=series_names, items=self.ps_types)
            self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES]] = 1.0
            self.pn_fit_spec.loc[self.ps_types[self.PS_VALUE_FIXED]] = qt.Qt.Unchecked
            
            self.df_series_spec = pd.DataFrame(index=series_names, columns=self.s_types)
            self.df_series_spec.loc[:, self.s_types[self.S_INCLUDED]] = qt.Qt.Checked
            self.df_params_spec = pd.DataFrame(index=param_names, columns=self.p_types)
            self.df_params_spec.loc[:, self.p_types[self.P_ALL_FIXED]] = qt.Qt.Unchecked
            self.df_params_spec.loc[:, self.p_types[self.P_ALL_LINKED]] = qt.Qt.Unchecked
            
            for sname in series_names:
                cbx = widgets.QCheckBox()
                cbx.setText("")
                cbx.setToolTip("Uncheck to exclude from analysis")
                cbx.setCheckState(int(self.df_series_spec.loc[sname, self.s_types[self.S_INCLUDED]])) 
                # int() is necessary for the checkbox to recognise the type as valid (int64 isn't)
                self.df_series_spec.loc[sname, self.s_types[self.S_INCLUDE_CBOXES]] = cbx 
                cbx.stateChanged.connect(self.on_series_selected_changed)
                           
            for pname in param_names:
                cb_lnk = widgets.QCheckBox()
                cb_lnk.setCheckState(qt.Qt.Unchecked)
                cb_lnk.setText("")
                cb_lnk.setToolTip("Check to link " + pname + " across all series")
                cb_lnk.stateChanged.connect(self.on_all_linked_changed)
                
                cb_fix = widgets.QCheckBox()
                cb_fix.setCheckState(qt.Qt.Unchecked)
                cb_fix.setText("")
                cb_fix.setToolTip("Check to keep " + pname + " constant for all series")
                cb_fix.stateChanged.connect(self.on_all_fixed_changed)
                
                self.df_params_spec.loc[pname, self.p_types[self.P_ALL_LINKED]] = int(cb_lnk.checkState())
                self.df_params_spec.loc[pname, self.p_types[self.P_LINK_CBOXES]] = cb_lnk
                self.df_params_spec.loc[pname, self.p_types[self.P_ALL_FIXED]] = int(cb_fix.checkState())
                self.df_params_spec.loc[pname, self.p_types[self.P_FIX_CBOXES]] = cb_fix
                
            for pname in param_names:      
                for sname in series_names:
                    edt = widgets.QLineEdit()
                    edt.setValidator(gui.QDoubleValidator())
                    edt.setText("{:.3g}".format(self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES], pname, sname]))
                    edt.textChanged.connect(self.on_param_val_changed)
                    cbx = widgets.QCheckBox()
                    cbx.setToolTip("Check to keep " + pname + " constant for series " + sname)
                    cbx.setCheckState(qt.Qt.Unchecked)
                    cbx.stateChanged.connect(self.on_param_fix_changed)
                    
                    combo = widgets.QComboBox()
                    combo.addItems(series_names)
                    combo.setEditable(False)
                    combo.setCurrentText(sname)
                    combo.currentIndexChanged.connect(self.on_linkage_changed)
                    try:
                        sp_vals = [float(edt.text()), edt, cbx.checkState(), cbx, combo.currentText(), combo]
                        for sp, val in zip(self.ps_types, sp_vals):
                            self.pn_fit_spec.loc[sp, pname, sname] = val
                    except Exception as e:
                        print(e)
                
    def init_ui(self):
        self.tbl_series_links.clear()
        self.tbl_series_links.setRowCount(0)
        self.tbl_series_links.setColumnCount(0)
        self.tbl_param_values.clear()
        self.tbl_param_values.setRowCount(0)
        self.tbl_param_values.setColumnCount(0)
        if self.current_state not in (self.ST_START, self.ST_DATA_ONLY,): # there is a current function
            self.lbl_fn_name.setText("Selected function: " + self.current_function.name)
            self.txt_description.setText(self.current_function.long_description)
        else:
            self.lbl_fn_name.setText("Selected function: None")
            self.txt_description.setText("")
        if self.current_state in (self.ST_READY, ):
            if self.pn_fit_spec is not None:
                params = self.pn_fit_spec.major_axis.values
                series = self.pn_fit_spec.minor_axis.values
                colours = self.canvas.curve_colours
                
                ptbl_vheader = [widgets.QTableWidgetItem("All")]
                for sname in series:
                    i = widgets.QTableWidgetItem(sname)
                    i.setIcon(self.line_icon(colours[sname]))
                    ptbl_vheader.extend([i])
                self.tbl_param_values.setRowCount(len(ptbl_vheader))
                for i in range(len(ptbl_vheader)):
                    self.tbl_param_values.setVerticalHeaderItem(i, ptbl_vheader[i])
                ptbl_hheader = ["Include"]
                ptbl_hheader.extend(params)
                self.tbl_param_values.setColumnCount(len(ptbl_hheader))
                self.tbl_param_values.setHorizontalHeaderLabels(ptbl_hheader)


                ltbl_vheader = [widgets.QTableWidgetItem("All")]
                for sname in series:
                    i = widgets.QTableWidgetItem(sname)
                    i.setIcon(self.line_icon(colours[sname]))
                    ltbl_vheader .extend([i])
                self.tbl_series_links.setRowCount(len(ltbl_vheader))
                for i in range(len(ltbl_vheader )):
                    self.tbl_series_links.setVerticalHeaderItem(i, ltbl_vheader[i])
                ltbl_hheader = []
                ltbl_hheader.extend(params)              
                self.tbl_series_links.setColumnCount(len(ltbl_hheader))            
                self.tbl_series_links.setHorizontalHeaderLabels(ltbl_hheader)
                                
                # create the parameter values table
                vrange = range(len(ptbl_vheader)-len(series), len(ptbl_vheader))
                hrange = range((len(ptbl_hheader)-len(params)), len(ptbl_hheader))
                for sname, row in zip(series, vrange):
                    w = self.centred_tablewidget(self.df_series_spec.loc[sname, self.s_types[self.S_INCLUDE_CBOXES]])
                    self.tbl_param_values.setCellWidget(row, 0, w)
                for pname, col in zip(params, hrange):
                    w = self.centred_tablewidget(self.df_params_spec.loc[pname, self.p_types[self.P_FIX_CBOXES]])
                    self.tbl_param_values.setCellWidget(0, col, w)
                for sname, row in zip(series, vrange):
                    for pname, col in zip(params, hrange):
                        edt = self.pn_fit_spec.loc[self.ps_types[self.PS_LEDITS], pname, sname]
                        cbx = self.pn_fit_spec.loc[self.ps_types[self.PS_FIX_CBOXES], pname, sname]
                        w = self.checkable_edit_widget(cbx, edt)
                        self.tbl_param_values.setCellWidget(row, col, w)
                        
                # create the linkage table
                vrange = range(len(ltbl_vheader)-len(series), len(ltbl_vheader))
                hrange = range((len(ltbl_hheader)-len(params)), len(ltbl_hheader))
                for pname, col in zip(params, hrange):
                    w = self.centred_tablewidget(self.df_params_spec.loc[pname, 'all_linked_cboxes'])
                    self.tbl_series_links.setCellWidget(0, col, w)
                for sname, row in zip(series, vrange):
                    for pname, col in zip(params, hrange):
                        self.tbl_series_links.setCellWidget(row, col, self.pn_fit_spec.loc['series_combos', pname, sname])
                
                self.tbl_param_values.resizeRowsToContents()
                self.tbl_series_links.resizeRowsToContents()
                self.on_global_changed()

    def on_all_fixed_changed(self):
        if self.current_state in (self.ST_READY, ):
            param, col = self.find_sender_index(self.df_params_spec)
            if param is not None:
                checkstate = int(self.df_params_spec.loc[param, col].checkState())
                self.df_params_spec.loc[param, self.p_types[self.P_ALL_FIXED]] = checkstate # synchronise with logical representation
                self.pn_fit_spec.loc[self.ps_types[self.PS_VALUE_FIXED], param] = checkstate
                self.update_param_vals_table()

    def on_all_linked_changed(self):
        if self.current_state in (self.ST_READY, ):
            param, col = self.find_sender_index(self.df_params_spec)
            if param is not None:
                checkstate = self.df_params_spec.loc[param, col].checkState()
                self.df_params_spec.loc[param, self.p_types[self.P_ALL_LINKED]] = checkstate # synchronise with logical representation
                linkto = self.pn_fit_spec.loc[self.ps_types[self.PS_GROUPS], param].iloc[0]
                for series in self.pn_fit_spec.loc[self.ps_types[self.PS_GROUPS], param].index:
                    if checkstate == qt.Qt.Unchecked: 
                        linkto = series
                    self.pn_fit_spec.loc[self.ps_types[self.PS_GROUPS], param, series] = linkto
                self.update_linkage_table()
            
    def on_analyze(self):
        if self.current_state in (self.ST_READY, ):
            try:
                params = self.current_function.parameters
                series = self.get_selected_series_names()
                
                fitted_params, sigmas, confidence_intervals, tol = self.perform_fit()

                df_pars = pd.DataFrame(fitted_params.transpose(), index=params, columns=series) 
                df_sigm = pd.DataFrame(sigmas.transpose(), index=params, columns=series) 
                sr_ftol = pd.Series(tol, index=series)          
                for pname, row in df_pars.iterrows():
                    for sname, val in row.iteritems():
                        self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES], pname, sname] = val
                for pname, row in df_sigm.iterrows():
                    for sname, val in row.iteritems():
                        self.pn_fit_spec.loc[self.ps_types[self.PS_SIGMAS], pname, sname] = val
                for sname, val in sr_ftol.iteritems():
                    self.df_series_spec.loc[sname, self.s_types[self.S_FTOL]] = val
                self.on_calculate()
                self.update_controls()
                self.update_param_vals_table()
                self.show_selected_data()
                self.show_smooth_line()
                self.show_fitted_params()
            except Exception as e:
                print(e)
        pass
            
    def on_calculate(self):
        if self.current_state in (self.ST_READY, ):
            self.set_calculated_curves()
            self.set_residual_curves()
            self.draw_current_data_set()
        pass  
    
    def on_close_data(self):
        if self.current_state in (self.ST_DATA_ONLY, self.ST_READY, ):
            self.current_xaxis = None
            self.set_axis_selector()
            self.canvas.clear_plots()
            
            self.blits_data = BlitsData()
            self.blits_fitted = BlitsData()
            self.blits_residuals = BlitsData()
            
            if self.current_state == self.ST_DATA_ONLY:
                self.current_state = self.ST_START
            else:
                self.current_state = self.FUNCTION_ONLY
                
            self.init_fit_spec()
            self.init_ui()
            self.update_controls()
        pass
            
    def on_create(self):  
        if self.current_state in (self.FUNCTION_ONLY, ):
            self.create_data_set_dialog = DataCreationDialog(None, self.current_function)
            if self.create_data_set_dialog.exec() == widgets.QDialog.Accepted:
                self.blits_data = BlitsData()
                self.blits_data.series_names = self.create_data_set_dialog.get_series_names()
                self.blits_data.axis_names = self.create_data_set_dialog.get_axes()
                self.blits_data.series_dict = self.create_data_set_dialog.get_series_dict()
                df_pars = self.create_data_set_dialog.get_parameters()
                self.current_state = self.ST_READY
                self.current_xaxis = self.blits_data.get_axes_names()[0]
                try:
                    self.set_axis_selector()
                    self.draw_current_data_set()
                    self.init_fit_spec()
                    for pname, row in df_pars.iterrows():
                        for sname, val in row.iteritems():
                            self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES], pname, sname] = val                                 
                    self.init_ui()
                except Exception as e:
                    print(e)
                self.update_controls()
                self.on_select_function()
            pass
        pass
                
    def on_estimate(self):
        if self.current_state in (self.ST_READY, ):
            fn_p0 = self.current_function.p0
            params = self.current_function.parameters
            series = self.get_selected_series_names()
            data = self.get_data_for_fitting(series)
            ffw = FunctionsFramework()
            values = ffw.get_initial_param_estimates(data, fn_p0, len(params)).transpose()
            df_pars = pd.DataFrame(values, index=params, columns=series)
            try:
                for pname, row in df_pars.iterrows():
                    for sname, val in row.iteritems():
                        self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES], pname, sname] = val
            except Exception as e:
                print(e)
            self.update_param_vals_table()
            self.on_calculate()
        pass 
    
    def on_global_changed(self):
        if self.chk_global.checkState() == qt.Qt.Checked:
            self.tbl_series_links.setEnabled(True)
        else:
            self.tbl_series_links.setEnabled(False)
    
    def on_linkage_changed(self):
        if self.current_state in (self.ST_READY, ):
            df = self.pn_fit_spec.loc[self.ps_types[self.PS_COMBOS]]
            param, series = self.find_sender_index(df)
            if param is not None and series is not None:
                link = df.loc[param, series].currentText()
                self.pn_fit_spec.loc[self.ps_types[self.PS_GROUPS], param, series] = link
                self.rationalise_groups(param)
                self.update_linkage_table()
            pass
        pass
            
    def on_open(self):
        if self.current_state in (self.ST_START, self.FUNCTION_ONLY, ):
            file_path = widgets.QFileDialog.getOpenFileName(self, 
            "Open Data File", "", "CSV data files (*.csv);;All files (*.*)")[0]
            if file_path:
                self.blits_data.import_data(file_path)
                axes = self.blits_data.get_axes_names() #cp.deepcopy(self.blits_data.get_axes_names())
                self.current_xaxis = axes[0] #self.blits_data.get_axes_names()[0]
                if self.current_state == self.ST_START:
                    self.current_state = self.ST_DATA_ONLY
                else:
                    if len(self.current_function.independents) <= len(axes):
                        self.current_state = self.ST_READY
                    else:
                        self.current_function = None
                        self.current_state = self.ST_DATA_ONLY
                self.set_axis_selector()
                self.init_fit_spec()
                self.init_ui()
                self.update_controls()
                self.on_select_function()
                    
    def on_param_fix_changed(self):
        if self.current_state in (self.ST_READY, ):
            param, series = None, None
            df = self.pn_fit_spec.loc[self.ps_types[self.PS_FIX_CBOXES]]
            param, series = self.find_sender_index(df)
            if param is not None and series is not None:
                param, series = self.find_sender_index(df)
                try:
                    self.pn_fit_spec.loc[self.ps_types[self.PS_VALUE_FIXED], param, series] = int(self.sender().checkState())
                except Exception as e:
                    print(e)                
                    
    def on_param_val_changed(self):
        if self.current_state in (self.ST_READY, ):
            param, series = None, None
            df = self.pn_fit_spec.loc[self.ps_types[self.PS_LEDITS]]
            param, series = self.find_sender_index(df)
            if param is not None and series is not None:
                param, series = self.find_sender_index(df)
                try:
                    self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES], param, series] = float(self.sender().text())
                except Exception as e:
                    print(e)
        
    def on_series_selected_changed(self):
        if self.current_state in (self.ST_READY, ):
            series, col = None, None
            series, col = self.find_sender_index(self.df_series_spec)
            if series is not None:
                try:
                    checkstate = self.df_series_spec.loc[series, col].checkState()
                    self.df_series_spec.loc[series, self.s_types[self.S_INCLUDED]] = int(checkstate) 
                    # synchronise with logical representation; int is necessary to make sure Qt recognises it (won't recognise int64 (??))
                except Exception as e:
                    print(e)
                    
    def on_save(self):
        file_path = ""
        if self.current_state in (self.ST_READY, ):
            file_path = widgets.QFileDialog.getSaveFileName(self, 
            "Save all", "", "Excel files (*.xlsx);;All files (*.*)")[0]
        if file_path:
            smooth_lines = self.get_xs_fitted_smooth_df()
            obs_fit_res = self.get_xs_obs_fit_res_df()
#            pd.concat((obs_fit_res, smooth_lines), axis=1)
            params = self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES]]
            try:
                writer = pd.ExcelWriter(file_path)
                obs_fit_res.to_excel(writer,'Data')
                smooth_lines.to_excel(writer, 'Fit')
                params.to_excel(writer,'Parameters')
                writer.save()
                writer.close()
            except Exception as e:
                print(e)
            
    def on_select_function(self):
        if self.current_state in range(self.N_STATES):  # should work from all states
            name, n_axes = "", 0
            if not self.current_state in (self.ST_START, self.ST_DATA_ONLY):  # a current function exists
                name = self.current_function.name
            if self.current_state in (self.ST_DATA_ONLY, self.ST_READY, ):
                n_axes = len(self.blits_data.get_axes_names())
            self.function_dialog = FunctionSelectionDialog(self, n_axes=n_axes, selected_fn_name=name)
            if self.function_dialog.exec() == widgets.QDialog.Accepted:
                self.current_function = self.function_dialog.get_selected_function()
                self.blits_fitted = BlitsData()
                self.blits_residuals = BlitsData()
                if self.current_state in (self.ST_START, self.FUNCTION_ONLY):
                    self.current_state = self.FUNCTION_ONLY
                else:
                    self.current_state = self.ST_READY
                self.init_fit_spec()
                self.init_ui()
                self.draw_current_data_set()
                self.update_controls()
    
    def on_xaxis_changed(self, checked):
        if self.current_state not in (self.ST_START, self.FUNCTION_ONLY, ):
            btn = self.sender()
            xaxis = btn.text()
            if btn.isChecked():
                self.preserve_xlimits()
                self.current_xaxis = xaxis
                self.draw_current_data_set() 
                
    def draw_current_data_set(self):
        self.canvas.clear_plots() 
        if self.current_state not in (self.ST_START, self.FUNCTION_ONLY, ):
            if self.blits_data.has_data():
                self.canvas.set_colours(self.blits_data.series_names.tolist())
                for key in self.blits_data.series_names:
                    series = self.blits_data.series_dict[key]
                    x = series[self.current_xaxis] 
                    y = series[key] 
                    self.canvas.draw_series(key, x, y, 'primary')
            if self.blits_fitted.has_data():
                for key in self.blits_fitted.series_names:
                    series = self.blits_fitted.series_dict[key]
                    x = series[self.current_xaxis] 
                    y = series[key] 
                    self.canvas.draw_series(key, x, y, 'calculated')
            if self.blits_residuals.has_data():
                for key in self.blits_residuals.series_names:
                    series = self.blits_residuals.series_dict[key]
                    x = series[self.current_xaxis] 
                    y = series[key] 
                    self.canvas.draw_series(key, x, y, 'residuals')
            if self.df_xlimits is not None:
                self.canvas.set_vlines(self.df_xlimits.loc[self.current_xaxis].as_matrix())
                
            
    def get_constant_params_for_fitting(self, series_names):
        """
        Returns an (n_curves, n_params)-shaped array of Boolean values 
        (with rows and columns parallel to self.series_names and self.current_function.parameters, 
        respectively) with values for each parameter for each series); if True, 
        parameter values is constant, if False, parameter value is variable.
        """
        selected = (self.pn_fit_spec.loc[self.ps_types[self.PS_VALUE_FIXED], :, series_names] == qt.Qt.Checked).transpose()
        return selected.as_matrix()
        
    def get_data_for_fitting(self, series_names):
        data = []
        self.preserve_xlimits()
        start, stop = self.df_xlimits.loc[self.current_xaxis].as_matrix() # self.canvas.get_vline_positions()
        for s in series_names:
            series = self.blits_data.series_dict[s] # the full data set
            indmin = series[self.current_xaxis].searchsorted(start, side='left')[0]
            indmax = series[self.current_xaxis].searchsorted(stop, side='right')[0]
            selection = cp.deepcopy(series[indmin:indmax]).as_matrix().transpose()
            if len(data) == 0:
                data = [selection]
            else:
                data.append(selection)
        return data
        
    def get_param_values_for_fitting(self, series_names):
        """
        Returns an (n_curves, n_params)-shaped array (with rows and columns 
        parallel to self.series_names and self.current_function.parameters, 
        respectively) with values for each parameter for each series).  
        """
        selected = self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES], :, series_names]
        params = selected.as_matrix().transpose()
        return params

    def get_selected_series_names(self):
        """
        Returns a numpy array of the selected series names
        """
        selected = self.df_series_spec.loc[:, self.s_types[self.S_INCLUDED]] == qt.Qt.Checked
        all_series = self.df_series_spec.index.values
        return all_series[selected]

    def get_series_linkage_for_fitting(self, series_names):
        """
        Returns an (n_curves, n_params)-shaped array (with rows and columns parallel to 
        self.series_names and self.current_function.parameters, respectively)
        of integers, in which linked parameters are grouped by their values.
        Example for 4 curves and 3 parameters:
              p0    p1    p2
        c0    0     2     3
        c1    0     2     4
        c2    1     2     5
        c3    1     2     6
        indicates that parameter p0 is assumed to have the same value in 
        curves c0 and c1, and in curves c2 and c3 (a different value), 
        and that the value for p1 is the same in all curves, whereas
        the value of p2 is different for all curves. 
        """
        selected = self.pn_fit_spec.loc[self.ps_types[self.PS_GROUPS], :, series_names].transpose()
        links_array = cp.deepcopy(selected)
        for series, row in selected.iterrows():
            for param, txt in row.iteritems():
                links_array.loc[series, param] = param + "_" + txt
        return links_array.as_matrix()
         
    def perform_fit(self):
        # Collect the required information
        func = self.current_function.func
        series_names = self.get_selected_series_names()  
        data = self.get_data_for_fitting(series_names)
        param_values = self.get_param_values_for_fitting(series_names)
        const_params = self.get_constant_params_for_fitting(series_names)  
        links = self.get_series_linkage_for_fitting(series_names)
        # set up for the fitting procedure
        fitted_params = cp.deepcopy(param_values)
        sigmas = np.empty_like(fitted_params)
        confidence_intervals = np.empty_like(fitted_params)
        tol = None
        results = None  
        ffw = FunctionsFramework()
        # Do the fit
        if self.chk_global.checkState() == qt.Qt.Checked: # Global
            results = ffw.perform_global_curve_fit(data, func, param_values, const_params, links)
            fitted_params = results[0]
            sigmas = results[1]
            confidence_intervals = results[2]
            tol = results[3]
        else: # not global)
            tol = []
            n = 0
            for d, p, c, l in zip(data, param_values, const_params, links):
                d = [d, ]
                p = np.reshape(p, (1, p.shape[0]))
                c = np.reshape(c, (1, c.shape[0]))
                l = np.reshape(l, (1, l.shape[0]))
                results = ffw.perform_global_curve_fit(d, func, p, c, l)
                fitted_params[n] = results[0]
                sigmas[n] = results[1]
                confidence_intervals[n] = results[2]
                tol.append(results[3])
                n += 1
        return fitted_params, sigmas, confidence_intervals, tol      

    def preserve_xlimits(self):
        if self.current_state in (self.ST_READY, ):
            if self.df_xlimits is not None: # its shouldn't be, but just to be sure
                self.df_xlimits.loc[self.current_xaxis] = self.canvas.get_vline_positions() 
        else:
            self.df_xlimits = None     # probably superfluous as well   


            
    def set_axis_selector(self):
        self.axis_selector_buttons = {}
        self.clearLayout(self.axis_layout)
        if self.blits_data.has_data():
            self.axis_layout.addStretch()
            for name in self.blits_data.get_axes_names():
                btn = widgets.QRadioButton()
                btn.setText(name)
                btn.toggled.connect(self.on_xaxis_changed)
                self.axis_layout.addWidget(btn)
                self.axis_selector_buttons[btn.text()] = btn
            self.axis_layout.addStretch()  
            if not self.current_xaxis is None:
                if self.current_xaxis in self.axis_selector_buttons:
                    self.axis_selector_buttons[self.current_xaxis].setChecked(True)
                
    def set_calculated_curves(self):
        selected_series = self.get_selected_series_names()
        params = self.get_param_values_for_fitting(selected_series)
        data = self.get_data_for_fitting(selected_series)
        axes = self.blits_data.get_axes_names()

        series_dict = {}
        for series_name, series_params, i in zip(selected_series, params, range(len(selected_series))):
            x_all = data[i][:-1]
            x = np.zeros((x_all.shape[0], self.nfitted_points))
            for i in range(x_all.shape[0]):
                start, stop = x_all[i][0], x_all[i][-1]
                x[i] = np.linspace(start, stop, self.nfitted_points)                
            y_fit = np.atleast_2d(self.current_function.func(x, series_params))
            # create the y values and put them in a DataFrame, transpose for easy concatenation
            df_x = pd.DataFrame(x, index=axes)
            df_y = pd.DataFrame(y_fit, index=[series_name])
            df_data = pd.concat((df_x, df_y)).transpose()
            series_dict[series_name] = df_data
        self.blits_fitted = BlitsData()
        self.blits_fitted.series_names= np.array(selected_series)
        self.blits_fitted.axis_names = cp.deepcopy(axes)
        self.blits_fitted.series_dict = series_dict
                
    def set_residual_curves(self):
        selected_series = self.get_selected_series_names()
        params = self.get_param_values_for_fitting(selected_series)
        data = self.get_data_for_fitting(selected_series)
        axes = self.blits_data.get_axes_names()
        
        series_dict = {}
        for series_name, series_params, i in zip(selected_series, params, range(len(selected_series))):
            x = data[i][:-1]
            y_obs = data[i][-1]
            y_fit = self.current_function.func(x, series_params)
            y_res = np.atleast_2d(y_obs - y_fit)
            # create the y values and put them in a DataFrame, transpose for easy concatenation
            df_x = pd.DataFrame(x, index=axes)
            df_y = pd.DataFrame(y_res, index=[series_name])
            df_data = pd.concat((df_x, df_y)).transpose()
            series_dict[series_name] = df_data
        self.blits_residuals = BlitsData()
        self.blits_residuals.series_names = np.array(selected_series)
        self.blits_residuals.axis_names = cp.deepcopy(axes)
        self.blits_residuals.series_dict = series_dict
        
    def get_xs_obs_fit_res_df(self):
        selected_series = self.get_selected_series_names()
        params = self.get_param_values_for_fitting(selected_series)
        data = self.get_data_for_fitting(selected_series)
        daxes = self.blits_data.get_axes_names()
        faxes = self.current_function.independents
        axes = np.array([f + "\n(" + a + ")"  for a, f in zip(daxes, faxes)])
        df_data = None
        for series_name, series_params, i in zip(selected_series, params, range(len(selected_series))):
            x = data[i][:-1]
            y_obs = np.atleast_2d(data[i][-1])
            y_fit = np.atleast_2d(self.current_function.func(x, series_params))
            y_res = np.atleast_2d(y_obs - y_fit)
            df_x = pd.DataFrame(x, index=axes) # no series name, get confusing
            df_y_obs = pd.DataFrame(y_obs, index=[' y-obs \n(' + series_name + ')' ])
            df_y_fit = pd.DataFrame(y_fit, index=[' y-fit\n(' + series_name + ')'])
            df_y_res = pd.DataFrame(y_res, index=[' y-res\n(' + series_name + ')'])
            df_data = pd.concat((df_data, df_x, df_y_obs, df_y_fit, df_y_res))
        return df_data.transpose()
    
    def get_xs_fitted_smooth_df(self):
        selected_series = self.get_selected_series_names()
        params = self.get_param_values_for_fitting(selected_series)
        data = self.get_data_for_fitting(selected_series)
        daxes = self.blits_data.get_axes_names()
        faxes = self.current_function.independents
        axes = np.array([f + "\n(" + a + ")"  for a, f in zip(daxes, faxes)])
        df_data = None
        for series_name, series_params, i in zip(selected_series, params, range(len(selected_series))):
            x0 = data[i][:-1, 0]
            x1 = data[i][:-1, -1]
            x = np.empty((len(axes), self.nfitted_points))
            for i, i0, i1 in zip(range(len(axes)), x0, x1):
                x[i] = np.linspace(i0, i1, self.nfitted_points, dtype=float)
            y_fit = np.atleast_2d(self.current_function.func(x, series_params))
            df_x = pd.DataFrame(x, index=axes) # no series name, get confusing
            df_y_fit = pd.DataFrame(y_fit, index=[' y-fit\n(' + series_name + ')'])
            df_data = pd.concat((df_data, df_x, df_y_fit))
        return df_data.transpose()
        
        
    def show_selected_data(self):
        self.tbl_fitted_data.clear()
        self.tbl_fitted_data.setColumnCount(0)
        self.tbl_fitted_data.setRowCount(0)
        all_data = self.get_xs_obs_fit_res_df()
        self.tbl_fitted_data.setRowCount(all_data.shape[0])
        self.tbl_fitted_data.setColumnCount(all_data.shape[1])
        self.tbl_fitted_data.setHorizontalHeaderLabels(all_data.columns.values)
        for i in range(self.tbl_fitted_data.rowCount()):
            for j in range(self.tbl_fitted_data.columnCount()):
                w = widgets.QTableWidgetItem()
                txt = ""
                if not np.isnan(all_data.iloc[i, j]):
                    txt = "{:8.3g}".format(all_data.iloc[i, j])
                w.setText(txt)
                self.tbl_fitted_data.setItem(i, j, w)
        self.tbl_fitted_data.resizeColumnsToContents()  
            
    def show_smooth_line(self):
        self.tbl_smooth_line.clear()
        self.tbl_smooth_line.setColumnCount(0)
        self.tbl_smooth_line.setRowCount(0)
        all_data = self.get_xs_fitted_smooth_df()
        self.tbl_smooth_line.setRowCount(all_data.shape[0])
        self.tbl_smooth_line.setColumnCount(all_data.shape[1])
        self.tbl_smooth_line.setHorizontalHeaderLabels(all_data.columns.values)
        for i in range(self.tbl_smooth_line.rowCount()):
            for j in range(self.tbl_smooth_line.columnCount()):
                w = widgets.QTableWidgetItem()
                txt = ""
                if not np.isnan(all_data.iloc[i, j]):
                    txt = "{:8.3g}".format(all_data.iloc[i, j])
                w.setText(txt)
                self.tbl_smooth_line.setItem(i, j, w)
        self.tbl_smooth_line.resizeColumnsToContents()  

    def show_fitted_params(self):
        self.tbl_fitted_params.clear()
        self.tbl_fitted_params.setColumnCount(0)
        self.tbl_fitted_params.setRowCount(0)
        
        pnames = self.pn_fit_spec.major_axis.values
        pheader = np.vstack((pnames, np.array(["Stderr\non " + pname for pname in pnames]))).transpose().ravel()
        pheader = np.hstack((pheader, np.array(["ftol"])))
        sheader = self.pn_fit_spec.minor_axis.values

        self.tbl_fitted_params.setColumnCount(len(pheader))
        self.tbl_fitted_params.setHorizontalHeaderLabels(pheader)
        self.tbl_fitted_params.setRowCount(len(sheader))
        self.tbl_fitted_params.setVerticalHeaderLabels(sheader)
        
        irow = -1
        for sname in self.pn_fit_spec.minor_axis.values:
            irow += 1
            icol = -1
            for pname in self.pn_fit_spec.major_axis.values:
                pval = self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES], pname, sname]
                perr = self.pn_fit_spec.loc[self.ps_types[self.PS_SIGMAS], pname, sname]
                spval, sperr = "", ""
                if not np.isnan(pval):
                    spval = '{:8.3g}'.format(pval)
                if not np.isnan(perr):
                    sperr = '{:8.3g}'.format(perr)
                icol += 1
                wi = widgets.QTableWidgetItem(spval)
                self.tbl_fitted_params.setItem(irow, icol, wi)
                icol += 1
                wi = widgets.QTableWidgetItem(sperr)
                self.tbl_fitted_params.setItem(irow, icol, wi)
            icol += 1
            ftol = self.df_series_spec.loc[sname, self.s_types[self.S_FTOL]]
            sftol = ""
            if not np.isnan(ftol):
                sftol = '{:8.3g}'.format(ftol)
            wi = widgets.QTableWidgetItem(sftol)
            self.tbl_fitted_params.setItem(irow, icol, wi)
            
        self.tbl_fitted_params.resizeColumnsToContents()  
        

    def rationalise_groups(self, parameter):
        if self.current_state in (self.ST_READY, ) and parameter != '':
            prow = self.pn_fit_spec.loc[self.ps_types[self.PS_GROUPS], parameter]
            x = prow.index
            df_wf = pd.DataFrame(np.zeros((len(x), len(x))), index=x, columns=x, dtype=bool) # set up the matrix
            for series, val in prow.iteritems():
                df_wf.loc[series, series] = True # make the matrix reflexive
                if series != val:
                    df_wf.loc[series, val] = True
                    df_wf.loc[val, series] = True # make the matrix symmetrical
            # make matrix transitive (Warshall-Floyd)
            for k in range(len(x)):
                for i in range(len(x)):
                    for j in range(len(x)):
                        df_wf.iloc[i, j] = df_wf.iloc[i, j] or (df_wf.iloc[i, k] == 1 and df_wf.iloc[k, j] == 1)
            # Find the equivalence classes for this parameter 
            seen = []
            sr_equiv_clss = pd.Series(index=x)          
            for series0, row in df_wf.iterrows():
                for series1, val in row.iteritems():
                    if val:
                        if series1 not in seen:
                            sr_equiv_clss.loc[series1] = series0
                            seen.append(series1)
            for series in x:
                self.pn_fit_spec.loc[self.ps_types[self.PS_GROUPS], parameter, series] = sr_equiv_clss.loc[series]
        pass
 
    def update_controls(self):
        """
        Enables and disables controls for each state
        """
        if self.current_state == self.ST_START:
            self.action_open.setEnabled(True)
            self.action_create.setEnabled(False)
            self.action_close.setEnabled(False)
            self.action_save.setEnabled(False)
            self.action_select_function.setEnabled(True)
            self.action_analyze.setEnabled(False)
            self.action_estimate.setEnabled(False)
            self.action_apply.setEnabled(False)
            self.btn_apply.setEnabled(False)
            self.btn_fit.setEnabled(False)
            self.btn_est.setEnabled(False)
            self.action_quit.setEnabled(True)
        elif self.current_state == self.ST_DATA_ONLY:
            self.action_open.setEnabled(False)
            self.action_create.setEnabled(False)
            self.action_close.setEnabled(True)
            self.action_save.setEnabled(True)
            self.action_select_function.setEnabled(True)
            self.action_analyze.setEnabled(False)
            self.action_estimate.setEnabled(False)
            self.action_apply.setEnabled(False)
            self.btn_apply.setEnabled(False)
            self.btn_fit.setEnabled(False)
            self.btn_est.setEnabled(False)
            self.action_quit.setEnabled(True) 
        elif self.current_state == self.FUNCTION_ONLY:
            self.action_open.setEnabled(True)
            self.action_create.setEnabled(True)
            self.action_close.setEnabled(False)
            self.action_save.setEnabled(False)
            self.action_select_function.setEnabled(True)
            self.action_analyze.setEnabled(False)
            self.action_estimate.setEnabled(False)
            self.action_apply.setEnabled(False)
            self.btn_apply.setEnabled(False)
            self.btn_fit.setEnabled(False)
            self.btn_est.setEnabled(False)
            self.action_quit.setEnabled(True) 
        elif self.current_state == self.ST_READY:
            self.action_open.setEnabled(False)
            self.action_create.setEnabled(False)
            self.action_close.setEnabled(True)
            self.action_save.setEnabled(True)
            self.action_select_function.setEnabled(True)
            self.action_analyze.setEnabled(True)
            self.action_estimate.setEnabled(True)
            self.action_apply.setEnabled(True)
            self.btn_apply.setEnabled(True)
            self.btn_fit.setEnabled(True)
            self.btn_est.setEnabled(True)
            self.action_quit.setEnabled(True) 
        else:
            print('Illegal state')
                                          
    def update_linkage_table(self):
        """
        Sets combo-boxes in linkage_combos to the current values in linkage_groups        
        """
        if self.current_state in (self.ST_READY, ):
            combos = self.pn_fit_spec.loc[self.ps_types[self.PS_COMBOS]]
            vals = self.pn_fit_spec.loc[self.ps_types[self.PS_GROUPS]]
            try:
                for i, row in vals.iterrows():
                    for j, val in row.iteritems():
                        box = combos.loc[i, j]
                        if box.currentText() != val:
                            box.currentIndexChanged.disconnect()
                            box.setCurrentText(val)
                            box.currentIndexChanged.connect(self.on_linkage_changed)
            except Exception as e:
                print(e)
                        
    def update_param_vals_table(self):
        """
        Sets text and checkstate of values table items to their corresponding 
        logical values in pn_fit_spec        
        """
        if self.current_state in (self.ST_READY, ):
            edts = self.pn_fit_spec.loc[self.ps_types[self.PS_LEDITS]]
            cbxs = self.pn_fit_spec.loc[self.ps_types[self.PS_FIX_CBOXES]]
            vals = self.pn_fit_spec.loc[self.ps_types[self.PS_VALUES]]
            chks = self.pn_fit_spec.loc[self.ps_types[self.PS_VALUE_FIXED]]
            try:
                for i, row in vals.iterrows():
                    for j, val in row.iteritems():
                        edt = edts.loc[i, j]
                        cbx = cbxs.loc[i, j]
                        checkstate = chks.loc[i, j]
                        if float(edt.text()) != val:
                            edt.textChanged.disconnect()
                            edt.setText('{:.3g}'.format(val))
                            edt.textChanged.connect(self.on_param_val_changed)
                        if  cbx.checkState() != checkstate:
                            cbx.stateChanged.disconnect()
                            cbx.setCheckState(qt.Qt.Unchecked)
                            if checkstate == qt.Qt.Checked:
                                cbx.setCheckState(qt.Qt.Checked)
                            cbx.stateChanged.connect(self.on_param_fix_changed)
            except Exception as e:
                print(e)
                
    def write_param_values_to_table(self, param_values):
        pass
        #self.parameters_model.change_content(param_values.transpose())
        #self.parameters_model.df_data[:] = param_values.transpose()
        #self.tbl_params.resizeColumnsToContents() # This redraws the table (necessary)

    ### Convenience functions and procedures
    
    def circle_icon(self, color):
        pix = gui.QPixmap(30,30)
        pix.fill(gui.QColor("transparent"))
        paint = gui.QPainter()
        paint.begin(pix)
        paint.setBrush(gui.QColor(color))
        paint.setPen(gui.QColor("transparent"))
        paint.drawEllipse(0,0,30,30)
        paint.end()
        icon = gui.QIcon(pix)
        return icon        

    def clearLayout(self, layout):
        while layout.count():
            child = layout.takeAt(0)
            if child.widget() is not None:
                child.widget().deleteLater()
            elif child.layout() is not None:
                self.clearLayout(child.layout())            
                                    
    def find_sender_index(self, dataframe):
        sender_i, sender_j = None, None
        for i, row, in dataframe.iterrows():
            for j, item in row.iteritems():
                if item is self.sender():
                    sender_i = i
                    sender_j = j
        return sender_i, sender_j
    
    def centred_tablewidget(self, qtwidget):
        wid = widgets.QWidget()
        hlo = widgets.QVBoxLayout()
        hlo.setContentsMargins(12, 0, 12, 0)
        hlo.setAlignment(qt.Qt.AlignCenter)
        wid.setLayout(hlo)
        hlo.addWidget(qtwidget)
        return wid            

    def checkable_edit_widget(self, checkbox, textbox):
        wid = widgets.QWidget()
        hlo = widgets.QHBoxLayout()
        hlo.setContentsMargins(12, 0, 12, 0)
        wid.setLayout(hlo)
        hlo.addWidget(textbox)
        hlo.addStretch()
        hlo.addWidget(checkbox)
        return wid
            
    def is_number(self, s):
        try:
            float(s)
            return True
        except ValueError:
            return False  
        
    def line_icon(self, color):
        pixmap = gui.QPixmap(50,10)
        pixmap.fill(gui.QColor(color))
        icon = gui.QIcon(pixmap)
        return icon