예제 #1
0
파일: base.py 프로젝트: pchj/nmrbrew
class ToolBase(QObject):
    '''
    Base tool definition for inclusion in the UI. Define specific config settings;
    attach a panel widget for configuration.
    '''

    is_manual_runnable = True

    is_auto_runnable = True
    is_auto_rerunnable = True
    is_disableable = True

    progress = pyqtSignal(float)
    status = pyqtSignal(str)

    config_panel_size = 250
    view_widget = 'SpectraViewer'

    def __init__(self, parent, *args, **kwargs):
        super(ToolBase, self).__init__(parent, *args, **kwargs)

        self.config = ConfigManager()
        self.config.hooks.update(custom_pyqtconfig_hooks.items())
        self.config.set_defaults({
            'is_active': True,
            'auto_run_on_config_change': True
        })

        self.config.updated.connect(self.auto_run_on_config_change)

        self.buttonBar = QWidget()

        self.configPanels = QWidget()
        self.configLayout = QVBoxLayout()
        self.configLayout.setContentsMargins(0, 0, 0, 0)

        self.configPanels.setLayout(self.configLayout)

        self._previous_config_backup_ = {}

        self._worker_thread_ = None
        self._worker_thread_lock_ = False

        self.data = {
            'spc': None,
        }

        self.current_status = 'ready'
        self.current_progress = 0

        self.progress.connect(self.progress_callback)
        self.status.connect(self.status_callback)

    def addConfigPanel(self, panel):
        self.configLayout.addWidget(panel(self))

    def addButtonBar(self, buttons):
        '''
        Create a button bar

        Supplied with a list of QPushButton objects (already created using helper stubs; see below)

        :param buttons:
        :return:
        '''

        btnlayout = QHBoxLayout()
        btnlayout.addSpacerItem(
            QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum))
        for btn in buttons:
            btnlayout.addWidget(btn)

        self.configLayout.addLayout(btnlayout)
        btnlayout.addSpacerItem(
            QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum))

    def run_manual(self):
        pass

    def disable(self):
        self.status.emit('inactive')
        self.config.set('is_active', False)
        self.item.setFlags(Qt.NoItemFlags)

    def reset(self):
        self.config.set_many(self.config.defaults)

    def undo(self):
        self.config.set_many(self._config_backup_)

    def deftaultButtons(self):

        buttons = []

        if self.is_disableable:
            disable = QPushButton(
                QIcon(os.path.join(utils.scriptdir, 'icons', 'cross.png')),
                'Disable')
            disable.setToolTip('Disable this tool')
            disable.pressed.connect(self.disable)
            buttons.append(disable)

        reset = QPushButton(
            QIcon(
                os.path.join(utils.scriptdir, 'icons',
                             'arrow-turn-180-left.png')), 'Reset to defaults')
        reset.setToolTip('Reset to defaults')
        reset.pressed.connect(self.reset)
        buttons.append(reset)

        undo = QPushButton(
            QIcon(
                os.path.join(utils.scriptdir, 'icons',
                             'arrow-turn-180-left.png')), 'Undo')
        undo.setToolTip('Undo recent changes')
        undo.pressed.connect(self.undo)
        buttons.append(undo)

        if self.is_auto_runnable:
            auto = QPushButton(
                QIcon(os.path.join(utils.scriptdir, 'icons', 'lightning.png')),
                'Auto')
            auto.setToolTip('Auto-update spectra when settings change')
            auto.setCheckable(True)
            auto.pressed.connect(self.run_manual)
            self.config.add_handler('auto_run_on_config_change', auto)
            buttons.append(auto)

        if self.is_manual_runnable:
            apply = QPushButton(
                QIcon(os.path.join(utils.scriptdir, 'icons', 'play.png')),
                'Apply')
            apply.setToolTip('Apply current settings to spectra')
            apply.pressed.connect(self.run_manual)
            buttons.append(apply)

        return buttons

    def enable(self):
        if self.current_status == 'inactive':
            self.status.emit('ready')
            self.item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)

        self.config.set('is_active', True)

    def activate(self):
        self.parent().current_tool = self
        self.enable()

        self._config_backup_ = self.config.as_dict()

        self._refresh_plot_timer_ = QTimer.singleShot(0, self.plot)

        self.parent().viewStack.setCurrentWidget(self.parent().spectraViewer)

        self.parent().configstack.setCurrentWidget(self.configPanels)
        self.parent().configstack.setMaximumHeight(self.config_panel_size)

    def set_active(self, active):
        self.config.set('is_active', active)

    def get_previous_tool(self):
        # Get the previous ACTIVE tool in the tool table
        n = self.parent().tools.index(self)
        for tool in self.parent().tools[n - 1::-1]:
            if tool.current_status != 'inactive':
                return tool

        else:
            return None

    def get_previous_spc(self):
        t = self.get_previous_tool()
        if t:
            return t.data['spc']
        else:
            return None

    def plot(self, **kwargs):
        if 'spc' in self.data:
            self.parent().spectraViewer.plot(self.data['spc'], **kwargs)

    def get_plotitem(self):
        return self.parent().spectraViewer.spectraViewer.plotItem

    def auto_run_on_config_change(self):
        pass
        #if self.is_auto_runnable and self.config.get('is_active') and self.config.get('auto_run_on_config_change'):
        #    self.run_manual()

    def run(self, fn):
        '''
        Run the target function, passing in the current spectra, and config settings (as dict)
        :param fn:
        :return:
        '''
        if self._worker_thread_lock_:
            return False  # Can't run

        self.progress.emit(0)
        self.status.emit('active')

        spc = self.get_previous_spc()

        self._worker_thread_lock_ = True

        print(self.config.as_dict())
        self._worker_thread_ = Worker(fn=fn,
                                      **{
                                          'spc': deepcopy(spc),
                                          'config': self.config.as_dict(),
                                          'progress_callback':
                                          self.progress.emit,
                                      })

        self._worker_thread_.signals.finished.connect(self.finished)
        self._worker_thread_.signals.result.connect(self.result)
        self._worker_thread_.signals.error.connect(self.error)

        self.parent().threadpool.start(self._worker_thread_)

    def error(self, error):
        self.progress.emit(1.0)
        self.status.emit('error')
        logging.error(error)
        self._worker_thread_lock_ = False

    def result(self, result):
        self.progress.emit(1)
        self.status.emit('complete')

        # Apply post-processing
        if 'spc' in result:
            result['spc'] = self.post_process_spc(result['spc'])

        self.data = result
        self.plot()

    def finished(self):
        # Cleanup
        self._worker_thread_lock_ = False

    def progress_callback(self, progress):
        self.current_progress = progress
        self.item.setData(Qt.UserRole + 2, progress)

    def status_callback(self, status):
        self.current_status = status
        self.item.setData(Qt.UserRole + 3, status)

    def post_process_spc(self, spc):
        '''
        Apply post-processing to the spectra before loading into the data store, e.g. for outlier
        detection, stats etc.

        :param spc:
        :return:
        '''

        # Outliers

        def identify_outliers(data, m=2):
            return abs(data - np.mean(data, axis=0)) < (m *
                                                        np.std(data, axis=0))

        # Identify outliers on a point by point basis. Count up 'outliers' and score ratio of points that are
        # outliers for each specra > 5% (make this configurable) is an outlier.
        spc.outliers = np.sum(~identify_outliers(spc.data), axis=1) / float(
            spc.data.shape[1])
        return spc
