Exemplo n.º 1
0
class BaseAnnotationItem(ResizableGraphicsItem):

    handler_cache = {}
    styles = ['font-family', 'font-size', 'text-bold', 'text-italic', 'text-underline', 'text-color', 'color-border', 'color-background']

    minSize = ANNOTATION_MINIMUM_QSIZE

    def __init__(self, position=None, *args, **kwargs):
        super(BaseAnnotationItem, self).__init__(*args, **kwargs)
        # Config for each annotation item, holding the settings (styles, etc)
        # update-control via the toolbar using add_handler linking
        self.config = ConfigManager()
        self.config.updated.connect(self.applyStyleConfig)

        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setFlag(QGraphicsItem.ItemIsFocusable)

        if position:
            self.setPos(position)

        self.setZValue(-1)

    def delete(self):
        self.prepareGeometryChange()
        self.scene().annotations.remove(self)
        self.scene().removeItem(self)
        self.removeHandlers()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Backspace and e.modifiers() == Qt.ControlModifier:
            self.delete()
        else:
            return super(BaseAnnotationItem, self).keyPressEvent(e)

    def importStyleConfig(self, config):
        for k in self.styles:
            self.config.set(k, config.get(k))

    def addHandlers(self):
        m = self.scene().views()[0].m  # Hack; need to switch to importing this
        for k in self.styles:
            self.config.add_handler(k, m.styletoolbarwidgets[k])

    def removeHandlers(self):
        for k in self.styles:
            self.config.remove_handler(k)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedChange:
            if not value:
                self.removeHandlers()

        elif change == QGraphicsItem.ItemSelectedHasChanged:
            if value:
                self.addHandlers()

        return super(BaseAnnotationItem, self).itemChange(change, value)
Exemplo n.º 2
0
class QGraphicsSceneExtend(QGraphicsScene):

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

        self.m = parent.m

        self.config = ConfigManager()
        # These config settings are transient (ie. not stored between sessions)
        self.config.set_defaults({
            'mode': EDITOR_MODE_NORMAL,
            'font-family': 'Arial',
            'font-size': '12',
            'text-bold': False,
            'text-italic': False,
            'text-underline': False,
            'text-color': '#000000',
            'color-border': None,  # '#000000',
            'color-background': None,
        })

        # Pre-set these values (will be used by default)
        self.config.set('color-background', '#5555ff')

        self.background_image = QImage(os.path.join(utils.scriptdir, 'icons', 'grid100.png'))
        if settings.get('Editor/Show_grid'):
            self.showGrid()
        else:
            self.hideGrid()

        self.mode = EDITOR_MODE_NORMAL
        self.mode_current_object = None

        self.annotations = []

    def mousePressEvent(self, e):
    
        if self.config.get('mode') != EDITOR_MODE_NORMAL:

            for i in self.selectedItems():
                i.setSelected(False)

            if self.config.get('mode') == EDITOR_MODE_TEXT:
                tw = AnnotationTextItem(position=e.scenePos())

            elif self.config.get('mode') == EDITOR_MODE_REGION:
                tw = AnnotationRegionItem(position=e.scenePos())

            elif self.config.get('mode') == EDITOR_MODE_ARROW:
                tw = AnnotationRegionItem(position=e.scenePos())

            self.addItem(tw)
            self.mode_current_object = tw
            tw._createFromMousePressEvent(e)
            tw.importStyleConfig(self.config)

            self.annotations.append(tw)

        else:
            for i in self.selectedItems():
                i.setSelected(False)
        
            super(QGraphicsSceneExtend, self).mousePressEvent(e)

    def mouseMoveEvent(self, e):
        if self.config.get('mode') == EDITOR_MODE_TEXT and self.mode_current_object:
            self.mode_current_object._resizeFromMouseMoveEvent(e)

        elif self.config.get('mode') == EDITOR_MODE_REGION and self.mode_current_object:
            self.mode_current_object._resizeFromMouseMoveEvent(e)

        else:
            super(QGraphicsSceneExtend, self).mouseMoveEvent(e)

    def mouseReleaseEvent(self, e):
        if self.config.get('mode'):
            self.mode_current_object.setSelected(True)
            self.mode_current_object.setFocus()

            self.config.set('mode', EDITOR_MODE_NORMAL)
            self.mode_current_object = None

        super(QGraphicsSceneExtend, self).mouseReleaseEvent(e)

    def showGrid(self):
        self.setBackgroundBrush(QBrush(self.background_image))

    def hideGrid(self):
        self.setBackgroundBrush(QBrush(None))

    def onSaveAsImage(self):
        filename, _ = QFileDialog.getSaveFileName(self.m, 'Save current figure', '', "Tagged Image File Format (*.tif);;\
                                                                                     Portable Network Graphics (*.png)")
        if filename:
            self.saveAsImage(filename)

    def saveAsImage(self, f):
        self.image = QImage(self.sceneRect().size().toSize(), QImage.Format_ARGB32)
        self.image.fill(Qt.white)
        painter = QPainter(self.image)
        self.render(painter)
        self.image.save(f)

    def addApp(self, app, position=None):
        i = ToolItem(self, app, position=position)
        self.addItem(i)
        #i.onShow()

        return i

    def removeApp(self, app):
        i = app.editorItem
        i.hide()
        self.removeItem(i)
        app.editorItem = None

    def dragEnterEvent(self, e):
        if e.mimeData().hasFormat('application/x-pathomx-app') or e.mimeData().hasFormat('text/uri-list'):
            e.accept()
        else:
            e.ignore()

    def dragMoveEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        scenePos = e.scenePos() - QPointF(32, 32)

        if e.mimeData().hasFormat('application/x-pathomx-app'):
            try:
                app_id = str(e.mimeData().data('application/x-pathomx-app'), 'utf-8')  # Python 3 
            except:
                app_id = str(e.mimeData().data('application/x-pathomx-app'))  # Python 2

            e.setDropAction(Qt.CopyAction)
            a = app_launchers[app_id](self.m, position=scenePos, auto_focus=False)
            #self.centerOn(a.editorItem)
            e.accept()

        elif e.mimeData().hasFormat('text/uri-list'):
            for ufn in e.mimeData().urls():
                fn = ufn.path()
                fnn, ext = os.path.splitext(fn)
                ext = ext.strip('.')
                if ext in file_handlers:
                    a = file_handlers[ext](position=scenePos, auto_focus=False, filename=fn)
                    self.centerOn(a.editorItem)
                    e.accept()

    def getXMLAnnotations(self, root):

    # Iterate over the entire set (in order) creating a XML representation of the MatchDef and Style
        for annotation in self.annotations:

            ase = et.SubElement(root, "Annotation")
            ase.set('type', type(annotation).__name__)

            ase.set('x', str(annotation.x()))
            ase.set('y', str(annotation.y()))
            ase.set('width', str(annotation.rect().width()))
            ase.set('height', str(annotation.rect().height()))

            if hasattr(annotation, 'text'):
                text = et.SubElement(ase, "Text")
                text.text = annotation.text.toPlainText()

            ase = annotation.config.getXMLConfig(ase)

        return root

    def setXMLAnnotations(self, root):

        ANNOTATION_TYPES = {
            'AnnotationTextItem': AnnotationTextItem,
            'AnnotationRegionItem': AnnotationRegionItem,
        }

        for ase in root.findall('Annotation'):

            # Validate the class definition before creating it
            if ase.get('type') in ANNOTATION_TYPES:

                pos = QPointF(float(ase.get('x')), float(ase.get('y')))
                aobj = ANNOTATION_TYPES[ase.get('type')](position=pos)
                aobj.setRect(QRectF(0, 0, float(ase.get('width')), float(ase.get('height'))))

                to = ase.find('Text')
                if to is not None:
                    aobj.text.setPlainText(to.text)

                self.addItem(aobj)
                self.annotations.append(aobj)
                aobj.config.setXMLConfig(ase)
                aobj.applyStyleConfig()
