示例#1
0
class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.title = 'kfit'
        self.left = 400
        self.top = 150
        self.width = 1200
        self.height = 800
        self.file_name = ''
        self.xcol_idx = 0
        self.ycol_idx = 1
        self.ngau = 0
        self.nlor = 0
        self.nvoi = 0
        self.nlin = 1
        self.model = None
        self.result = None
        self.curves_df = None
        self.params_df = None
        self.edit_mode = False
        # empty Parameters to hold parameter guesses/constraints
        self.params = Parameters()
        self.guesses = {'value': {}, 'min': {}, 'max': {}}
        self.usr_vals = {'value': {}, 'min': {}, 'max': {}}
        self.usr_entry_widgets = {}
        self.cid = None

        # file import settings
        self.sep = ','
        self.header = 'infer'
        self.index_col = None
        self.skiprows = None
        self.dtype = None
        self.encoding = None

        # keyboard shortcuts
        self.fit_shortcut = QShortcut(QKeySequence('Ctrl+F'), self)
        self.fit_shortcut.activated.connect(self.fit)
        self.reset_shortcut = QShortcut(QKeySequence('Ctrl+R'), self)
        self.reset_shortcut.activated.connect(self.hard_reset)
        self.save_fit = QShortcut(QKeySequence('Ctrl+S'), self)
        self.save_fit.activated.connect(self.export_results)
        self.add_gau = QShortcut(QKeySequence('G'), self)
        self.add_gau.activated.connect(lambda: self.increment('gau', True))
        self.add_gau.activated.connect(self.init_param_widgets)
        self.sub_gau = QShortcut(QKeySequence('Shift+G'), self)
        self.sub_gau.activated.connect(lambda: self.increment('gau', False))
        self.sub_gau.activated.connect(self.init_param_widgets)
        self.add_lor = QShortcut(QKeySequence('L'), self)
        self.add_lor.activated.connect(lambda: self.increment('lor', True))
        self.add_lor.activated.connect(self.init_param_widgets)
        self.sub_lor = QShortcut(QKeySequence('Shift+L'), self)
        self.sub_lor.activated.connect(lambda: self.increment('lor', False))
        self.sub_lor.activated.connect(self.init_param_widgets)
        self.add_voi = QShortcut(QKeySequence('V'), self)
        self.add_voi.activated.connect(lambda: self.increment('voi', True))
        self.add_voi.activated.connect(self.init_param_widgets)
        self.sub_voi = QShortcut(QKeySequence('Shift+V'), self)
        self.sub_voi.activated.connect(lambda: self.increment('voi', False))
        self.sub_voi.activated.connect(self.init_param_widgets)
        self.add_lin = QShortcut(QKeySequence('N'), self)
        self.add_lin.activated.connect(lambda: self.increment('lin', True))
        self.add_lin.activated.connect(self.init_param_widgets)
        self.sub_lin = QShortcut(QKeySequence('Shift+N'), self)
        self.sub_lin.activated.connect(lambda: self.increment('lin', False))
        self.sub_lin.activated.connect(self.init_param_widgets)

        # temporary data
        x = np.linspace(0, 10, 500)
        y = models.gauss(x, 0.5, 4, 0.4) + \
            models.gauss(x, 0.8, 5, 0.2) + \
            models.gauss(x, 0.4, 6, 0.3) + 0.2

        # set data
        self.data = pd.DataFrame([x, y]).T
        self.data.columns = ['x', 'y']
        self.x = self.data['x']
        self.y = self.data['y']
        self.xmin = self.data['x'].min()
        self.xmax = self.data['x'].max()

        self.initUI()

    def initUI(self):
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.setWindowTitle(self.title)
        self.setWindowIcon(QIcon('../images/K.png'))

        # set up the status bar
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage('Welcome to kfit!', msg_length)
        self.status_bar.setStyleSheet('background-color: white')

        # Create the Main Widget and Layout
        self.main_layout = QVBoxLayout()
        self.main_widget = QSplitter()
        self.main_widget.setOrientation(Qt.Vertical)
        self.setCentralWidget(self.main_widget)

        # create "top bar" widget
        self.topbar_layout = QHBoxLayout()
        self.topbar_widget = QWidget()
        # fit button
        self.fit_button = QPushButton('Fit', self)
        self.fit_button.setMaximumWidth(100)
        self.fit_button.clicked.connect(self.fit)
        self.fit_button.installEventFilter(self)
        # import button
        self.import_button = QPushButton('Import', self)
        self.import_button.setMaximumWidth(100)
        self.import_button.clicked.connect(self.get_data)
        self.import_button.installEventFilter(self)
        # import settings button
        self.import_settings_button = QPushButton('', self)
        self.import_settings_button.setIcon(
            QIcon.fromTheme('stock_properties'))
        self.import_settings_button.setMaximumWidth(40)
        self.import_settings_button.clicked.connect(
            self.import_settings_dialog)
        self.import_settings_button.installEventFilter(self)
        # reset fit button
        self.reset_button = QPushButton('', self)
        self.reset_button.setIcon(QIcon.fromTheme('view-refresh'))
        self.reset_button.setMaximumWidth(40)
        self.reset_button.clicked.connect(self.hard_reset)
        self.reset_button.installEventFilter(self)
        # save results button
        self.save_button = QPushButton('', self)
        self.save_button.setIcon(QIcon.fromTheme('filesave'))
        self.save_button.setMaximumWidth(40)
        self.save_button.clicked.connect(self.export_results)
        self.save_button.installEventFilter(self)
        # progress bar
        self.progress_bar = QProgressBar()
        # self.progressBar.setMaximumWidth(150)
        self.progress_bar.hide()
        # get column header for x
        self.xlabel = QLabel(self)
        self.xlabel.setText('ColumnIndex(X):')
        self.xlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        # self.xlabel.setMaximumWidth(250)
        self.xline_entry = QLineEdit(self)
        self.xline_entry.setText('0')
        self.xline_entry.setAlignment(Qt.AlignCenter)
        # self.xline_entry.setMaximumWidth(50)
        self.xline_entry.returnPressed.connect(self.column_index_set)
        # get column header for y
        self.ylabel = QLabel(self)
        self.ylabel.setText('ColumnIndex(Y):')
        self.ylabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        # self.ylabel.setMaximumWidth(100)
        self.yline_entry = QLineEdit(self)
        self.yline_entry.setText('1')
        self.yline_entry.setAlignment(Qt.AlignCenter)
        # self.yline_entry.setMaximumWidth(50)
        self.yline_entry.returnPressed.connect(self.column_index_set)
        # add topbar widgets to layout
        self.topbar_layout.addSpacing(600)
        self.topbar_layout.addWidget(self.xlabel)
        self.topbar_layout.addWidget(self.xline_entry)
        self.topbar_layout.addWidget(self.ylabel)
        self.topbar_layout.addWidget(self.yline_entry)
        self.topbar_layout.addWidget(self.fit_button)
        self.topbar_layout.addWidget(self.import_button)
        self.topbar_layout.addWidget(self.import_settings_button)
        self.topbar_layout.addWidget(self.reset_button)
        self.topbar_layout.addWidget(self.save_button)
        self.topbar_layout.addWidget(self.progress_bar)
        self.topbar_layout.setAlignment(Qt.AlignRight)
        self.topbar_widget.setLayout(self.topbar_layout)
        self.topbar_widget.setMaximumHeight(75)

        # create tabs widget
        self.tabs = QTabWidget(self)
        self.tab1 = QWidget(self)
        self.tab2 = QTableView(self)
        self.tab3 = QWidget(self)
        self.tabs.addTab(self.tab1, 'Graph')
        self.tabs.addTab(self.tab2, 'Data')
        self.tabs.addTab(self.tab3, 'Output')
        self.tabs.setMinimumHeight(300)

        # create params widget
        self.params_widget = QSplitter()
        self.gau_layout = QVBoxLayout()
        self.gau_layout.setAlignment(Qt.AlignTop)
        self.gau_widget = QWidget()
        self.gau_widget.setLayout(self.gau_layout)
        self.gau_widget.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        self.gau_scroll = QScrollArea()
        self.gau_scroll.setWidget(self.gau_widget)
        self.gau_scroll.setWidgetResizable(True)
        self.lor_widget = QWidget()
        self.lor_layout = QVBoxLayout()
        self.lor_layout.setAlignment(Qt.AlignTop)
        self.lor_widget.setLayout(self.lor_layout)
        self.lor_widget.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        self.lor_scroll = QScrollArea()
        self.lor_scroll.setWidget(self.lor_widget)
        self.lor_scroll.setWidgetResizable(True)
        self.voi_widget = QWidget()
        self.voi_layout = QVBoxLayout()
        self.voi_layout.setAlignment(Qt.AlignTop)
        self.voi_widget.setLayout(self.voi_layout)
        self.voi_widget.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        self.voi_scroll = QScrollArea()
        self.voi_scroll.setWidget(self.voi_widget)
        self.voi_scroll.setWidgetResizable(True)
        self.lin_widget = QWidget()
        self.lin_layout = QVBoxLayout()
        self.lin_layout.setAlignment(Qt.AlignTop)
        self.lin_widget.setLayout(self.lin_layout)
        self.lin_widget.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        self.lin_scroll = QScrollArea()
        self.lin_scroll.setWidget(self.lin_widget)
        self.lin_scroll.setWidgetResizable(True)
        self.params_widget.addWidget(self.gau_scroll)
        self.params_widget.addWidget(self.lor_scroll)
        self.params_widget.addWidget(self.voi_scroll)
        self.params_widget.addWidget(self.lin_scroll)
        self.params_widget.setMinimumHeight(180)

        # add everything to main widget
        self.main_widget.addWidget(self.topbar_widget)
        self.main_widget.addWidget(self.tabs)
        self.main_widget.addWidget(self.params_widget)

        # Tab 1 - Graph / Model
        # Graph
        plt.style.use('fivethirtyeight')
        self.tab1.figure = Figure(figsize=(8, 6), dpi=60)
        self.tab1.canvas = FigureCanvas(self.tab1.figure)
        self.tab1.toolbar = NavigationToolbar(self.tab1.canvas, self)
        # tristate checkbox for edit mode
        self.emode_box = QCheckBox()
        self.emode_box.setTristate(True)
        self.emode_box.setIcon(QIcon.fromTheme('stock_edit'))
        self.emode_box.stateChanged.connect(self.toggle_edit_mode)
        self.emode_box.installEventFilter(self)
        # tweaking the toolbar layout
        self.tab1.toolbar.setIconSize(QSize(18, 18))
        spacer = QWidget()
        spacer.setFixedWidth(20)
        self.tab1.toolbar.addWidget(spacer)
        self.tab1.toolbar.addWidget(self.emode_box)
        self.tab1.toolbar.locLabel.setAlignment(Qt.AlignRight | Qt.AlignCenter)
        graph_layout = QVBoxLayout()
        graph_layout.addWidget(self.tab1.toolbar)
        graph_layout.addWidget(self.tab1.canvas)

        # The "Set Model" layout
        model_layout = QGridLayout()
        widget_setgau = QWidget()
        layout_setgau = QHBoxLayout()
        layout_setgau.setSizeConstraint(QLayout.SetFixedSize)
        widget_setgau.setLayout(layout_setgau)
        widget_setlor = QWidget()
        layout_setlor = QHBoxLayout()
        layout_setlor.setSizeConstraint(QLayout.SetFixedSize)
        widget_setlor.setLayout(layout_setlor)
        widget_setvoi = QWidget()
        layout_setvoi = QHBoxLayout()
        layout_setvoi.setSizeConstraint(QLayout.SetFixedSize)
        widget_setvoi.setLayout(layout_setvoi)
        widget_setlin = QWidget()
        layout_setlin = QHBoxLayout()
        layout_setlin.setSizeConstraint(QLayout.SetFixedSize)
        widget_setlin.setLayout(layout_setlin)
        model_layout.addWidget(widget_setgau, 0, 0)
        model_layout.addWidget(widget_setlor, 0, 1)
        model_layout.addWidget(widget_setvoi, 0, 2)
        model_layout.addWidget(widget_setlin, 0, 3)

        # specify number of gaussian curves
        gauss_label = QLabel(self)
        gauss_label.setText('Gaussians')
        gauss_label.setAlignment(Qt.AlignVCenter)
        gauss_button_add = QPushButton('', self)
        gauss_button_add.setIcon(QIcon.fromTheme('list-add'))
        gauss_button_add.clicked.connect(lambda: self.increment('gau', True))
        gauss_button_add.clicked.connect(self.init_param_widgets)
        gauss_button_sub = QPushButton('', self)
        gauss_button_sub.setIcon(QIcon.fromTheme('list-remove'))
        gauss_button_sub.clicked.connect(lambda: self.increment('gau', False))
        gauss_button_sub.clicked.connect(self.init_param_widgets)
        layout_setgau.addWidget(gauss_label)
        layout_setgau.addWidget(gauss_button_add)
        layout_setgau.addWidget(gauss_button_sub)
        # specify number of lorentzian curves
        lorentz_label = QLabel(self)
        lorentz_label.setText('Lorentzians')
        lorentz_label.setAlignment(Qt.AlignVCenter)
        lorentz_button_add = QPushButton('', self)
        lorentz_button_add.setIcon(QIcon.fromTheme('list-add'))
        lorentz_button_add.clicked.connect(lambda: self.increment('lor', True))
        lorentz_button_add.clicked.connect(self.init_param_widgets)
        lorentz_button_sub = QPushButton('', self)
        lorentz_button_sub.setIcon(QIcon.fromTheme('list-remove'))
        lorentz_button_sub.clicked.connect(
            lambda: self.increment('lor', False))
        lorentz_button_sub.clicked.connect(self.init_param_widgets)
        layout_setlor.addWidget(lorentz_label)
        layout_setlor.addWidget(lorentz_button_add)
        layout_setlor.addWidget(lorentz_button_sub)
        # specify number of voigt curves
        voigt_label = QLabel(self)
        voigt_label.setText('Pseudo-Voigts')
        voigt_label.setAlignment(Qt.AlignVCenter)
        voigt_button_add = QPushButton('', self)
        voigt_button_add.setIcon(QIcon.fromTheme('list-add'))
        voigt_button_add.clicked.connect(lambda: self.increment('voi', True))
        voigt_button_add.clicked.connect(self.init_param_widgets)
        voigt_button_sub = QPushButton('', self)
        voigt_button_sub.setIcon(QIcon.fromTheme('list-remove'))
        voigt_button_sub.clicked.connect(lambda: self.increment('voi', False))
        voigt_button_sub.clicked.connect(self.init_param_widgets)
        layout_setvoi.addWidget(voigt_label)
        layout_setvoi.addWidget(voigt_button_add)
        layout_setvoi.addWidget(voigt_button_sub)
        # specify number of lines
        line_label = QLabel(self)
        line_label.setText('Lines:')
        line_label.setAlignment(Qt.AlignVCenter)
        line_button_add = QPushButton('', self)
        line_button_add.setIcon(QIcon.fromTheme('list-add'))
        line_button_add.clicked.connect(lambda: self.increment('lin', True))
        line_button_add.clicked.connect(self.init_param_widgets)
        line_button_sub = QPushButton('', self)
        line_button_sub.setIcon(QIcon.fromTheme('list-remove'))
        line_button_sub.clicked.connect(lambda: self.increment('lin', False))
        line_button_sub.clicked.connect(self.init_param_widgets)
        layout_setlin.addWidget(line_label)
        layout_setlin.addWidget(line_button_add)
        layout_setlin.addWidget(line_button_sub)

        graph_layout.addLayout(model_layout)
        self.tab1.setLayout(graph_layout)
        self.plot()

        # Tab 2 - Data Table
        self.table_model = PandasModel(self.data)
        self.tab2.setModel(self.table_model)
        self.tab2.resizeColumnsToContents()

        # Tab 3 - Output
        self.tab3_widget = QPlainTextEdit()
        tab3_layout = QVBoxLayout()
        tab3_layout.addWidget(self.tab3_widget)
        self.tab3.setLayout(tab3_layout)

        self.init_param_widgets()
        self.show()

    def export_results(self):
        self.process_results()
        # open file dialog
        exp_file_name, _ = QFileDialog.getSaveFileName(
            self,
            'QFileDialog.getSaveFileName()',
            'fit_results.csv',
            'CSV files (*.csv)',
        )
        if exp_file_name:
            self.process_results()
            self.curves_df.to_csv(exp_file_name)
            # NOTE: if user chooses a file extension other than .csv, or does
            # not use a file extension, this should still work, but I haven't
            # tested too rigorously yet
            self.params_df.to_csv('{}.params.csv'.format(
                exp_file_name[:exp_file_name.find('.csv')]))
            self.status_bar.showMessage(
                'Exported fit results to: ' + exp_file_name, 2 * msg_length)
        else:
            self.status_bar.showMessage('Export canceled.', 2 * msg_length)
            return

    def process_results(self):
        if self.result is not None:
            self.params_df = pd.DataFrame.from_dict(self.result.best_values,
                                                    orient='index')
            self.params_df.index.name = 'parameter'
            self.params_df.columns = ['value']
            curves_dict = {
                'data': self.y,
                'total_fit': self.result.best_fit,
            }
            components = self.result.eval_components()
            for i, comp in enumerate(components):
                curves_dict[comp[:comp.find('_')]] = components[comp]
            self.curves_df = pd.DataFrame.from_dict(curves_dict)
            self.curves_df.index = self.x
            self.curves_df.index.name = self.data.columns[self.xcol_idx]
        else:
            self.status_bar.showMessage('No fit results to export!',
                                        msg_length)

    def eventFilter(self, object, event):
        if event.type() == QEvent.Enter:
            if object is self.reset_button:
                self.status_bar.showMessage("Reset fit")
            if object is self.import_settings_button:
                self.status_bar.showMessage("File import settings")
            if object is self.import_button:
                self.status_bar.showMessage("Import .csv file")
            if object is self.fit_button:
                self.status_bar.showMessage("Fit data")
            if object is self.emode_box:
                self.status_bar.showMessage("Toggle edit mode")
            if object is self.save_button:
                self.status_bar.showMessage("Export fit results")
            return True
        elif event.type() == QEvent.Leave:
            self.status_bar.showMessage(None)
        return False

    def init_model(self):
        # increment() ensures nlin >= 1
        self.model = models.line_mod(self.nlin)
        if self.ngau != 0:
            self.model += models.gauss_mod(self.ngau)
        if self.nlor != 0:
            self.model += models.lor_mod(self.nlor)
        if self.nvoi != 0:
            self.model += models.voigt_mod(self.nvoi)
        self.status_bar.showMessage(
            "Model updated: " +
            str([self.ngau, self.nlor, self.nvoi, self.nlin]), msg_length)

    def init_param_widgets(self):
        self.init_model()
        self.usr_entry_widgets = {'value': {}, 'min': {}, 'max': {}}
        labels = {}
        rnd = 3  # decimals to round to in placeholder text
        self.clear_fit_layouts()

        for param_name in self.model.param_names:
            # set param label text
            labels[param_name] = QLabel()
            labels[param_name].setText(param_name)

            # make qlineedit widgets
            for key in self.usr_entry_widgets:
                self.usr_entry_widgets[key][param_name] = QLineEdit()
                if param_name in self.usr_vals[key]:
                    self.usr_entry_widgets[key][param_name]\
                        .setPlaceholderText(
                            str(round(self.usr_vals[key][param_name], rnd))
                        )
                else:
                    self.usr_entry_widgets[key][param_name]\
                        .setPlaceholderText(key)
                # set up connections
                # connect() expects a callable func, hence the lambda
                self.usr_entry_widgets[key][param_name].returnPressed.connect(
                    lambda: self.update_usr_vals(self.usr_entry_widgets))

            # add widgets to respective layouts
            sublayout1 = QVBoxLayout()
            sublayout2 = QHBoxLayout()
            sublayout1.addWidget(labels[param_name])
            for key in self.usr_entry_widgets:
                sublayout2.addWidget(self.usr_entry_widgets[key][param_name])
            if param_name.find('gau') != -1:
                self.gau_layout.addLayout(sublayout1)
                self.gau_layout.addLayout(sublayout2)
            if param_name.find('lor') != -1:
                self.lor_layout.addLayout(sublayout1)
                self.lor_layout.addLayout(sublayout2)
            if param_name.find('voi') != -1:
                self.voi_layout.addLayout(sublayout1)
                self.voi_layout.addLayout(sublayout2)
            if param_name.find('lin') != -1:
                self.lin_layout.addLayout(sublayout1)
                self.lin_layout.addLayout(sublayout2)

        # Resize all of the LineEntry widgets
        for key in self.usr_entry_widgets:
            for param, widget in self.usr_entry_widgets[key].items():
                widget.setMaximumWidth(150)

        if self.result is not None:
            self.set_params()
            self.update_param_widgets()

    def update_usr_vals(self, entry):
        # get text input from each usr_entry_widget
        for val_type, param_dict in self.usr_entry_widgets.items():
            for param, param_widget in param_dict.items():
                try:
                    self.usr_vals[val_type][param] = \
                        float(param_widget.text())
                except Exception:
                    pass

    def update_param_widgets(self):
        rnd = 3
        # the 'value' placeholder text is the result for that param
        # taken from self.result
        # the 'min' and 'max' text is from either the self.guesses
        # or from self.usr_vals
        for param in self.params:
            if param in self.result.best_values:
                self.usr_entry_widgets['value'][param].setPlaceholderText(
                    str(round(self.result.best_values[param], rnd)))
                self.usr_entry_widgets['min'][param].setPlaceholderText(
                    str(round(self.params[param].min, rnd)))
                self.usr_entry_widgets['max'][param].setPlaceholderText(
                    str(round(self.params[param].max, rnd)))

    def guess_params(self):
        for comp in self.model.components:
            if comp.prefix.find('gau') != -1 or \
                    comp.prefix.find('lor') != -1 or \
                    comp.prefix.find('voi') != -1:

                # need to define explicitly to make proper guesses
                c = comp.prefix + 'center'
                a = comp.prefix + 'amplitude'
                s = comp.prefix + 'sigma'
                f = comp.prefix + 'fraction'

                self.guesses['value'][c] = \
                    self.data.iloc[:, self.xcol_idx].mean()
                self.guesses['value'][a] = \
                    self.data.iloc[:, self.ycol_idx].mean()
                self.guesses['value'][s] = \
                    self.data.iloc[:, self.xcol_idx].std()
                self.guesses['min'][c] = None
                self.guesses['min'][a] = 0
                self.guesses['min'][s] = 0
                self.guesses['max'][c] = None
                self.guesses['max'][a] = None
                self.guesses['max'][s] = None

                if comp.prefix.find('voi') != -1:
                    self.guesses['value'][f] = 0.5
                    self.guesses['min'][f] = 0
                    self.guesses['max'][f] = 1
            else:
                slope = comp.prefix + 'slope'
                intc = comp.prefix + 'intercept'
                for p in [slope, intc]:
                    self.guesses['value'][p] = \
                        self.data.iloc[:, self.ycol_idx].mean()
                    self.guesses['min'][p] = None
                    self.guesses['max'][p] = None

    def set_params(self):
        self.params = Parameters()
        self.guess_params()
        self.update_usr_vals(self.usr_entry_widgets)
        vals = {}

        # fill params with any user-entered values
        # fill in blanks with guesses
        for param_name in self.model.param_names:
            for val_type in ['value', 'min', 'max']:
                if param_name in self.usr_vals[val_type]:
                    vals[val_type] = self.usr_vals[val_type][param_name]
                    # print('param: ' + param_name + ', type: ' +\
                    #         val_type + ', set_by: user')
                else:
                    vals[val_type] = self.guesses[val_type][param_name]
                    # print('param: ' + param_name + ', type: ' +\
                    #         val_type + ', set_by: guess')
            self.params.add(name=param_name,
                            value=vals['value'],
                            vary=True,
                            min=vals['min'],
                            max=vals['max'])

    def set_xy_range(self):
        self.x = self.data.iloc[:, self.xcol_idx]
        self.y = self.data.iloc[:, self.ycol_idx]
        self.xmin, self.xmax = self.ax.get_xlim()
        range_bool = (self.x >= self.xmin) & (self.x <= self.xmax)
        self.x = self.x[range_bool].values
        self.y = self.y[range_bool].values

    def reset_xy_range(self):
        self.xmin = np.min(self.x)
        self.xmax = np.max(self.x)

    def fit(self):
        self.emode_box.setCheckState(0)
        self.toggle_edit_mode()
        self.set_xy_range()
        self.set_params()
        self.result = self.model.fit(data=self.y,
                                     params=self.params,
                                     x=self.x,
                                     method='least_squares')
        self.tab3_widget.clear()
        self.tab3_widget.insertPlainText(self.result.fit_report())
        self.plot()
        # overwrite widgets to clear input (not ideal method..)
        self.init_param_widgets()
        # update widgets with new placeholder text
        self.update_param_widgets()

    def column_index_set(self):
        # make sure user enters index that can be converted to int
        try:
            idx_x = int(self.xline_entry.text())
        except ValueError:
            self.status_bar.showMessage(idx_type_error_msg, msg_length)
            self.xline_entry.setText(None)
            return
        try:
            idx_y = int(self.yline_entry.text())
        except ValueError:
            self.status_bar.showMessage(idx_type_error_msg, msg_length)
            self.yline_entry.setText(None)
            return
        self.xcol_idx = idx_x
        self.ycol_idx = idx_y
        self.result = None
        # make sure user enters an index that's in the data range
        try:
            self.x = self.data.iloc[:, self.xcol_idx]
        except IndexError:
            self.status_bar.showMessage(idx_range_error_msg, msg_length)
            self.xline_entry.setText(None)
            return
        try:
            self.y = self.data.iloc[:, self.ycol_idx]
        except IndexError:
            self.status_bar.showMessage(idx_range_error_msg, msg_length)
            self.yline_entry.setText(None)
            return
        self.xmin = np.min(self.x)
        self.xmax = np.max(self.x)
        self.plot()
        self.status_bar.showMessage(
            'ColumnIndex(X) = ' + str(idx_x) + ', ' + 'ColumnIndex(Y) = ' +
            str(idx_y), msg_length)

    def toggle_edit_mode(self):
        # first toggle off the zoom or pan button
        # so they don't interfere with edit_mode cursor style
        if self.tab1.toolbar._active == 'ZOOM':
            self.tab1.toolbar.zoom()
        if self.tab1.toolbar._active == 'PAN':
            self.tab1.toolbar.pan()
        states = {
            0: 'Edit mode off',
            1: 'Edit mode on | copy x-value',
            2: 'Edit mode on | copy y-value',
        }
        self.status_bar.showMessage(states[self.emode_box.checkState()],
                                    msg_length)
        if self.emode_box.checkState() == 0:
            self.mpl_cursor = None
            self.tab1.canvas.mpl_disconnect(self.cid)
        if self.emode_box.checkState() == 1:
            self.mpl_cursor = Cursor(self.ax, lw=1, c='red', linestyle='--')
            self.cid = self.tab1.canvas.mpl_connect('button_press_event',
                                                    self.get_coord_click)
        if self.emode_box.checkState() == 2:
            self.cid = self.tab1.canvas.mpl_connect('button_press_event',
                                                    self.get_coord_click)

    def get_coord_click(self, event):
        self.x_edit, self.y_edit = round(event.xdata, 3), round(event.ydata, 3)
        if self.emode_box.checkState() == 1:
            pyperclip.copy(self.x_edit)
            self.status_bar.showMessage(
                'Copied X=' + str(self.x_edit) + ' to clipboard!', msg_length)
        if self.emode_box.checkState() == 2:
            pyperclip.copy(self.y_edit)
            self.status_bar.showMessage(
                'Copied Y=' + str(self.y_edit) + ' to clipboard!', msg_length)

    def close_app(self):
        sys.exit()

    def get_data(self):
        self.emode_box.setCheckState(0)
        self.toggle_edit_mode()
        # reset column indices
        self.xcol_idx = 0
        self.ycol_idx = 1
        # open file dialog
        self.file_name, _ = QFileDialog.getOpenFileName(
            self, 'Open File', '', 'CSV files (*.csv);; All Files (*)')
        if self.file_name:
            # this message isn't showing up...
            # TODO: needs to be threaded
            self.status_bar.showMessage('Importing: ' + self.file_name,
                                        msg_length)
            try:
                df = tools.to_df(self.file_name,
                                 sep=self.sep,
                                 header=self.header,
                                 index_col=self.index_col,
                                 skiprows=self.skiprows,
                                 dtype=self.dtype,
                                 encoding=self.encoding)
                df.iloc[:, self.xcol_idx]
                df.iloc[:, self.ycol_idx]
            except Exception:
                self.status_bar.showMessage(file_import_error_msg,
                                            2 * msg_length)
                return
        else:
            self.status_bar.showMessage('Import canceled.', msg_length)
            return

        self.data = df
        self.table_model = PandasModel(self.data)
        self.tab2.setModel(self.table_model)
        self.tab2.resizeColumnsToContents()
        # clear any previous fit result
        self.result = None
        # reset x, y, and xlim
        self.x = self.data.iloc[:, self.xcol_idx].values
        self.y = self.data.iloc[:, self.ycol_idx].values
        self.xmin = self.data.iloc[:, self.xcol_idx].min()
        self.xmax = self.data.iloc[:, self.xcol_idx].max()
        self.plot()
        self.status_bar.showMessage('Import finished.', msg_length)

    def import_settings_dialog(self):
        self.dialog_window = QDialog()
        self.dialog_window.setWindowTitle('File Import Settings')
        toplevel = QVBoxLayout()
        dialog_layout = QHBoxLayout()
        label_layout = QVBoxLayout()
        entry_layout = QVBoxLayout()
        button_layout = QVBoxLayout()
        label1 = QLabel(self.dialog_window)
        label1.setText('sep')
        label2 = QLabel(self.dialog_window)
        label2.setText('header')
        label3 = QLabel(self.dialog_window)
        label3.setText('skiprows')
        label4 = QLabel(self.dialog_window)
        label4.setText('dtype')
        label5 = QLabel(self.dialog_window)
        label5.setText('encoding')
        for lbl in [label1, label2, label3, label4, label5]:
            label_layout.addWidget(lbl)
        self.sep_edit = QLineEdit(self.dialog_window)
        self.head_edit = QLineEdit(self.dialog_window)
        self.skipr_edit = QLineEdit(self.dialog_window)
        self.dtype_edit = QLineEdit(self.dialog_window)
        self.enc_edit = QLineEdit(self.dialog_window)
        self.sep_edit.setText(self.sep)
        self.head_edit.setText(self.header)
        # if value is None, show text as 'None'
        if self.skiprows is not None:
            self.skipr_edit.setText(self.skiprows)
        else:
            self.skipr_edit.setText('None')
        if self.dtype is not None:
            self.dtype_edit.setText(self.dtype)
        else:
            self.dtype_edit.setText('None')
        if self.encoding is not None:
            self.enc_edit.setText(self.encoding)
        else:
            self.enc_edit.setText('None')

        # add widgets to layout
        for ewidget in [
                self.sep_edit, self.head_edit, self.skipr_edit,
                self.dtype_edit, self.enc_edit
        ]:
            ewidget.setAlignment(Qt.AlignCenter)
            entry_layout.addWidget(ewidget)

        button1 = QPushButton('Set', self.dialog_window)
        button2 = QPushButton('Set', self.dialog_window)
        button3 = QPushButton('Set', self.dialog_window)
        button4 = QPushButton('Set', self.dialog_window)
        button5 = QPushButton('Set', self.dialog_window)
        for btn in [button1, button2, button3, button4, button5]:
            btn.clicked.connect(self.set_import_settings)
            button_layout.addWidget(btn)

        reflabel = QLabel(self.dialog_window)
        reflabel.setText(
            "for help, refer to <a href='https://pandas.pydata.org/" +
            "pandas-docs/stable/reference/api/" +
            "pandas.read_csv.html'>pandas.read_csv()</a>")
        reflabel.setOpenExternalLinks(True)
        reflabel.setAlignment(Qt.AlignCenter)
        for lo in [label_layout, entry_layout, button_layout]:
            dialog_layout.addLayout(lo)
        toplevel.addLayout(dialog_layout)
        toplevel.addSpacing(25)
        toplevel.addWidget(reflabel)
        self.dialog_window.setLayout(toplevel)
        self.dialog_window.setWindowModality(Qt.ApplicationModal)
        self.dialog_window.exec_()

    def set_import_settings(self):
        self.sep = self.sep_edit.text()
        self.header = self.head_edit.text()
        # convert 'None' entries to None
        if self.skipr_edit.text() == 'None':
            self.skiprows = None
        else:
            self.skiprows = self.skipr_edit.text()
        if self.dtype_edit.text() == 'None':
            self.dtype = None
        else:
            self.dtype = self.dtype_edit.text()
        if self.enc_edit.text() == 'None':
            self.encoding = None
        else:
            self.encoding = self.enc_edit.text()

    def plot(self):
        self.tab1.figure.clear()
        self.ax = self.tab1.figure.add_subplot(111, label=self.file_name)
        self.ax.scatter(self.x,
                        self.y,
                        s=100,
                        c='None',
                        edgecolors='black',
                        linewidth=1,
                        label='data')
        if self.result is not None:
            yfit = self.result.best_fit
            self.ax.plot(self.x, yfit, c='r', linewidth=2.5)
            cmap = cm.get_cmap('gnuplot')
            components = self.result.eval_components()
            for i, comp in enumerate(components):
                self.ax.plot(self.x,
                             components[comp],
                             linewidth=2.5,
                             linestyle='--',
                             c=cmap(i / len(components)),
                             label=comp[:comp.find('_')])
        self.ax.set_xlabel(self.data.columns[self.xcol_idx], labelpad=15)
        self.ax.set_ylabel(self.data.columns[self.ycol_idx], labelpad=15)
        self.ax.set_xlim([self.xmin, self.xmax])
        self.ax.legend(loc='upper right')
        self.tab1.figure.subplots_adjust(bottom=0.15, left=0.06, right=0.94)
        self.tab1.canvas.draw()

    def clear_layout(self, layout):
        if layout:
            while layout.count():
                item = layout.takeAt(0)
                widget = item.widget()
                if widget:
                    widget.deleteLater()
                else:
                    self.clear_layout(item.layout())
                layout.removeItem(item)

    def clear_fit_layouts(self):
        for layout in [
                self.gau_layout, self.lor_layout, self.voi_layout,
                self.lin_layout
        ]:
            self.clear_layout(layout)

    def hard_reset(self):
        self.clear_fit_layouts()
        self.ngau = 0
        self.nlor = 0
        self.nvoi = 0
        self.nlin = 1
        self.init_model()
        self.params = Parameters()
        self.result = None
        self.params_df = None
        self.curves_df = None
        self.guesses = {'value': {}, 'min': {}, 'max': {}}
        self.usr_vals = {'value': {}, 'min': {}, 'max': {}}
        self.init_param_widgets()
        self.plot()

    def increment(self, val, add):
        if add:
            if val == 'gau':
                self.ngau += 1
            if val == 'lor':
                self.nlor += 1
            if val == 'voi':
                self.nvoi += 1
            if val == 'lin':
                self.nlin += 1
        if not add:
            if val == 'gau':
                self.ngau -= 1
            if val == 'lor':
                self.nlor -= 1
            if val == 'voi':
                self.nvoi -= 1
            if val == 'lin':
                self.nlin -= 1

        # make sure value doesn't go below zero
        if self.ngau < 0:
            self.ngau = 0
        if self.nlor < 0:
            self.nlor = 0
        if self.nvoi < 0:
            self.nvoi = 0
        if self.nlin < 1:
            self.nlin = 1