예제 #2
0
class SerialDialog(QDialog):

    def __init__(self, settings, parent=None):
        super().__init__(parent)
        self.settings = settings
        self.serialports = []

        # port
        self.portLabel = QLabel(self.tr("COM Port:"))
        self.portComboBox = QComboBox()
        self.portLabel.setBuddy(self.portComboBox)
        self.refresh_comports(self.portComboBox)

        # baudrate
        self.baudrateLabel = QLabel(self.tr("Baudrate:"))
        self.baudrateComboBox = QComboBox()
        self.baudrateLabel.setBuddy(self.baudrateComboBox)
        for br in BAUDRATES:
            self.baudrateComboBox.addItem(str(br), br)

        # buttons
        self.dlgbuttons = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal)
        self.dlgbuttons.rejected.connect(self.reject)
        self.dlgbuttons.accepted.connect(self.accept)

        # layout
        layout = QGridLayout()
        layout.addWidget(self.portLabel, 0, 0)
        layout.addWidget(self.portComboBox, 0, 1)
        layout.addWidget(self.baudrateLabel, 1, 0)
        layout.addWidget(self.baudrateComboBox, 1, 1)
        layout.addWidget(self.dlgbuttons, 2, 0, 1, 2)
        self.setLayout(layout)
        self.setWindowTitle(self.tr("Serial Settings"))

        # settings
        defaults = {
            PORT_SETTING: "",
            BAUDRATE_SETTING: "115200"
        }
        self.tmp_settings = ConfigManager()
        self.tmp_settings.set_defaults(defaults)
        self.tmp_settings.set_many(
            {key: self.settings.get(key) for key in defaults.keys()}
        )
        self.tmp_settings.add_handler(PORT_SETTING, self.portComboBox)
        self.tmp_settings.add_handler(BAUDRATE_SETTING, self.baudrateComboBox)

    def accept(self):
        d = self.tmp_settings.as_dict()
        self.settings.set_many(d)
        super().accept()

    def refresh_comports(self, combobox):
        self.serialports = serial_ports()
        for port in self.serialports:
            combobox.addItem(port)

    @property
    def port(self):
        return self.portComboBox.currentText()

    @port.setter
    def port(self, value):
        if value in self.serialports:
            self.portComboBox.setCurrentIndex(
                self.portComboBox.findText(value))
        else:
            raise ValueError("serial port '%s' not available" % value)

    @property
    def baudrate(self):
        return self.baudrateComboBox.currentData()