Exemplo n.º 3
0
Arquivo: ui.py Projeto: pchj/nmrbrew
class AnnotatePeaks(GenericDialog):
    def __init__(self, parent, config=None, *args, **kwargs):
        super(AnnotatePeaks, self).__init__(parent, *args, **kwargs)

        self.setWindowTitle('Annotate Peaks')

        if config:
            # Copy in starting state
            self.config = ConfigManager()
            self.config.hooks.update(custom_pyqtconfig_hooks.items())

            self.config.set_defaults(config)

        self.fwd_map_cache = {}

        # Correlation variables
        gb = QGroupBox('Peaks')
        vbox = QVBoxLayout()
        # Populate the list boxes
        self.lw_peaks = QListWidgetAddRemove()
        self.lw_peaks.setSelectionMode(QAbstractItemView.ExtendedSelection)
        vbox.addWidget(self.lw_peaks)

        vboxh = QHBoxLayout()

        self.add_label = QLineEdit()
        self.add_start = QDoubleSpinBox()
        self.add_start.setRange(-1, 12)
        self.add_start.setDecimals(3)
        self.add_start.setSuffix('ppm')
        self.add_start.setSingleStep(0.001)

        self.add_end = QDoubleSpinBox()
        self.add_end.setRange(-1, 12)
        self.add_end.setDecimals(3)
        self.add_end.setSuffix('ppm')
        self.add_end.setSingleStep(0.001)

        addc = QPushButton('Add')
        addc.clicked.connect(self.onPeakAdd)

        remc = QPushButton('Remove selected')
        remc.clicked.connect(self.onPeakAdd)

        loadc = QPushButton("Import from file")
        loadc.setIcon(
            QIcon(
                os.path.join(utils.scriptdir, 'icons',
                             'folder-open-document.png')))
        loadc.clicked.connect(self.onPeakImport)

        metabh = QPushButton("Auto match via MetaboHunter")
        metabh.setIcon(
            QIcon(os.path.join(utils.scriptdir, 'icons', 'metabohunter.png')))
        metabh.clicked.connect(self.onPeakImportMetabohunter)

        vboxh.addWidget(self.add_label)
        vboxh.addWidget(self.add_start)
        vboxh.addWidget(self.add_end)

        vboxh.addWidget(addc)

        vbox.addWidget(remc)
        vbox.addLayout(vboxh)
        vbox.addWidget(loadc)
        vbox.addWidget(metabh)

        gb.setLayout(vbox)

        self.layout.addWidget(gb)

        self.config.add_handler('annotation/peaks', self.lw_peaks,
                                (self.map_list_fwd, self.map_list_rev))

        self.dialogFinalise()

    def onPeakAdd(self):
        c = self.config.get(
            'annotation/peaks'
        )[:]  # Create new list to force refresh on reassign
        c.append((self.add_label.text(), float(self.add_start.value()),
                  float(self.add_end.value())))
        self.config.set('annotation/peaks', c)

    def onPeakRemove(self):
        i = self.lw_peaks.removeItemAt(self.lw_peaks.currentRow())
        # c = self.map_list_fwd(i.text())

    def onPeakImport(self):
        filename, _ = QFileDialog.getOpenFileName(
            self.parent(), 'Load peak annotations from file', '',
            "All compatible files (*.csv *.txt *.tsv);;Comma Separated Values (*.csv);;Plain Text Files (*.txt);;Tab Separated Values (*.tsv);;All files (*.*)"
        )
        if filename:
            c = self.config.get(
                'annotation/peaks'
            )[:]  # Create new list to force refresh on reassign

            with open(filename, 'rU') as f:
                reader = csv.reader(f, delimiter=b',', dialect='excel')
                for row in reader:
                    if row not in c:
                        c.append(row[0], float(row[1]), float(row[2]))

            self.config.set('annotation/peaks', c)

    def onPeakImportMetabohunter(self):
        c = self.config.get(
            'annotation/peaks'
        )[:]  # Create new list to force refresh on reassign
        t = self.parent().current_tool

        dlg = MetaboHunter(self)
        if dlg.exec_():

            if 'spc' in t.data:
                # We have a spectra; calcuate mean; reduce size if required
                spc = t.data['spc']
                n = spc.data.shape[1]
                ppm = spc.ppm
                spcd = np.mean(spc.data, axis=0)

                # Set a hard limit on the size of data we submit to be nice.
                if n > 3000:
                    # Calculate the division required to be under the limit
                    d = np.ceil(float(n) / 3000)
                    # Trim axis to multiple of divisor
                    trim = (n // d) * d
                    spcd = spcd[:trim]
                    ppm = ppm[:trim]
                    # Mean d shape
                    spcd = np.mean(spcd.reshape(-1, d), axis=1)
                    ppm = np.mean(ppm.reshape(-1, d), axis=1)

            # Submit with settings
            hmdbs = metabohunter.request(
                ppm,
                spcd,
                metabotype=dlg.config.get('Metabotype'),
                database=dlg.config.get('Database Source'),
                ph=dlg.config.get('Sample pH'),
                solvent=dlg.config.get('Solvent'),
                frequency=dlg.config.get('Frequency'),
                method=dlg.config.get('Method'),
                noise=dlg.config.get('Noise Threshold'),
                confidence=dlg.config.get('Confidence Threshold'),
                tolerance=dlg.config.get('Tolerance'))

            ha = np.array(hmdbs)
            unique_hmdbs = set(hmdbs)
            if None in unique_hmdbs:
                unique_hmdbs.remove(None)

            # Extract out regions
            for hmdb in unique_hmdbs:
                hb = np.diff(ha == hmdb)

                # These are needed to ensure markers are there for objects starting and ending on array edge
                if ha[0] == hmdb:
                    hb[0] == True

                if ha[-1] == hmdb:
                    hb[-1] == True

                idx = np.nonzero(hb)[0]
                idx = idx.reshape(-1, 2)

                if dlg.config.get(
                        'convert_hmdb_ids_to_names'
                ) and hmdb in METABOHUNTER_HMDB_NAME_MAP.keys():
                    label = METABOHUNTER_HMDB_NAME_MAP[hmdb]
                else:
                    label = hmdb

                # Now we have an array of all start, stop positions for this item
                for start, stop in idx:
                    c.append((label, ppm[start], ppm[stop]))

        self.config.set('annotation/peaks', c)

    def map_list_fwd(self, s):
        " Receive text name, return the indexes "
        return self.fwd_map_cache[s]

    def map_list_rev(self, x):
        " Receive the indexes, return the label"
        s = "%s\t%.2f\t%.2f" % tuple(x)
        self.fwd_map_cache[s] = x
        return s
Exemplo n.º 4
0
Arquivo: ui.py Projeto: pchj/nmrbrew
class AnnotateClasses(GenericDialog):
    def __init__(self, parent, config=None, *args, **kwargs):
        super(AnnotateClasses, self).__init__(parent, *args, **kwargs)

        self.setWindowTitle('Annotate Classes')

        if config:
            # Copy in starting state
            self.config = ConfigManager()
            self.config.hooks.update(custom_pyqtconfig_hooks.items())

            self.config.set_defaults(config)

        self.fwd_map_cache = {}

        # Correlation variables
        gb = QGroupBox('Sample classes')
        vbox = QVBoxLayout()
        # Populate the list boxes
        self.lw_classes = QListWidgetAddRemove()
        self.lw_classes.setSelectionMode(QAbstractItemView.ExtendedSelection)
        vbox.addWidget(self.lw_classes)

        vboxh = QHBoxLayout()

        self.add_label = QLineEdit()
        self.add_class = QLineEdit()

        addc = QPushButton('Add')
        addc.clicked.connect(self.onClassAdd)

        remc = QPushButton('Remove selected')
        remc.clicked.connect(self.onClassAdd)

        loadc = QPushButton('Import from file')
        loadc.setIcon(
            QIcon(
                os.path.join(utils.scriptdir, 'icons',
                             'folder-open-document.png')))
        loadc.clicked.connect(self.onClassImport)

        vboxh.addWidget(self.add_label)
        vboxh.addWidget(self.add_class)
        vboxh.addWidget(addc)

        vbox.addWidget(remc)
        vbox.addLayout(vboxh)
        vboxh.addWidget(loadc)

        gb.setLayout(vbox)

        self.layout.addWidget(gb)

        self.config.add_handler('annotation/sample_classes', self.lw_classes,
                                (self.map_list_fwd, self.map_list_rev))

        self.dialogFinalise()

    def onClassAdd(self):
        c = self.config.get(
            'annotation/sample_classes'
        )[:]  # Create new list to force refresh on reassign
        c.append((self.add_label.text(), self.add_class.text()))
        self.config.set('annotation/sample_classes', c)

    def onClassRemove(self):
        i = self.lw_classes.removeItemAt(self.lw_classes.currentRow())
        # c = self.map_list_fwd(i.text())

    def onClassImport(self):
        filename, _ = QFileDialog.getOpenFileName(
            self.parent(), 'Load classifications from file', '',
            "All compatible files (*.csv *.txt *.tsv);;Comma Separated Values (*.csv);;Plain Text Files (*.txt);;Tab Separated Values (*.tsv);;All files (*.*)"
        )
        if filename:
            c = self.config.get(
                'annotation/sample_classes'
            )[:]  # Create new list to force refresh on reassign

            with open(filename, 'rU') as f:
                reader = csv.reader(f, delimiter=b',', dialect='excel')
                for row in reader:
                    if row not in c:
                        c.append(row[:2])

            self.config.set('annotation/sample_classes', c)

    def map_list_fwd(self, s):
        " Receive text name, return the indexes "
        return self.fwd_map_cache[s]

    def map_list_rev(self, x):
        " Receive the indexes, return the label"
        s = "%s\t%s" % tuple(x)
        self.fwd_map_cache[s] = x
        return s
Exemplo n.º 5
0
class BaseAnnotationItem(ResizableGraphicsItem):

    handler_cache = {}
    styles = [
        'font-family', 'font-size', 'text-bold', 'text-italic',
        'text-underline', 'text-color', 'color-border', 'color-background'
    ]

    minSize = ANNOTATION_MINIMUM_QSIZE

    def __init__(self, position=None, *args, **kwargs):
        super(BaseAnnotationItem, self).__init__(*args, **kwargs)
        # Config for each annotation item, holding the settings (styles, etc)
        # update-control via the toolbar using add_handler linking
        self.config = ConfigManager()
        self.config.updated.connect(self.applyStyleConfig)

        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setFlag(QGraphicsItem.ItemIsSelectable)
        self.setFlag(QGraphicsItem.ItemIsFocusable)

        if position:
            self.setPos(position)

        self.setZValue(-1)

    def delete(self):
        self.prepareGeometryChange()
        self.scene().annotations.remove(self)
        self.scene().removeItem(self)
        self.removeHandlers()

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Backspace and e.modifiers() == Qt.ControlModifier:
            self.delete()
        else:
            return super(BaseAnnotationItem, self).keyPressEvent(e)

    def importStyleConfig(self, config):
        for k in self.styles:
            self.config.set(k, config.get(k))

    def addHandlers(self):
        m = self.scene().views()[0].m  # Hack; need to switch to importing this
        for k in self.styles:
            self.config.add_handler(k, m.styletoolbarwidgets[k])

    def removeHandlers(self):
        for k in self.styles:
            self.config.remove_handler(k)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedChange:
            if not value:
                self.removeHandlers()

        elif change == QGraphicsItem.ItemSelectedHasChanged:
            if value:
                self.addHandlers()

        return super(BaseAnnotationItem, self).itemChange(change, value)
Exemplo n.º 6
0
class QGraphicsSceneExtend(QGraphicsScene):
    def __init__(self, parent, *args, **kwargs):
        super(QGraphicsSceneExtend, self).__init__(parent, *args, **kwargs)

        self.m = parent.m

        self.config = ConfigManager()
        # These config settings are transient (ie. not stored between sessions)
        self.config.set_defaults({
            'mode': EDITOR_MODE_NORMAL,
            'font-family': 'Arial',
            'font-size': '12',
            'text-bold': False,
            'text-italic': False,
            'text-underline': False,
            'text-color': '#000000',
            'color-border': None,  # '#000000',
            'color-background': None,
        })

        # Pre-set these values (will be used by default)
        self.config.set('color-background', '#5555ff')

        self.background_image = QImage(
            os.path.join(utils.scriptdir, 'icons', 'grid100.png'))
        if settings.get('Editor/Show_grid'):
            self.showGrid()
        else:
            self.hideGrid()

        self.mode = EDITOR_MODE_NORMAL
        self.mode_current_object = None

        self.annotations = []

    def mousePressEvent(self, e):
        if self.config.get('mode') != EDITOR_MODE_NORMAL:

            for i in self.selectedItems():
                i.setSelected(False)

            if self.config.get('mode') == EDITOR_MODE_TEXT:
                tw = AnnotationTextItem(position=e.scenePos())

            elif self.config.get('mode') == EDITOR_MODE_REGION:
                tw = AnnotationRegionItem(position=e.scenePos())

            elif self.config.get('mode') == EDITOR_MODE_ARROW:
                tw = AnnotationRegionItem(position=e.scenePos())

            self.addItem(tw)
            self.mode_current_object = tw
            tw._createFromMousePressEvent(e)
            tw.importStyleConfig(self.config)

            self.annotations.append(tw)

        else:
            super(QGraphicsSceneExtend, self).mousePressEvent(e)

    def mouseMoveEvent(self, e):
        if self.config.get(
                'mode') == EDITOR_MODE_TEXT and self.mode_current_object:
            self.mode_current_object._resizeFromMouseMoveEvent(e)

        elif self.config.get(
                'mode') == EDITOR_MODE_REGION and self.mode_current_object:
            self.mode_current_object._resizeFromMouseMoveEvent(e)

        else:
            super(QGraphicsSceneExtend, self).mouseMoveEvent(e)

    def mouseReleaseEvent(self, e):
        if self.config.get('mode'):
            self.mode_current_object.setSelected(True)
            self.mode_current_object.setFocus()

            self.config.set('mode', EDITOR_MODE_NORMAL)
            self.mode_current_object = None

        super(QGraphicsSceneExtend, self).mouseReleaseEvent(e)

    def showGrid(self):
        self.setBackgroundBrush(QBrush(self.background_image))

    def hideGrid(self):
        self.setBackgroundBrush(QBrush(None))

    def onSaveAsImage(self):
        filename, _ = QFileDialog.getSaveFileName(
            self.m, 'Save current figure', '',
            "Tagged Image File Format (*.tif);;\
                                                                                     Portable Network Graphics (*.png)"
        )
        if filename:
            self.saveAsImage(filename)

    def saveAsImage(self, f):
        self.image = QImage(self.sceneRect().size().toSize(),
                            QImage.Format_ARGB32)
        self.image.fill(Qt.white)
        painter = QPainter(self.image)
        self.render(painter)
        self.image.save(f)

    def addApp(self, app, position=None):
        i = ToolItem(self, app, position=position)
        self.addItem(i)
        return i

    def removeApp(self, app):
        i = app.editorItem
        i.hide()
        self.removeItem(i)
        app.editorItem = None

    def dragEnterEvent(self, e):
        if e.mimeData().hasFormat('application/x-pathomx-app') or e.mimeData(
        ).hasFormat('text/uri-list'):
            e.accept()
        else:
            e.ignore()

    def dragMoveEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        scenePos = e.scenePos() - QPointF(32, 32)

        if e.mimeData().hasFormat('application/x-pathomx-app'):
            try:
                app_id = str(e.mimeData().data('application/x-pathomx-app'),
                             'utf-8')  # Python 3
            except:
                app_id = str(
                    e.mimeData().data('application/x-pathomx-app'))  # Python 2

            e.setDropAction(Qt.CopyAction)
            a = app_launchers[app_id](self.m,
                                      position=scenePos,
                                      auto_focus=False)
            #self.centerOn(a.editorItem)
            e.accept()

        elif e.mimeData().hasFormat('text/uri-list'):
            for ufn in e.mimeData().urls():
                fn = ufn.path()
                fnn, ext = os.path.splitext(fn)
                ext = ext.strip('.')
                if ext in file_handlers:
                    a = file_handlers[ext](position=scenePos,
                                           auto_focus=False,
                                           filename=fn)
                    self.centerOn(a.editorItem)
                    e.accept()

    def getXMLAnnotations(self, root):

        # Iterate over the entire set (in order) creating a XML representation of the MatchDef and Style
        for annotation in self.annotations:

            ase = et.SubElement(root, "Annotation")
            ase.set('type', type(annotation).__name__)

            ase.set('x', str(annotation.x()))
            ase.set('y', str(annotation.y()))
            ase.set('width', str(annotation.rect().width()))
            ase.set('height', str(annotation.rect().height()))

            if hasattr(annotation, 'text'):
                text = et.SubElement(ase, "Text")
                text.text = annotation.text.toPlainText()

            ase = annotation.config.getXMLConfig(ase)

        return root

    def setXMLAnnotations(self, root):

        ANNOTATION_TYPES = {
            'AnnotationTextItem': AnnotationTextItem,
            'AnnotationRegionItem': AnnotationRegionItem,
        }

        for ase in root.findall('Annotation'):

            # Validate the class definition before creating it
            if ase.get('type') in ANNOTATION_TYPES:

                pos = QPointF(float(ase.get('x')), float(ase.get('y')))
                aobj = ANNOTATION_TYPES[ase.get('type')](position=pos)
                aobj.setRect(
                    QRectF(0, 0, float(ase.get('width')),
                           float(ase.get('height'))))

                to = ase.find('Text')
                if to is not None:
                    aobj.text.setPlainText(to.text)

                self.addItem(aobj)
                self.annotations.append(aobj)
                aobj.config.setXMLConfig(ase)
                aobj.applyStyleConfig()
Exemplo n.º 7
0
Arquivo: base.py Projeto: 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
Exemplo n.º 8
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)
Exemplo n.º 9
0
class AutomatonDialog(GenericDialog):

    mode_options = {
        'Manual': MODE_MANUAL,
        'Watch files': MODE_WATCH_FILES,
        'Watch folder': MODE_WATCH_FOLDER,
        'Timer': MODE_TIMER,
    }

    def __init__(self, parent, **kwargs):
        super(AutomatonDialog, self).__init__(parent, **kwargs)
        self.setWindowTitle("Edit Automaton")

        self.config = ConfigManager()

        gb = QGroupBox('IPython notebook(s) (*.ipynb)')
        grid = QGridLayout()
        notebook_path_le = QLineEdit()
        self.config.add_handler('notebook_paths',
                                notebook_path_le,
                                mapper=(lambda x: x.split(";"),
                                        lambda x: ";".join(x)))
        grid.addWidget(notebook_path_le, 0, 0, 1, 2)

        notebook_path_btn = QToolButton()
        notebook_path_btn.setIcon(
            QIcon(
                os.path.join(utils.scriptdir, 'icons',
                             'document-attribute-i.png')))
        notebook_path_btn.clicked.connect(
            lambda: self.onNotebookBrowse(notebook_path_le))
        grid.addWidget(notebook_path_btn, 0, 2, 1, 1)
        gb.setLayout(grid)

        self.layout.addWidget(gb)

        gb = QGroupBox('Automaton mode')
        grid = QGridLayout()
        mode_cb = QComboBox()
        mode_cb.addItems(self.mode_options.keys())
        mode_cb.currentIndexChanged.connect(self.onChangeMode)
        self.config.add_handler('mode', mode_cb, mapper=self.mode_options)
        grid.addWidget(QLabel('Mode'), 0, 0)
        grid.addWidget(mode_cb, 0, 1)

        grid.addWidget(QLabel('Hold trigger'), 1, 0)
        fwatcher_hold_sb = QSpinBox()
        fwatcher_hold_sb.setRange(0, 60)
        fwatcher_hold_sb.setSuffix(' secs')
        self.config.add_handler('trigger_hold', fwatcher_hold_sb)
        grid.addWidget(fwatcher_hold_sb, 1, 1)
        gb.setLayout(grid)

        self.layout.addWidget(gb)

        self.watchfile_gb = QGroupBox('Watch files')
        grid = QGridLayout()

        watched_path_le = QLineEdit()
        grid.addWidget(watched_path_le, 0, 0, 1, 2)
        self.config.add_handler('watched_files',
                                watched_path_le,
                                mapper=(lambda x: x.split(";"),
                                        lambda x: ";".join(x)))

        watched_path_btn = QToolButton()
        watched_path_btn.setIcon(
            QIcon(os.path.join(utils.scriptdir, 'icons', 'document-copy.png')))
        watched_path_btn.setStatusTip('Add file(s)')
        watched_path_btn.clicked.connect(
            lambda: self.onFilesBrowse(watched_path_le))
        grid.addWidget(watched_path_btn, 0, 2, 1, 1)

        grid.addWidget(QLabel('Watch window'), 1, 0)
        watch_window_sb = QSpinBox()
        watch_window_sb.setRange(0, 60)
        watch_window_sb.setSuffix(' secs')
        self.config.add_handler('watch_window', watch_window_sb)
        grid.addWidget(watch_window_sb, 1, 1)

        self.watchfile_gb.setLayout(grid)
        self.layout.addWidget(self.watchfile_gb)

        self.watchfolder_gb = QGroupBox('Watch folder')
        grid = QGridLayout()

        watched_path_le = QLineEdit()
        grid.addWidget(watched_path_le, 0, 0, 1, 3)
        self.config.add_handler('watched_folder', watched_path_le)

        watched_path_btn = QToolButton()
        watched_path_btn.setIcon(
            QIcon(
                os.path.join(utils.scriptdir, 'icons',
                             'folder-horizontal-open.png')))
        watched_path_btn.setStatusTip('Add folder')
        watched_path_btn.clicked.connect(
            lambda: self.onFolderBrowse(watched_path_le))
        grid.addWidget(watched_path_btn, 0, 3, 1, 1)

        grid.addWidget(QLabel('Iterate files in folder'), 3, 0)
        loop_folder_sb = QCheckBox()
        self.config.add_handler('iterate_watched_folder', loop_folder_sb)
        grid.addWidget(loop_folder_sb, 3, 1)

        loop_wildcard_le = QLineEdit()
        self.config.add_handler('iterate_wildcard', loop_wildcard_le)
        grid.addWidget(loop_wildcard_le, 3, 2)

        self.watchfolder_gb.setLayout(grid)
        self.layout.addWidget(self.watchfolder_gb)

        self.timer_gb = QGroupBox('Timer')
        grid = QGridLayout()

        grid.addWidget(QLabel('Run every'), 0, 0)
        watch_timer_sb = QSpinBox()
        watch_timer_sb.setRange(0, 60)
        watch_timer_sb.setSuffix(' secs')
        self.config.add_handler('timer_seconds', watch_timer_sb)
        grid.addWidget(watch_timer_sb, 0, 1)

        self.timer_gb.setLayout(grid)
        self.layout.addWidget(self.timer_gb)

        self.manual_gb = QGroupBox('Manual')  # No show
        grid = QGridLayout()
        grid.addWidget(QLabel('No configuration'), 0, 0)
        self.manual_gb.setLayout(grid)
        self.layout.addWidget(self.manual_gb)

        gb = QGroupBox('Output')
        grid = QGridLayout()
        output_path_le = QLineEdit()
        self.config.add_handler('output_path', output_path_le)
        grid.addWidget(output_path_le, 0, 0, 1, 2)

        notebook_path_btn = QToolButton()
        notebook_path_btn.setIcon(
            QIcon(
                os.path.join(utils.scriptdir, 'icons',
                             'folder-horizontal-open.png')))
        notebook_path_btn.clicked.connect(
            lambda: self.onFolderBrowse(notebook_path_le))
        grid.addWidget(notebook_path_btn, 0, 2, 1, 1)

        export_cb = QComboBox()
        export_cb.addItems(IPyexporter_map.keys())
        self.config.add_handler('output_format', export_cb)
        grid.addWidget(QLabel('Notebook output format'), 1, 0)
        grid.addWidget(export_cb, 1, 1)

        gb.setLayout(grid)

        self.layout.addWidget(gb)

        self.layout.addStretch()
        self.finalise()

        self.onChangeMode(mode_cb.currentIndex())

    def onNotebookBrowse(self, t):
        global _w
        filenames, _ = QFileDialog.getOpenFileNames(
            _w, "Load IPython notebook(s)", '',
            "IPython Notebooks (*.ipynb);;All files (*.*)")
        if filenames:
            self.config.set('notebook_paths', filenames)

    def onFolderBrowse(self, t):
        global _w
        filename = QFileDialog.getExistingDirectory(_w,
                                                    "Select folder to watch")
        if filename:
            self.config.set('watched_folder', filename)

    def onFilesBrowse(self, t):
        global _w
        filenames, _ = QFileDialog.getOpenFileNames(_w,
                                                    "Select file(s) to watch")
        if filenames:
            self.config.set('watched_files', filenames)

    def onChangeMode(self, i):
        for m, gb in {
                MODE_MANUAL: self.manual_gb,
                MODE_WATCH_FILES: self.watchfile_gb,
                MODE_WATCH_FOLDER: self.watchfolder_gb,
                MODE_TIMER: self.timer_gb
        }.items():
            if m == list(self.mode_options.items())[i][1]:
                gb.show()
            else:
                gb.hide()

    def sizeHint(self):
        return QSize(400, 200)