示例#2
0
class PreviewWidgetStyle(QGroupBox):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setTitle(self.tr("Preview"))
        self.setMaximumHeight(220)
        self.setObjectName("groupBox")

        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")

        self.tabWidget = QTabWidget(self)
        self.tabWidget.setObjectName("tabWidgetPreview")

        self.tab = QWidget()
        self.tab.setObjectName("tab")

        self.horizontalLayout = QHBoxLayout(self.tab)
        self.horizontalLayout.setObjectName("horizontalLayout")

        self.groupBox = QGroupBox(self.tab)
        self.groupBox.setTitle(self.tr("Group Box"))
        self.groupBox.setObjectName("groupBox")

        self.verticalLayout_2 = QVBoxLayout(self.groupBox)
        self.verticalLayout_2.setObjectName("verticalLayout_2")

        self.radioButton = QRadioButton(self.groupBox)
        self.radioButton.setText(self.tr("Radio Button"))
        self.radioButton.setChecked(True)
        self.radioButton.setObjectName("radioButton")
        self.verticalLayout_2.addWidget(self.radioButton)

        self.radioButton_2 = QRadioButton(self.groupBox)
        self.radioButton_2.setText(self.tr("Radio Button"))
        self.radioButton_2.setObjectName("radioButton_2")
        self.verticalLayout_2.addWidget(self.radioButton_2)

        self.line = QFrame(self.groupBox)
        self.line.setFrameShape(QFrame.HLine)
        self.line.setFrameShadow(QFrame.Sunken)
        self.line.setObjectName("line")
        self.verticalLayout_2.addWidget(self.line)

        self.checkBox = QCheckBox(self.groupBox)
        self.checkBox.setText(self.tr("Check Box"))
        self.checkBox.setChecked(True)
        self.checkBox.setObjectName("checkBox")
        self.verticalLayout_2.addWidget(self.checkBox)

        self.horizontalLayout.addWidget(self.groupBox)

        self.verticalLayout_3 = QVBoxLayout()
        self.verticalLayout_3.setObjectName("verticalLayout_3")

        self.progressBar = QProgressBar(self.tab)
        self.progressBar.setProperty("value", 75)
        self.progressBar.setObjectName("progressBar")
        self.verticalLayout_3.addWidget(self.progressBar)

        self.horizontalSlider = QSlider(self.tab)
        self.horizontalSlider.setProperty("value", 45)
        self.horizontalSlider.setSliderPosition(45)
        self.horizontalSlider.setOrientation(Qt.Horizontal)
        self.horizontalSlider.setObjectName("horizontalSlider")
        self.verticalLayout_3.addWidget(self.horizontalSlider)

        self.horizontalLayout_2 = QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")

        self.spinBox = QSpinBox(self.tab)
        self.spinBox.setObjectName("spinBox")
        self.horizontalLayout_2.addWidget(self.spinBox)

        self.pushButton = QPushButton(self.tab)
        self.pushButton.setText(self.tr("Button"))
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout_2.addWidget(self.pushButton)

        self.verticalLayout_3.addLayout(self.horizontalLayout_2)

        self.comboBox = QComboBox(self.tab)
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem(self.tr("Combo Box"))
        self.verticalLayout_3.addWidget(self.comboBox)

        self.horizontalLayout.addLayout(self.verticalLayout_3)

        self.verticalScrollBar = QScrollBar(self.tab)
        self.verticalScrollBar.setPageStep(50)
        self.verticalScrollBar.setOrientation(Qt.Vertical)
        self.verticalScrollBar.setObjectName("verticalScrollBar")
        self.horizontalLayout.addWidget(self.verticalScrollBar)
        self.tabWidget.addTab(self.tab, self.tr("Tab 1"))

        self.tab_2 = QWidget()
        self.tab_2.setObjectName("tab_2")
        self.tabWidget.addTab(self.tab_2, self.tr("Tab 2"))

        self.verticalLayout.addWidget(self.tabWidget)

        self.pushButton.installEventFilter(self)
        self.pushButton.setFocusPolicy(Qt.NoFocus)
        self.radioButton.installEventFilter(self)
        self.radioButton.setFocusPolicy(Qt.NoFocus)
        self.radioButton_2.installEventFilter(self)
        self.radioButton_2.setFocusPolicy(Qt.NoFocus)
        self.checkBox.installEventFilter(self)
        self.checkBox.setFocusPolicy(Qt.NoFocus)
        self.comboBox.installEventFilter(self)
        self.comboBox.setFocusPolicy(Qt.NoFocus)
        self.spinBox.installEventFilter(self)
        self.spinBox.setFocusPolicy(Qt.NoFocus)
        self.horizontalSlider.installEventFilter(self)
        self.horizontalSlider.setFocusPolicy(Qt.NoFocus)
        self.verticalScrollBar.installEventFilter(self)
        self.verticalScrollBar.setFocusPolicy(Qt.NoFocus)
        self.tab.installEventFilter(self)
        self.tab.setFocusPolicy(Qt.NoFocus)
        self.tab_2.installEventFilter(self)
        self.tab_2.setFocusPolicy(Qt.NoFocus)
        self.tabWidget.installEventFilter(self)
        self.tabWidget.setFocusPolicy(Qt.NoFocus)

        self.tabWidget.currentChanged.connect(self.noClick)

    def noClick(self, x):
        self.tabWidget.setCurrentIndex(0)

    def eventFilter(self, obj, event):
        if self.pushButton:
            if event.type() == QEvent.MouseButtonRelease:
                return True
            elif event.type() == QEvent.MouseButtonPress:
                return True
            elif event.type() == QEvent.MouseButtonDblClick:
                return True
            else:
                return False
        else:
            super().eventFilter(obj, event)