예제 #3
0
class ToolBase(QObject):
    '''
    Base tool definition for inclusion in the UI. Define specific config settings;
    attach a panel widget for configuration.
    '''

    is_manual_runnable = True
    is_auto_runnable = False
    is_auto_rerunnable = True
    is_disableable = True

    progress = pyqtSignal(float)
    status = pyqtSignal(str)
    complete = pyqtSignal()

    config_panel_size = 150
    view = None

    def __init__(self, parent, *args, **kwargs):
        super(ToolBase, self).__init__(parent, *args, **kwargs)


        self.config = ConfigManager()
        self.config.hooks.update(custom_pyqtconfig_hooks.items())
        self.config.set_defaults({
            'auto_run_on_config_change': True
            })
        self.current_status = 'inactive'
        self.config.updated.connect(self.auto_run_on_config_change)

        self.buttonBar = QWidget()

        self.configPanels = QWidget()
        self.configLayout = QVBoxLayout()
        self.configLayout.setContentsMargins(0,0,0,0)

        self.configPanels.setLayout(self.configLayout)

        self._previous_config_backup_ = {}

        self._worker_thread_ = None
        self._worker_thread_lock_ = False

        self.view = MplView(self.parent())

        self.setup()

        self.current_progress = 0

        self.progress.connect(self.progress_callback)
        self.status.connect(self.status_callback)


    def setup(self):
        self.data = {
            'data': None,
        }
        self.view.figure.clear()
        self.view.redraw()
        self.status.emit('inactive' if self.current_status == 'inactive' else 'ready')



    def addConfigPanel(self, panel):
        self.configLayout.addWidget( panel(self) )

    def addButtonBar(self, buttons):
        '''
        Create a button bar

        Supplied with a list of QPushButton objects (already created using helper stubs; see below)

        :param buttons:
        :return:
        '''

        btnlayout = QHBoxLayout()
        btnlayout.addSpacerItem(QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum))
        for btn in buttons:
            btnlayout.addWidget(btn)

        self.configLayout.addLayout(btnlayout)
        btnlayout.addSpacerItem(QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum))

    def run_manual(self):
        pass


    def disable(self):
        self.status.emit('inactive')
        self.item.setFlags(Qt.NoItemFlags)

    def reset(self):
        self.config.set_many( self.config.defaults )

    def undo(self):
        self.config.set_many(self._config_backup_)

    def defaultButtons(self):

        buttons = []

        reset = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'arrow-turn-180-left.png')), 'Reset to defaults')
        reset.setToolTip('Reset to defaults')
        reset.pressed.connect(self.reset)
        buttons.append(reset)

        apply = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'play.png')), 'Apply')
        apply.setToolTip('Apply current settings to spectra')
        apply.pressed.connect(self.run_manual)
        buttons.append(apply)

        return buttons

    def enable(self):
        if self.current_status == 'inactive':
            self.current_status = 'ready'
            self.status.emit('ready')
            self.item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)

    def activate(self):
        self.parent().current_tool = self
        self.enable()

        self._config_backup_ = self.config.as_dict()

        self._refresh_plot_timer_ = QTimer.singleShot(0, self.plot)

        if self.view:
            self.parent().viewStack.setCurrentWidget(self.view)

        self.parent().configstack.setCurrentWidget(self.configPanels)
        self.parent().configstack.setMaximumHeight(self.config_panel_size)

    def set_active(self, active):
        self.config.set('is_active', active)

    def get_previous_tool(self):
        # Get the previous ACTIVE tool in the tool table
        n = self.parent().tools.index(self)
        for tool in self.parent().tools[n-1::-1]:
            if tool.current_status != 'inactive':
                return tool

        else:
            return None

    def get_next_tool(self):
        # Get the previous ACTIVE tool in the tool table
        n = self.parent().tools.index(self)
        for tool in self.parent().tools[n+1:]:
            if tool.current_status != 'inactive':
                return tool

        else:
            return None



    def get_previous_data(self):
        t = self.get_previous_tool()
        if t and 'data' in t.data:
            return t.data['data']
        else:
            return None

    def plot(self, **kwargs):
        pass
        #if 'spc' in self.data:
        #    self.parent().spectraViewer.plot(self.data['spc'], **kwargs)

    def get_plotitem(self):
        return self.parent().spectraViewer.spectraViewer.plotItem

    def auto_run_on_config_change(self):
        if self.is_auto_runnable and self.current_status == 'ready' and self.config.get('auto_run_on_config_change'):
            self.run_manual()

    def run(self, fn):
        '''
        Run the target function, passing in the current spectra, and config settings (as dict)
        :param fn:
        :return:
        '''
        if self._worker_thread_lock_:
            return False # Can't run

        self.progress.emit(0)
        self.status.emit('active')

        data = self.get_previous_data()

        self._worker_thread_lock_ = True

        kwargs = {
            'config': self.config.as_dict(),
            'progress_callback': self.progress.emit,
        }
        if data:
            kwargs.update(deepcopy(data))

        # Close any open figures; ensure new axes.
        plt.close()

        self._worker_thread_ = Worker(fn = fn, **kwargs)

        self._worker_thread_.signals.finished.connect(self.finished)
        self._worker_thread_.signals.result.connect(self.result)
        self._worker_thread_.signals.error.connect(self.error)

        self.parent().threadpool.start(self._worker_thread_)


    def error(self, error):
        self.progress.emit(1.0)
        self.status.emit('error')
        logging.error(error)
        self._worker_thread_lock_ = False



    def result(self, result):

        # Apply post-processing
        if 'fig' in result and result['fig']:
            fig = result['fig']
            fig.set_size_inches(self.view.get_size_inches(fig.get_dpi()))
            fig.set_tight_layout(False)
            fig.tight_layout(pad=0.5, rect=[0.25, 0.10, 0.75, 0.90])
            self.view.figure = fig
            self.view.redraw()

        self.data = result
        self.plot()

        self.progress.emit(1)
        self.status.emit('complete')

    def finished(self):
        # Cleanup
        self._worker_thread_lock_ = False
        self.complete.emit()


    def progress_callback(self, progress):
        self.current_progress = progress
        self.item.setData(Qt.UserRole + 2, progress)

    def status_callback(self, status):
        self.current_status = status
        self.item.setData(Qt.UserRole + 3, status)