Exemplo n.º 10
0
class AutomatonDialog(GenericDialog):

    mode_options = {
        'Manual': MODE_MANUAL,
        'Watch files': MODE_WATCH_FILES,
        'Watch folder': MODE_WATCH_FOLDER,
        'Timer': MODE_TIMER,
    }

    def __init__(self, parent, **kwargs):
        super(AutomatonDialog, self).__init__(parent, **kwargs)
        self.setWindowTitle("Edit Automaton")

        self.config = ConfigManager()

        gb = QGroupBox('IPython notebook(s) (*.ipynb)')
        grid = QGridLayout()
        notebook_path_le = QLineEdit()
        self.config.add_handler('notebook_paths', notebook_path_le, mapper=(lambda x: x.split(";"), lambda x: ";".join(x)))
        grid.addWidget(notebook_path_le, 0, 0, 1, 2)

        notebook_path_btn = QToolButton()
        notebook_path_btn.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'document-attribute-i.png')))
        notebook_path_btn.clicked.connect(lambda: self.onNotebookBrowse(notebook_path_le))
        grid.addWidget(notebook_path_btn, 0, 2, 1, 1)
        gb.setLayout(grid)

        self.layout.addWidget(gb)

        gb = QGroupBox('Automaton mode')
        grid = QGridLayout()
        mode_cb = QComboBox()
        mode_cb.addItems(self.mode_options.keys())
        mode_cb.currentIndexChanged.connect(self.onChangeMode)
        self.config.add_handler('mode', mode_cb, mapper=self.mode_options)
        grid.addWidget(QLabel('Mode'), 0, 0)
        grid.addWidget(mode_cb, 0, 1)

        grid.addWidget(QLabel('Hold trigger'), 1, 0)
        fwatcher_hold_sb = QSpinBox()
        fwatcher_hold_sb.setRange(0, 60)
        fwatcher_hold_sb.setSuffix(' secs')
        self.config.add_handler('trigger_hold', fwatcher_hold_sb)
        grid.addWidget(fwatcher_hold_sb, 1, 1)
        gb.setLayout(grid)

        self.layout.addWidget(gb)

        self.watchfile_gb = QGroupBox('Watch files')
        grid = QGridLayout()

        watched_path_le = QLineEdit()
        grid.addWidget(watched_path_le, 0, 0, 1, 2)
        self.config.add_handler('watched_files', watched_path_le, mapper=(lambda x: x.split(";"), lambda x: ";".join(x)))

        watched_path_btn = QToolButton()
        watched_path_btn.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'document-copy.png')))
        watched_path_btn.setStatusTip('Add file(s)')
        watched_path_btn.clicked.connect(lambda: self.onFilesBrowse(watched_path_le))
        grid.addWidget(watched_path_btn, 0, 2, 1, 1)

        grid.addWidget(QLabel('Watch window'), 1, 0)
        watch_window_sb = QSpinBox()
        watch_window_sb.setRange(0, 60)
        watch_window_sb.setSuffix(' secs')
        self.config.add_handler('watch_window', watch_window_sb)
        grid.addWidget(watch_window_sb, 1, 1)

        self.watchfile_gb.setLayout(grid)
        self.layout.addWidget(self.watchfile_gb)

        self.watchfolder_gb = QGroupBox('Watch folder')
        grid = QGridLayout()

        watched_path_le = QLineEdit()
        grid.addWidget(watched_path_le, 0, 0, 1, 3)
        self.config.add_handler('watched_folder', watched_path_le)

        watched_path_btn = QToolButton()
        watched_path_btn.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'folder-horizontal-open.png')))
        watched_path_btn.setStatusTip('Add folder')
        watched_path_btn.clicked.connect(lambda: self.onFolderBrowse(watched_path_le))
        grid.addWidget(watched_path_btn, 0, 3, 1, 1)

        grid.addWidget(QLabel('Iterate files in folder'), 3, 0)
        loop_folder_sb = QCheckBox()
        self.config.add_handler('iterate_watched_folder', loop_folder_sb)
        grid.addWidget(loop_folder_sb, 3, 1)

        loop_wildcard_le = QLineEdit()
        self.config.add_handler('iterate_wildcard', loop_wildcard_le)
        grid.addWidget(loop_wildcard_le, 3, 2)

        self.watchfolder_gb.setLayout(grid)
        self.layout.addWidget(self.watchfolder_gb)

        self.timer_gb = QGroupBox('Timer')
        grid = QGridLayout()

        grid.addWidget(QLabel('Run every'), 0, 0)
        watch_timer_sb = QSpinBox()
        watch_timer_sb.setRange(0, 60)
        watch_timer_sb.setSuffix(' secs')
        self.config.add_handler('timer_seconds', watch_timer_sb)
        grid.addWidget(watch_timer_sb, 0, 1)

        self.timer_gb.setLayout(grid)
        self.layout.addWidget(self.timer_gb)

        self.manual_gb = QGroupBox('Manual')  # No show
        grid = QGridLayout()
        grid.addWidget(QLabel('No configuration'), 0, 0)
        self.manual_gb.setLayout(grid)
        self.layout.addWidget(self.manual_gb)

        gb = QGroupBox('Output')
        grid = QGridLayout()
        output_path_le = QLineEdit()
        self.config.add_handler('output_path', output_path_le)
        grid.addWidget(output_path_le, 0, 0, 1, 2)

        notebook_path_btn = QToolButton()
        notebook_path_btn.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'folder-horizontal-open.png')))
        notebook_path_btn.clicked.connect(lambda: self.onFolderBrowse(notebook_path_le))
        grid.addWidget(notebook_path_btn, 0, 2, 1, 1)

        export_cb = QComboBox()
        export_cb.addItems(IPyexporter_map.keys())
        self.config.add_handler('output_format', export_cb)
        grid.addWidget(QLabel('Notebook output format'), 1, 0)
        grid.addWidget(export_cb, 1, 1)

        gb.setLayout(grid)

        self.layout.addWidget(gb)

        self.layout.addStretch()
        self.finalise()

        self.onChangeMode(mode_cb.currentIndex())

    def onNotebookBrowse(self, t):
        global _w
        filenames, _ = QFileDialog.getOpenFileNames(_w, "Load IPython notebook(s)", '', "IPython Notebooks (*.ipynb);;All files (*.*)")
        if filenames:
            self.config.set('notebook_paths', filenames)

    def onFolderBrowse(self, t):
        global _w
        filename = QFileDialog.getExistingDirectory(_w, "Select folder to watch")
        if filename:
            self.config.set('watched_folder', filename)

    def onFilesBrowse(self, t):
        global _w
        filenames, _ = QFileDialog.getOpenFileNames(_w, "Select file(s) to watch")
        if filenames:
            self.config.set('watched_files', filenames)
        
    def onChangeMode(self, i):
        for m, gb in {MODE_MANUAL: self.manual_gb, MODE_WATCH_FILES: self.watchfile_gb, MODE_WATCH_FOLDER: self.watchfolder_gb, MODE_TIMER: self.timer_gb}.items():
            if m == list(self.mode_options.items())[i][1]:
                gb.show()
            else:
                gb.hide()
        
    def sizeHint(self):
        return QSize(400, 200)
