crikit.ui.widget_ALS — CRIKit2 0.4.3 documentation

# Source code for crikit.ui.widget_ALS

    """
    Widget for PlotEffect that adjusts the parameters appropriate for
    asymmetric least squares (ALS)

    Created on Thu Dec 22 01:16:01 2016

    @author: chc
    """
    import numpy as _np

    from PyQt5.QtWidgets import QTableWidgetItem as _QTableWidgetItem

    from crikit.ui.dialog_AbstractPlotEffect import (AbstractPlotEffectPlugin
                                                     as _AbstractPlotEffectPlugin)

    from crikit.ui.qt_PlotEffect_ALS3 import Ui_Form as _Ui_Form

    from crikit.ui.widget_scientificspin import (ScientificDoubleSpinBox as
                                                 _SciSpin)

    from crikit.preprocess.algorithms.als import AlsCvxopt as _Als
    from crikit.utils.general import find_nearest as _find_nearest

    class widgetALS(_AbstractPlotEffectPlugin):
        """
        Widget for PlotEffect that adjusts the parameters appropriate for
        asymmetric least squares (ALS)

        Parameters
        ----------
        wavenumber_increasing : bool, optional (default, True)
            If wavenumber is increasing left-to-right. Opposite value as the KK
            conjugate value

        smoothness_param : float, optional (default, 1e3)
            Smoothness parameter

        asym_param : float, optional (default, 1e-4)
            Assymetry parameter

        redux : int, optional (default, 1)
            Reduction parameter to sub-sample input signal

        order : int, optional (default, 2)
            Derivative regularization term. Order=2 for Whittaker-smoother

        fix_end_points : bool, optional (default, False)
            Weight the baseline endpoints to approach equally the end-points
            of the data.

        max_iter : int, optional (default, 100)
            Maximum number of least-squares iterations to perform

        min_diff : float, optional (default, 1e-5)
            Break iterative calculations if difference is less than min_diff

        parent: QObject
            Parent

        Methods
        -------
        fcn : Perform ALS detrending

        Signals:
            changed : a value in the UI has changed
        """

        # Parameter dict that will be returned from PlotEffect
        # Will be updated later in program to contain all parameters
        # to pass to underlying algorithm
        parameters = {'name' : 'ALS',
                      'long_name' : 'Asymmetric least squares'}

        # Labeling options for original data plot
        labels_orig = {
                       'x_label' : 'Wavenumber (cm$^{-1}$)',
                       'y_label' : 'Input Int (au)',
                       'title' : 'Original'
                       }

        # Labeling options for affected data plot
        labels_affected = {
                           'x_label' : labels_orig['x_label'],
                           'y_label' : 'Output Int (au)',
                           'title' : 'Detrended'
                          }

        def __init__(self, x=None, wavenumber_increasing=True,
                     rng=None, smoothness_param=1, asym_param=1e-3, redux=10,
                     order=2, fix_end_points=True, fix_const=1, fix_rng=None,
                     max_iter=100, min_diff=1e-6, verbose=False, sub_asym_list=None,
                     sub_w_list=None, parent = None):

            super(widgetALS, self).__init__(parent) ### EDIT ###

            self._x = x
            self.rng = rng

            self.ui = _Ui_Form()
            self.ui.setupUi(self)

            self.sub_asym_list = sub_asym_list
            self.sub_w_list = sub_w_list

            # ! Change this into its own function after
            # ! the asym table widget is all setup
            if not self.sub_asym_list:
                self.parameters['asym_param'] = asym_param
            else:
                self.parameters['asym_param'] = 0*self.x + asym_param

            # Update parameter dict
            self.parameters['smoothness_param'] = smoothness_param
            self.parameters['rng'] = rng
            self.parameters['redux'] = redux
            self.parameters['order'] = order
            self.parameters['fix_end_points'] = fix_end_points
            self.parameters['fix_rng'] = fix_rng
            self.parameters['fix_const'] = fix_const
            self.parameters['max_iter'] = max_iter
            self.parameters['min_diff'] = min_diff
            self.parameters['verbose'] = verbose
            self.parameters['wavenumber_increasing'] = wavenumber_increasing

            self.setup_asym()  # Setup controls for asymmetry parameter
            self.setup_smoothness()  # Setup controls for smoothness parameter

            # Redux factor
            self.ui.spinBoxRedux.setValue(self.parameters['redux'])

            # Fixed ends
            self.ui.checkBox.setChecked(self.parameters['fix_end_points'])

            # Max iterations
            self.ui.spinBoxMaxIter.setValue(self.parameters['max_iter'])

            # Min Difference
            self.ui.spinBoxMinDiff = _SciSpin()
            self.ui.verticalLayoutMiNMaxIterDiff.insertWidget(3, self.ui.spinBoxMinDiff)
            self.ui.spinBoxMinDiff.setValue(self.parameters['min_diff'])

            self.ui.checkBoxWNIncreasing.setChecked(self.parameters['wavenumber_increasing'])

            # SIGNALS & SLOTS
            self.ui.spinBoxP.editingFinished.connect(self.spinBoxChanged)
            self.ui.spinBoxLambda.editingFinished.connect(self.spinBoxChanged)
            self.ui.spinBoxRedux.editingFinished.connect(self.spinBoxChanged)

            self.ui.spinBoxMaxIter.editingFinished.connect(self.spinBoxChanged)
            self.ui.spinBoxMinDiff.editingFinished.connect(self.spinBoxChanged)

            self.ui.checkBox.clicked.connect(self.selectFixedEnds)

            self.ui.spinBoxAsymSubSections.valueChanged.connect(self.asym_sub_val_change)
            self.ui.spinBoxWSubSections.valueChanged.connect(self.weight_sub_val_change)

            self.ui.checkBoxWNIncreasing.clicked.connect(self.selectWNIncrease)

            self.ui.spinBoxWeight = _SciSpin()
            self.ui.spinBoxWeight.setMinimum(0)
            self.ui.spinBoxWeight.setMaximum(1e10)
            self.ui.spinBoxWeight.setValue(1)
            self.ui.spinBoxWeight.editingFinished.connect(self.weightspinboxchanged)
            self.ui.horizontalLayout_4.insertWidget(1, self.ui.spinBoxWeight)

        @property
        def x(self):
            if self.rng is None:
                return self._x
            else:
                return self._x[self.rng]

        def weight_sub_val_change(self):
            """ Weights Subsections spinbox has changed value """
            sbval = self.ui.spinBoxWSubSections.value()
            n_rows = self.ui.tableWidgetWeights.rowCount()
            n_cols = self.ui.tableWidgetWeights.columnCount()

            max_x = _np.max(self.x)
            min_x = _np.min(self.x)

            if sbval > n_rows:
                for nr in _np.arange(n_rows, sbval):
                    self.ui.tableWidgetWeights.setRowCount(sbval)
                    n_rows = self.ui.tableWidgetWeights.rowCount()

                    # Start X
                    scispin = _SciSpin()
                    scispin.setMinimum(self._x.min())
                    scispin.setMaximum(self._x.max())
                    scispin.setValue(min_x)
                    scispin.editingFinished.connect(self.weightspinboxchanged)
                    self.ui.tableWidgetWeights.setCellWidget(nr, 0, scispin)

                    # Stop X
                    scispin = _SciSpin()
                    scispin.setMinimum(self._x.min())
                    scispin.setMaximum(self._x.max())
                    scispin.setValue(max_x)
                    scispin.editingFinished.connect(self.weightspinboxchanged)
                    self.ui.tableWidgetWeights.setCellWidget(nr, 1, scispin)

                    # Weight Parameter
                    # scispin = _SciSpin()
                    # scispin.setMinimum(0)
                    # scispin.setMaximum(1e4)
                    # scispin.setValue(1)
                    # scispin.editingFinished.connect(self.weightspinboxchanged)
                    # self.ui.tableWidgetWeights.setCellWidget(nr, 2, scispin)
            elif sbval < n_rows:
                for nr in _np.arange(sbval, n_rows):
                    for nc in range(n_cols):
                        sw = self.ui.tableWidgetWeights.cellWidget(nr, nc)
                        sw.editingFinished.disconnect()

                self.ui.tableWidgetWeights.setRowCount(sbval)
                n_rows = self.ui.tableWidgetWeights.rowCount()

            self.weightspinboxchanged()

        def asym_sub_val_change(self):
            """ P Subsections spinbox has changed value """
            sbval = self.ui.spinBoxAsymSubSections.value()
            n_rows = self.ui.tableWidgetAsym.rowCount()
            n_cols = self.ui.tableWidgetAsym.columnCount()

            max_x = _np.max(self.x)
            min_x = _np.min(self.x)

            if sbval > n_rows:
                for nr in _np.arange(n_rows, sbval):
                    self.ui.tableWidgetAsym.setRowCount(sbval)
                    n_rows = self.ui.tableWidgetAsym.rowCount()

                    # Start X
                    scispin = _SciSpin()
                    scispin.setMinimum(self._x.min())
                    scispin.setMaximum(self._x.max())
                    scispin.setValue(min_x)
                    scispin.editingFinished.connect(self.asymspinboxchanged)
                    self.ui.tableWidgetAsym.setCellWidget(nr, 0, scispin)

                    # Stop X
                    scispin = _SciSpin()
                    scispin.setMinimum(self._x.min())
                    scispin.setMaximum(self._x.max())
                    scispin.setValue(max_x)
                    scispin.editingFinished.connect(self.asymspinboxchanged)
                    self.ui.tableWidgetAsym.setCellWidget(nr, 1, scispin)

                    # Asym Parameter
                    scispin = _SciSpin()
                    scispin.setMinimum(0)
                    scispin.setMaximum(1e20)
                    scispin.setValue(self.ui.spinBoxP.value())
                    scispin.editingFinished.connect(self.asymspinboxchanged)
                    self.ui.tableWidgetAsym.setCellWidget(nr, 2, scispin)
            elif sbval < n_rows:
                for nr in _np.arange(sbval, n_rows):
                    for nc in range(n_cols):
                        sw = self.ui.tableWidgetAsym.cellWidget(nr, nc)
                        sw.editingFinished.disconnect()

                self.ui.tableWidgetAsym.setRowCount(sbval)
                n_rows = self.ui.tableWidgetAsym.rowCount()

            self.asymspinboxchanged()

        def fcn(self, data_in):
            """
            If return list, [0] goes to original, [1] goes to affected
            """

            data_out = _np.zeros(data_in.shape)
            baseline = _np.zeros(data_in.shape)

            # if callable(self.parameters['asym_param']):
            #     self.parameters['asym_param'] = \
            #         self.parameters['asym_param'](data_in.shape[-1])

            # smoothness_param = self.parameters['smoothness_param']
            # asym_param = self.parameters['asym_param']
            # redux = self.parameters['redux']
            # fep = self.parameters['fix_end_points']
            # max_iter = self.parameters['max_iter']
            # min_diff = self.parameters['min_diff']
            # order = self.parameters['order']
            # rng = self.parameters['rng']
            # fix_rng = self.parameters['fix_rng']
            # smoothness_param=1e3, asym_param=1e-4, redux=1,
            #          order=2, rng=None, fix_end_points=False, fix_rng=None,
            #          fix_const=1, max_iter=100, min_diff=1e-5, verbose=False

            _als = _Als(**self.parameters)

            if data_in.ndim == 1:
                baseline = _als.calculate(data_in)
                data_out = data_in - baseline
            else:
                for num, spectrum in enumerate(data_in):
                    baseline[num,:] = _als.calculate(spectrum)
                    data_out[num,:] = spectrum - baseline[num,:]
            return [baseline, data_out]

        def setup_smoothness(self):
            """
            Lambda/smoothness parameter rlated
            """

            self.ui.label_2.setText('{} (Smoothness)'.format(u'\u03BB'))
            self.ui.spinBoxLambda = _SciSpin()
            self.ui.verticalLayout_2.insertWidget(1, self.ui.spinBoxLambda)
            self.ui.spinBoxLambda.setValue(self.parameters['smoothness_param'])

        def setup_asym(self):
            """
            P/asymmetry parameter related
            """

            self.ui.spinBoxP = _SciSpin()
            self.ui.verticalLayout.insertWidget(1, self.ui.spinBoxP)
            self.ui.spinBoxP.setValue(self.parameters['asym_param'])

        def asymspinboxchanged(self):
            """
            Asymetry parameter-related values have changed
            """
            n_rows = self.ui.tableWidgetAsym.rowCount()

            # * Currently, asym_param in ALS is in the full vector space
            x = self._x

            self.sub_asym_list = []

            if n_rows == 0:
                self.parameters['asym_param'] = self.ui.spinBoxP.value()
            else:
                self.parameters['asym_param'] = self.ui.spinBoxP.value() + 0*x
                for rc in range(n_rows):
                    xstart_pix = _find_nearest(x,
                                               self.ui.tableWidgetAsym.cellWidget(rc, 0).value())[1]
                    xstop_pix = _find_nearest(x,
                                              self.ui.tableWidgetAsym.cellWidget(rc, 1).value())[1]

                    asym = self.ui.tableWidgetAsym.cellWidget(rc, 2).value()

                    self.parameters['asym_param'][_np.minimum(xstart_pix, xstop_pix):_np.maximum(xstart_pix, xstop_pix)+1] = 1*asym
                    print('XStart: {}, XStop: {}, ASym: {}'.format(xstart_pix, xstop_pix, asym))
                    self.sub_asym_list.append([xstart_pix, xstop_pix, asym])

                print('---------')
            self.changed.emit()

        def weightspinboxchanged(self):
            """
            Weight parameter-related values have changed
            """
            n_rows = self.ui.tableWidgetWeights.rowCount()

            # * Currently, fix_rng (weight parameters) in ALS is in the rng vector space
            x = self.x

            self.sub_w_list = []

            if n_rows == 0:
                self.parameters['fix_rng'] = None
            else:
                self.parameters['fix_rng'] = []
                for rc in range(n_rows):
                    xstart_pix = _find_nearest(x,
                                               self.ui.tableWidgetWeights.cellWidget(rc, 0).value())[1]
                    xstop_pix = _find_nearest(x,
                                              self.ui.tableWidgetWeights.cellWidget(rc, 1).value())[1]
                    weight = self.ui.spinBoxWeight.value()

                    # self.parameters['fix_rng'].extend(_np.arange(xstart_pix, xstop_pix+1).tolist())

                    self.parameters['fix_rng'].extend(_np.arange(_np.minimum(xstart_pix, xstop_pix),_np.maximum(xstart_pix, xstop_pix)).tolist())  # No +1

                    # self.parameters['asym_param'][xstart_pix:xstop_pix+1] = 1*asym
                    # print('XStart: {}, XStop: {}, ASym: {}'.format(xstart_pix, xstop_pix, asym))
                    self.sub_w_list.append([xstart_pix, xstop_pix, weight])
                self.parameters['fix_rng'] = _np.array(self.parameters['fix_rng'])
                self.parameters['fix_const'] = self.ui.spinBoxWeight.value()
                print('---------')
            self.changed.emit()

        def spinBoxChanged(self):
            """
            Controller for all spinBoxes
            """
            sdr = self.sender()

            if sdr == self.ui.spinBoxLambda:
                self.parameters['smoothness_param'] = self.ui.spinBoxLambda.value()

            elif sdr == self.ui.spinBoxP:
                self.asymspinboxchanged()

            elif sdr == self.ui.spinBoxRedux:
                self.parameters['redux'] = self.ui.spinBoxRedux.value()

            elif sdr == self.ui.spinBoxMaxIter:
                self.parameters['max_iter'] = self.ui.spinBoxMaxIter.value()

            elif sdr == self.ui.spinBoxMinDiff:
                self.parameters['min_diff'] = self.ui.spinBoxMinDiff.value()

            self.changed.emit()

        def selectFixedEnds(self):
            """
            Check selection of fixed end-points
            """

            self.parameters['fix_end_points'] =self.ui.checkBox.isChecked()
            self.changed.emit()

        def selectWNIncrease(self):
            """
            Check selection of conjugate
            """

            self.parameters['wavenumber_increasing'] = self.ui.checkBoxWNIncreasing.isChecked()
            self.changed.emit()

    if __name__ == '__main__':
        import sys as _sys
        from PyQt5.QtWidgets import (QApplication as _QApplication)

        app = _QApplication(_sys.argv)
        app.setStyle('Cleanlooks')

        x = _np.linspace(500, 4000, 1001)
        winALS = widgetALS(x=x)

        winALS.show()

        app.exec_()
        print(winALS.parameters)
        _sys.exit()