예제 #4
0
파일: base.py 프로젝트: mfitzp/nmrbrew
class ToolBase(QObject):
    '''
    Base tool definition for inclusion in the UI. Define specific config settings;
    attach a panel widget for configuration.
    '''

    is_manual_runnable = True

    is_auto_runnable = True
    is_auto_rerunnable = True
    is_disableable = True

    progress = pyqtSignal(float)
    status = pyqtSignal(str)

    config_panel_size = 250
    view_widget = 'SpectraViewer'

    def __init__(self, parent, *args, **kwargs):
        super(ToolBase, self).__init__(parent, *args, **kwargs)

        self.config = ConfigManager()
        self.config.hooks.update(custom_pyqtconfig_hooks.items())
        self.config.set_defaults({
            'is_active': True,
            'auto_run_on_config_change': True
            })

        self.config.updated.connect(self.auto_run_on_config_change)

        self.buttonBar = QWidget()

        self.configPanels = QWidget()
        self.configLayout = QVBoxLayout()
        self.configLayout.setContentsMargins(0,0,0,0)

        self.configPanels.setLayout(self.configLayout)

        self._previous_config_backup_ = {}

        self._worker_thread_ = None
        self._worker_thread_lock_ = False

        self.data = {
            'spc': None,

        }

        self.current_status = 'ready'
        self.current_progress = 0

        self.progress.connect(self.progress_callback)
        self.status.connect(self.status_callback)

    def addConfigPanel(self, panel):
        self.configLayout.addWidget( panel(self) )

    def addButtonBar(self, buttons):
        '''
        Create a button bar

        Supplied with a list of QPushButton objects (already created using helper stubs; see below)

        :param buttons:
        :return:
        '''

        btnlayout = QHBoxLayout()
        btnlayout.addSpacerItem(QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum))
        for btn in buttons:
            btnlayout.addWidget(btn)

        self.configLayout.addLayout(btnlayout)
        btnlayout.addSpacerItem(QSpacerItem(250, 1, QSizePolicy.Maximum, QSizePolicy.Maximum))

    def run_manual(self):
        pass


    def disable(self):
        self.status.emit('inactive')
        self.config.set('is_active', False)
        self.item.setFlags(Qt.NoItemFlags)

    def reset(self):
        self.config.set_many( self.config.defaults )

    def undo(self):
        self.config.set_many(self._config_backup_)

    def deftaultButtons(self):

        buttons = []

        if self.is_disableable:
            disable = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'cross.png')), 'Disable')
            disable.setToolTip('Disable this tool')
            disable.pressed.connect(self.disable)
            buttons.append(disable)

        reset = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'arrow-turn-180-left.png')), 'Reset to defaults')
        reset.setToolTip('Reset to defaults')
        reset.pressed.connect(self.reset)
        buttons.append(reset)

        undo = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'arrow-turn-180-left.png')), 'Undo')
        undo.setToolTip('Undo recent changes')
        undo.pressed.connect(self.undo)
        buttons.append(undo)

        if self.is_auto_runnable:
            auto = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'lightning.png')), 'Auto')
            auto.setToolTip('Auto-update spectra when settings change')
            auto.setCheckable(True)
            auto.pressed.connect(self.run_manual)
            self.config.add_handler('auto_run_on_config_change', auto)
            buttons.append(auto)

        if self.is_manual_runnable:
            apply = QPushButton(QIcon(os.path.join(utils.scriptdir, 'icons', 'play.png')), 'Apply')
            apply.setToolTip('Apply current settings to spectra')
            apply.pressed.connect(self.run_manual)
            buttons.append(apply)

        return buttons

    def enable(self):
        if self.current_status == 'inactive':
            self.status.emit('ready')
            self.item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)

        self.config.set('is_active', True)


    def activate(self):
        self.parent().current_tool = self
        self.enable()

        self._config_backup_ = self.config.as_dict()

        self._refresh_plot_timer_ = QTimer.singleShot(0, self.plot)

        self.parent().viewStack.setCurrentWidget(self.parent().spectraViewer)

        self.parent().configstack.setCurrentWidget(self.configPanels)
        self.parent().configstack.setMaximumHeight(self.config_panel_size)

    def set_active(self, active):
        self.config.set('is_active', active)

    def get_previous_tool(self):
        # Get the previous ACTIVE tool in the tool table
        n = self.parent().tools.index(self)
        for tool in self.parent().tools[n-1::-1]:
            if tool.current_status != 'inactive':
                return tool

        else:
            return None


    def get_previous_spc(self):
        t = self.get_previous_tool()
        if t:
            return t.data['spc']
        else:
            return None

    def plot(self, **kwargs):
        if 'spc' in self.data:
            self.parent().spectraViewer.plot(self.data['spc'], **kwargs)

    def get_plotitem(self):
        return self.parent().spectraViewer.spectraViewer.plotItem

    def auto_run_on_config_change(self):
        pass
        #if self.is_auto_runnable and self.config.get('is_active') and self.config.get('auto_run_on_config_change'):
        #    self.run_manual()

    def run(self, fn):
        '''
        Run the target function, passing in the current spectra, and config settings (as dict)
        :param fn:
        :return:
        '''
        if self._worker_thread_lock_:
            return False # Can't run

        self.progress.emit(0)
        self.status.emit('active')

        spc = self.get_previous_spc()

        self._worker_thread_lock_ = True

        print(self.config.as_dict())
        self._worker_thread_ = Worker(fn = fn, **{
            'spc': deepcopy(spc),
            'config': self.config.as_dict(),
            'progress_callback': self.progress.emit,
        })

        self._worker_thread_.signals.finished.connect(self.finished)
        self._worker_thread_.signals.result.connect(self.result)
        self._worker_thread_.signals.error.connect(self.error)

        self.parent().threadpool.start(self._worker_thread_)


    def error(self, error):
        self.progress.emit(1.0)
        self.status.emit('error')
        logging.error(error)
        self._worker_thread_lock_ = False

    def result(self, result):
        self.progress.emit(1)
        self.status.emit('complete')

        # Apply post-processing
        if 'spc' in result:
            result['spc'] = self.post_process_spc(result['spc'])

        self.data = result
        self.plot()

    def finished(self):
        # Cleanup
        self._worker_thread_lock_ = False


    def progress_callback(self, progress):
        self.current_progress = progress
        self.item.setData(Qt.UserRole + 2, progress)

    def status_callback(self, status):
        self.current_status = status
        self.item.setData(Qt.UserRole + 3, status)

    def post_process_spc(self, spc):
        '''
        Apply post-processing to the spectra before loading into the data store, e.g. for outlier
        detection, stats etc.

        :param spc:
        :return:
        '''

        # Outliers

        def identify_outliers(data, m=2):
            return abs(data - np.mean(data, axis=0)) < (m * np.std(data,axis=0))

        # Identify outliers on a point by point basis. Count up 'outliers' and score ratio of points that are
        # outliers for each specra > 5% (make this configurable) is an outlier.
        spc.outliers = np.sum( ~identify_outliers(spc.data), axis=1 ) / float(spc.data.shape[1])
        return spc