Exemplo n.º 11
0
Arquivo: ui.py Projeto: mfitzp/nmrbrew
class AnnotatePeaks(GenericDialog):

    def __init__(self, parent, config=None, *args, **kwargs):
        super(AnnotatePeaks, self).__init__(parent, *args, **kwargs)

        self.setWindowTitle('Annotate Peaks')

        if config:
            # Copy in starting state
            self.config = ConfigManager()
            self.config.hooks.update(custom_pyqtconfig_hooks.items())

            self.config.set_defaults(config)

        self.fwd_map_cache = {}

        # Correlation variables
        gb = QGroupBox('Peaks')
        vbox = QVBoxLayout()
        # Populate the list boxes
        self.lw_peaks = QListWidgetAddRemove()
        self.lw_peaks.setSelectionMode(QAbstractItemView.ExtendedSelection)
        vbox.addWidget(self.lw_peaks)

        vboxh = QHBoxLayout()

        self.add_label = QLineEdit()
        self.add_start = QDoubleSpinBox()
        self.add_start.setRange(-1, 12)
        self.add_start.setDecimals(3)
        self.add_start.setSuffix('ppm')
        self.add_start.setSingleStep(0.001)

        self.add_end = QDoubleSpinBox()
        self.add_end.setRange(-1, 12)
        self.add_end.setDecimals(3)
        self.add_end.setSuffix('ppm')
        self.add_end.setSingleStep(0.001)

        addc = QPushButton('Add')
        addc.clicked.connect(self.onPeakAdd)

        remc = QPushButton('Remove selected')
        remc.clicked.connect(self.onPeakAdd)

        loadc = QPushButton("Import from file")
        loadc.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'folder-open-document.png')))
        loadc.clicked.connect(self.onPeakImport)


        metabh = QPushButton("Auto match via MetaboHunter")
        metabh.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'metabohunter.png')))
        metabh.clicked.connect(self.onPeakImportMetabohunter)

        vboxh.addWidget(self.add_label)
        vboxh.addWidget(self.add_start)
        vboxh.addWidget(self.add_end)

        vboxh.addWidget(addc)

        vbox.addWidget(remc)
        vbox.addLayout(vboxh)
        vbox.addWidget(loadc)
        vbox.addWidget(metabh)

        gb.setLayout(vbox)

        self.layout.addWidget(gb)

        self.config.add_handler('annotation/peaks', self.lw_peaks, (self.map_list_fwd, self.map_list_rev))

        self.dialogFinalise()

    def onPeakAdd(self):
        c = self.config.get('annotation/peaks')[:]  # Create new list to force refresh on reassign
        c.append( (self.add_label.text(), float(self.add_start.value()), float(self.add_end.value()) ) )
        self.config.set('annotation/peaks', c)

    def onPeakRemove(self):
        i = self.lw_peaks.removeItemAt(self.lw_peaks.currentRow())
        # c = self.map_list_fwd(i.text())

    def onPeakImport(self):
        filename, _ = QFileDialog.getOpenFileName(self.parent(), 'Load peak annotations from file', '', "All compatible files (*.csv *.txt *.tsv);;Comma Separated Values (*.csv);;Plain Text Files (*.txt);;Tab Separated Values (*.tsv);;All files (*.*)")
        if filename:
            c = self.config.get('annotation/peaks')[:]  # Create new list to force refresh on reassign

            with open(filename, 'rU') as f:
                reader = csv.reader(f, delimiter=b',', dialect='excel')
                for row in reader:
                    if row not in c:
                        c.append( row[0], float(row[1]), float(row[2]) )

            self.config.set('annotation/peaks', c)

    def onPeakImportMetabohunter(self):
        c = self.config.get('annotation/peaks')[:]  # Create new list to force refresh on reassign
        t = self.parent().current_tool

        dlg = MetaboHunter(self)
        if dlg.exec_():

            if 'spc' in t.data:
                # We have a spectra; calcuate mean; reduce size if required
                spc = t.data['spc']
                n = spc.data.shape[1]
                ppm = spc.ppm
                spcd = np.mean(spc.data, axis=0)

                # Set a hard limit on the size of data we submit to be nice.
                if n > 3000:
                    # Calculate the division required to be under the limit
                    d = np.ceil(float(n)/3000)
                    # Trim axis to multiple of divisor
                    trim = (n//d)*d
                    spcd = spcd[:trim]
                    ppm = ppm[:trim]
                    # Mean d shape
                    spcd = np.mean( spcd.reshape(-1,d), axis=1)
                    ppm = np.mean( ppm.reshape(-1,d), axis=1)

            # Submit with settings
            hmdbs = metabohunter.request(ppm, spcd,
                  metabotype=dlg.config.get('Metabotype'),
                  database=dlg.config.get('Database Source'),
                  ph=dlg.config.get('Sample pH'),
                  solvent=dlg.config.get('Solvent'),
                  frequency=dlg.config.get('Frequency'),
                  method=dlg.config.get('Method'),
                  noise=dlg.config.get('Noise Threshold'),
                  confidence=dlg.config.get('Confidence Threshold'),
                  tolerance=dlg.config.get('Tolerance')
            )

            ha = np.array(hmdbs)
            unique_hmdbs = set(hmdbs)
            if None in unique_hmdbs:
                unique_hmdbs.remove(None)

            # Extract out regions
            for hmdb in unique_hmdbs:
                hb = np.diff(ha == hmdb)

                # These are needed to ensure markers are there for objects starting and ending on array edge
                if ha[0] == hmdb:
                    hb[0] == True

                if ha[-1] == hmdb:
                    hb[-1] == True

                idx = np.nonzero(hb)[0]
                idx = idx.reshape(-1,2)

                if dlg.config.get('convert_hmdb_ids_to_names') and hmdb in METABOHUNTER_HMDB_NAME_MAP.keys():
                    label = METABOHUNTER_HMDB_NAME_MAP[hmdb]
                else:
                    label = hmdb

                # Now we have an array of all start, stop positions for this item
                for start, stop in idx:
                    c.append( (label, ppm[start], ppm[stop]) )


        self.config.set('annotation/peaks', c)

    def map_list_fwd(self, s):
        " Receive text name, return the indexes "
        return self.fwd_map_cache[s]

    def map_list_rev(self, x):
        " Receive the indexes, return the label"
        s = "%s\t%.2f\t%.2f" % tuple(x)
        self.fwd_map_cache[s] = x
        return s
Exemplo n.º 12
0
Arquivo: ui.py Projeto: mfitzp/nmrbrew
class AnnotateClasses(GenericDialog):

    def __init__(self, parent, config=None, *args, **kwargs):
        super(AnnotateClasses, self).__init__(parent, *args, **kwargs)

        self.setWindowTitle('Annotate Classes')

        if config:
            # Copy in starting state
            self.config = ConfigManager()
            self.config.hooks.update(custom_pyqtconfig_hooks.items())

            self.config.set_defaults(config)

        self.fwd_map_cache = {}

        # Correlation variables
        gb = QGroupBox('Sample classes')
        vbox = QVBoxLayout()
        # Populate the list boxes
        self.lw_classes = QListWidgetAddRemove()
        self.lw_classes.setSelectionMode(QAbstractItemView.ExtendedSelection)
        vbox.addWidget(self.lw_classes)

        vboxh = QHBoxLayout()

        self.add_label = QLineEdit()
        self.add_class = QLineEdit()

        addc = QPushButton('Add')
        addc.clicked.connect(self.onClassAdd)

        remc = QPushButton('Remove selected')
        remc.clicked.connect(self.onClassAdd)

        loadc = QPushButton('Import from file')
        loadc.setIcon(QIcon(os.path.join(utils.scriptdir, 'icons', 'folder-open-document.png')))
        loadc.clicked.connect(self.onClassImport)

        vboxh.addWidget(self.add_label)
        vboxh.addWidget(self.add_class)
        vboxh.addWidget(addc)

        vbox.addWidget(remc)
        vbox.addLayout(vboxh)
        vboxh.addWidget(loadc)

        gb.setLayout(vbox)

        self.layout.addWidget(gb)

        self.config.add_handler('annotation/sample_classes', self.lw_classes, (self.map_list_fwd, self.map_list_rev))

        self.dialogFinalise()

    def onClassAdd(self):
        c = self.config.get('annotation/sample_classes')[:]  # Create new list to force refresh on reassign
        c.append( (self.add_label.text(), self.add_class.text()) )
        self.config.set('annotation/sample_classes', c)

    def onClassRemove(self):
        i = self.lw_classes.removeItemAt(self.lw_classes.currentRow())
        # c = self.map_list_fwd(i.text())

    def onClassImport(self):
        filename, _ = QFileDialog.getOpenFileName(self.parent(), 'Load classifications from file', '', "All compatible files (*.csv *.txt *.tsv);;Comma Separated Values (*.csv);;Plain Text Files (*.txt);;Tab Separated Values (*.tsv);;All files (*.*)")
        if filename:
            c = self.config.get('annotation/sample_classes')[:]  # Create new list to force refresh on reassign

            with open(filename, 'rU') as f:
                reader = csv.reader(f, delimiter=b',', dialect='excel')
                for row in reader:
                    if row not in c:
                        c.append(row[:2])

            self.config.set('annotation/sample_classes', c)

    def map_list_fwd(self, s):
        " Receive text name, return the indexes "
        return self.fwd_map_cache[s]

    def map_list_rev(self, x):
        " Receive the indexes, return the label"
        s = "%s\t%s" % tuple(x)
        self.fwd_map_cache[s] = x
        return s
Exemplo n.º 13
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 = 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