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_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 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 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 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 __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()
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