示例#3
0
class PreviewWidgetStyle(QGroupBox):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setTitle(self.tr("Preview"))
        self.setMaximumHeight(220)
        self.setObjectName("groupBox")

        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setObjectName("verticalLayout")

        self.tabWidget = QTabWidget(self)
        self.tabWidget.setObjectName("tabWidgetPreview")

        self.tab = QWidget()
        self.tab.setObjectName("tab")

        self.horizontalLayout = QHBoxLayout(self.tab)
        self.horizontalLayout.setObjectName("horizontalLayout")

        self.groupBox = QGroupBox(self.tab)
        self.groupBox.setTitle(self.tr("Group Box"))
        self.groupBox.setObjectName("groupBox")


        self.verticalLayout_2 = QVBoxLayout(self.groupBox)
        self.verticalLayout_2.setObjectName("verticalLayout_2")

        self.radioButton = QRadioButton(self.groupBox)
        self.radioButton.setText(self.tr("Radio Button"))
        self.radioButton.setChecked(True)
        self.radioButton.setObjectName("radioButton")
        self.verticalLayout_2.addWidget(self.radioButton)

        self.radioButton_2 = QRadioButton(self.groupBox)
        self.radioButton_2.setText(self.tr("Radio Button"))
        self.radioButton_2.setObjectName("radioButton_2")
        self.verticalLayout_2.addWidget(self.radioButton_2)

        self.line = QFrame(self.groupBox)
        self.line.setFrameShape(QFrame.HLine)
        self.line.setFrameShadow(QFrame.Sunken)
        self.line.setObjectName("line")
        self.verticalLayout_2.addWidget(self.line)

        self.checkBox = QCheckBox(self.groupBox)
        self.checkBox.setText(self.tr("Check Box"))
        self.checkBox.setChecked(True)
        self.checkBox.setObjectName("checkBox")
        self.verticalLayout_2.addWidget(self.checkBox)

        self.horizontalLayout.addWidget(self.groupBox)


        self.verticalLayout_3 = QVBoxLayout()
        self.verticalLayout_3.setObjectName("verticalLayout_3")

        self.progressBar = QProgressBar(self.tab)
        self.progressBar.setProperty("value", 75)
        self.progressBar.setObjectName("progressBar")
        self.verticalLayout_3.addWidget(self.progressBar)

        self.horizontalSlider = QSlider(self.tab)
        self.horizontalSlider.setProperty("value", 45)
        self.horizontalSlider.setSliderPosition(45)
        self.horizontalSlider.setOrientation(Qt.Horizontal)
        self.horizontalSlider.setObjectName("horizontalSlider")
        self.verticalLayout_3.addWidget(self.horizontalSlider)

        self.horizontalLayout_2 = QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")

        self.spinBox = QSpinBox(self.tab)
        self.spinBox.setObjectName("spinBox")
        self.horizontalLayout_2.addWidget(self.spinBox)

        self.pushButton = QPushButton(self.tab)
        self.pushButton.setText(self.tr("Button"))
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout_2.addWidget(self.pushButton)

        self.verticalLayout_3.addLayout(self.horizontalLayout_2)

        self.comboBox = QComboBox(self.tab)
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem(self.tr("Combo Box"))
        self.verticalLayout_3.addWidget(self.comboBox)

        self.horizontalLayout.addLayout(self.verticalLayout_3)

        self.verticalScrollBar = QScrollBar(self.tab)
        self.verticalScrollBar.setPageStep(50)
        self.verticalScrollBar.setOrientation(Qt.Vertical)
        self.verticalScrollBar.setObjectName("verticalScrollBar")
        self.horizontalLayout.addWidget(self.verticalScrollBar)
        self.tabWidget.addTab(self.tab, self.tr("Tab 1"))


        self.tab_2 = QWidget()
        self.tab_2.setObjectName("tab_2")
        self.tabWidget.addTab(self.tab_2, self.tr("Tab 2"))

        self.verticalLayout.addWidget(self.tabWidget)

        self.pushButton.installEventFilter(self)
        self.pushButton.setFocusPolicy(Qt.NoFocus)
        self.radioButton.installEventFilter(self)
        self.radioButton.setFocusPolicy(Qt.NoFocus)
        self.radioButton_2.installEventFilter(self)
        self.radioButton_2.setFocusPolicy(Qt.NoFocus)
        self.checkBox.installEventFilter(self)
        self.checkBox.setFocusPolicy(Qt.NoFocus)
        self.comboBox.installEventFilter(self)
        self.comboBox.setFocusPolicy(Qt.NoFocus)
        self.spinBox.installEventFilter(self)
        self.spinBox.setFocusPolicy(Qt.NoFocus)
        self.horizontalSlider.installEventFilter(self)
        self.horizontalSlider.setFocusPolicy(Qt.NoFocus)
        self.verticalScrollBar.installEventFilter(self)
        self.verticalScrollBar.setFocusPolicy(Qt.NoFocus)
        self.tab.installEventFilter(self)
        self.tab.setFocusPolicy(Qt.NoFocus)
        self.tab_2.installEventFilter(self)
        self.tab_2.setFocusPolicy(Qt.NoFocus)
        self.tabWidget.installEventFilter(self)
        self.tabWidget.setFocusPolicy(Qt.NoFocus)

        self.tabWidget.currentChanged.connect(self.noClick)

    def noClick(self, x):
        self.tabWidget.setCurrentIndex(0)

    def eventFilter(self, obj, event):
        if self.pushButton:
            if event.type() == QEvent.MouseButtonRelease:
                return True
            elif event.type() == QEvent.MouseButtonPress:
                return True
            elif event.type() == QEvent.MouseButtonDblClick:
                return True
            else:
                return False
        else:
            super().eventFilter(obj, event)