Example #1
0
    def contextMenuEvent(self, event):

        def correctWord(cursor, word):
            # From QTextCursor doc:
            # if there is a selection, the selection is deleted and replaced
            return lambda: cursor.insertText(word)

        popup_menu = self.createStandardContextMenu()

        # Spellcheck the word under mouse cursor, not self.textCursor
        cursor = self.cursorForPosition(event.pos())
        cursor.select(QTextCursor.WordUnderCursor)

        text = cursor.selectedText()
        if self.speller and text:
            if not self.speller.check(text):
                lastAction = popup_menu.actions()[0]
                for word in self.speller.suggest(text)[:10]:
                    action = QAction(word, popup_menu)
                    action.triggered.connect(correctWord(cursor, word))
                    action.setFont(QFont("sans", weight=QFont.Bold))
                    popup_menu.insertAction(lastAction, action)
                popup_menu.insertSeparator(lastAction)

        popup_menu.exec_(event.globalPos())
Example #2
0
    def contextMenuEvent(self, event):
        def correctWord(cursor, word):
            # From QTextCursor doc:
            # if there is a selection, the selection is deleted and replaced
            return lambda: cursor.insertText(word)

        popup_menu = self.createStandardContextMenu()
        paste_action = popup_menu.actions()[6]
        paste_formatted_action = QAction(self.tr("Paste raw HTML"), popup_menu)
        paste_formatted_action.triggered.connect(self.insertFromRawHtml)
        paste_formatted_action.setShortcut(QKeySequence("Ctrl+Shift+V"))
        popup_menu.insertAction(paste_action, paste_formatted_action)

        # Spellcheck the word under mouse cursor, not self.textCursor
        cursor = self.cursorForPosition(event.pos())
        cursor.select(QTextCursor.WordUnderCursor)

        text = cursor.selectedText()
        if self.speller and text:
            if not self.speller.check(text):
                lastAction = popup_menu.actions()[0]
                for word in self.speller.suggest(text)[:10]:
                    action = QAction(word, popup_menu)
                    action.triggered.connect(correctWord(cursor, word))
                    action.setFont(QFont(None, weight=QFont.Bold))
                    popup_menu.insertAction(lastAction, action)
                popup_menu.insertSeparator(lastAction)

        popup_menu.exec_(event.globalPos())
Example #3
0
    def contextMenuEvent(self, event):

        def correctWord(cursor, word):
            # From QTextCursor doc:
            # if there is a selection, the selection is deleted and replaced
            return lambda: cursor.insertText(word)

        popup_menu = self.createStandardContextMenu()
        paste_action = popup_menu.actions()[6]
        paste_formatted_action = QAction(self.tr("Paste raw HTML"), popup_menu)
        paste_formatted_action.triggered.connect(self.insertFromRawHtml)
        paste_formatted_action.setShortcut(QKeySequence("Ctrl+Shift+V"))
        popup_menu.insertAction(paste_action, paste_formatted_action)

        # Spellcheck the word under mouse cursor, not self.textCursor
        cursor = self.cursorForPosition(event.pos())
        cursor.select(QTextCursor.WordUnderCursor)

        text = cursor.selectedText()
        if self.speller and text:
            if not self.speller.check(text):
                lastAction = popup_menu.actions()[0]
                for word in self.speller.suggest(text)[:10]:
                    action = QAction(word, popup_menu)
                    action.triggered.connect(correctWord(cursor, word))
                    action.setFont(QFont(None, weight=QFont.Bold))
                    popup_menu.insertAction(lastAction, action)
                popup_menu.insertSeparator(lastAction)

        popup_menu.exec_(event.globalPos())
Example #4
0
def createAction(self, text, slot, icon=None, signal='triggered()'):
    action = QAction(text, self)
    action.setFont(defaultFont)
    if icon:
        action.setIcon(icon)
    self.connect(action, SIGNAL(signal), slot)
    return action
Example #5
0
def menuTitle(icon, text, parent):
    eventEater = EventEater()
    buttonaction = QAction(parent)
    font = buttonaction.font()
    font.setBold(True)
    buttonaction.setFont(font)
    buttonaction.setText(text)
    buttonaction.setIcon(icon)

    action = QWidgetAction(parent)
    action.setObjectName('trayMenuTitle')
    titleButton = QToolButton(parent)
    titleButton.installEventFilter(eventEater)
    titleButton.setDefaultAction(buttonaction)
    titleButton.setDown(True)
    titleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
    action.setDefaultWidget(titleButton)

    return action
Example #6
0
def menuTitle(icon, text, parent):
    eventEater = EventEater()
    buttonaction = QAction(parent)
    font = buttonaction.font()
    font.setBold(True)
    buttonaction.setFont(font)
    buttonaction.setText(text)
    buttonaction.setIcon(icon)

    action = QWidgetAction(parent)
    action.setObjectName('trayMenuTitle')
    titleButton = QToolButton(parent)
    titleButton.installEventFilter(eventEater)
    titleButton.setDefaultAction(buttonaction)
    titleButton.setDown(True)
    titleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
    action.setDefaultWidget(titleButton)

    return action
Example #7
0
    def contextMenuEvent(self, event):
        def correctWord(cursor, word):
            # From QTextCursor doc:
            # if there is a selection, the selection is deleted and replaced
            return lambda: cursor.insertText(word)

        popup_menu = self.createStandardContextMenu()

        # Spellcheck the word under mouse cursor, not self.textCursor
        cursor = self.cursorForPosition(event.pos())
        cursor.select(QTextCursor.WordUnderCursor)

        text = cursor.selectedText()
        if self.speller and text:
            if not self.speller.check(text):
                lastAction = popup_menu.actions()[0]
                for word in self.speller.suggest(text)[:10]:
                    action = QAction(word, popup_menu)
                    action.triggered.connect(correctWord(cursor, word))
                    action.setFont(QFont("sans", weight=QFont.Bold))
                    popup_menu.insertAction(lastAction, action)
                popup_menu.insertSeparator(lastAction)

        popup_menu.exec_(event.globalPos())
Example #8
0
class SimpleRichText(QTextEdit):
    def __init__(self, focusin, focusout):
        QTextEdit.__init__(self)        
        self.focusin = focusin
        self.focusout = focusout
        self.createActions()

        #self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)

    def focusOutEvent ( self, event ):
        #print "focus out"
        self.focusout()

    def focusInEvent ( self, event ):        
        self.focusin()


    def closeEvent(self, event):
        event.accept()        

    def createActions(self):
        self.boldAct = QAction(self.tr("&Bold"), self)
        self.boldAct.setCheckable(True)
        self.boldAct.setShortcut(self.tr("Ctrl+B"))
        self.boldAct.setStatusTip(self.tr("Make the text bold"))
        self.connect(self.boldAct, SIGNAL("triggered()"), self.setBold)
        self.addAction(self.boldAct)

        boldFont = self.boldAct.font()
        boldFont.setBold(True)
        self.boldAct.setFont(boldFont)

        self.italicAct = QAction(self.tr("&Italic"), self)
        self.italicAct.setCheckable(True)
        self.italicAct.setShortcut(self.tr("Ctrl+I"))
        self.italicAct.setStatusTip(self.tr("Make the text italic"))
        self.connect(self.italicAct, SIGNAL("triggered()"), self.setItalic)
        self.addAction(self.italicAct)

    def setBold(self):
        format = QTextCharFormat()
        if self.boldAct.isChecked():
                weight = QFont.Bold
        else:
                weight = QFont.Normal
        format.setFontWeight(weight)
        self.setFormat(format)

    def setItalic(self):
        format = QTextCharFormat()
        #format.setFontItalic(self.__italic.isChecked())
        format.setFontItalic(self.italicAct.isChecked())
        self.setFormat(format)

    def setUnderline(self):
        format = QTextCharFormat()
        format.setFontUnderline(self.__underline.isChecked())
        self.setFormat(format)

    def setFormat(self, format):
        self.textCursor().mergeCharFormat(format)
        self.mergeCurrentCharFormat(format)

    def bold(self):
        print("bold")

    def italic(self):
        print("italic")
Example #9
0
class SimpleRichText(QTextEdit):
    def __init__(self, focusin, focusout):
        QTextEdit.__init__(self)
        self.focusin = focusin
        self.focusout = focusout
        self.createActions()

        #self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)

    def focusOutEvent ( self, event ):
        #print "focus out"
        self.focusout()

    def focusInEvent ( self, event ):
        self.focusin()


    def closeEvent(self, event):
        event.accept()

    def createActions(self):
        self.boldAct = QAction(self.tr("&Bold"), self)
        self.boldAct.setCheckable(True)
        self.boldAct.setShortcut(self.tr("Ctrl+B"))
        self.boldAct.setStatusTip(self.tr("Make the text bold"))
        self.connect(self.boldAct, SIGNAL("triggered()"), self.setBold)
        self.addAction(self.boldAct)

        boldFont = self.boldAct.font()
        boldFont.setBold(True)
        self.boldAct.setFont(boldFont)

        self.italicAct = QAction(self.tr("&Italic"), self)
        self.italicAct.setCheckable(True)
        self.italicAct.setShortcut(self.tr("Ctrl+I"))
        self.italicAct.setStatusTip(self.tr("Make the text italic"))
        self.connect(self.italicAct, SIGNAL("triggered()"), self.setItalic)
        self.addAction(self.italicAct)

    def setBold(self):
        format = QTextCharFormat()
        if self.boldAct.isChecked():
            weight = QFont.Bold
        else:
            weight = QFont.Normal
        format.setFontWeight(weight)
        self.setFormat(format)

    def setItalic(self):
        format = QTextCharFormat()
        #format.setFontItalic(self.__italic.isChecked())
        format.setFontItalic(self.italicAct.isChecked())
        self.setFormat(format)

    def setUnderline(self):
        format = QTextCharFormat()
        format.setFontUnderline(self.__underline.isChecked())
        self.setFormat(format)

    def setFormat(self, format):
        self.textCursor().mergeCharFormat(format)
        self.mergeCurrentCharFormat(format)

    def bold(self):
        print("bold")

    def italic(self):
        print("italic")
Example #10
0
class Viewer(QMainWindow):
    """High-level API to view multi-dimensional arrays.

    Properties:
        title -- window title

    """
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        uiDirectory = os.path.split(volumina.__file__)[0]
        if uiDirectory == '':
            uiDirectory = '.'
        loadUi(uiDirectory + '/viewer.ui', self)

        self._dataShape = None
        self._viewerInitialized = False
        self.editor = None
        self.viewingWidget = None
        self.actionQuit.triggered.connect(qApp.quit)

        #when connecting in renderScreenshot to a partial(...) function,
        #we need to remember the created function to be able to disconnect
        #to it later
        self._renderScreenshotDisconnect = None

        self.initLayerstackModel()

        self.actionCurrentView = QAction(QIcon(), "Only for selected view",
                                         self.menuView)
        f = self.actionCurrentView.font()
        f.setBold(True)
        self.actionCurrentView.setFont(f)

        # Lazy import here to prevent this module from ignoring volumine.NO3D flag.
        from volumina.volumeEditor import VolumeEditor
        self.editor = VolumeEditor(self.layerstack, parent=self)

        #make sure the layer stack widget, which is the right widget
        #managed by the splitter self.splitter shows up correctly
        #TODO: find a proper way of doing this within the designer
        def adjustSplitter():
            s = self.splitter.sizes()
            s = [int(0.66 * s[0]), s[0] - int(0.66 * s[0])]
            self.splitter.setSizes(s)

        QTimer.singleShot(0, adjustSplitter)

    @property
    def title(self):
        return self.windowTitle()

    @title.setter
    def title(self, t):
        self.setWindowTitle(t)

    def initLayerstackModel(self):
        self.layerstack = LayerStackModel()
        self.layerWidget.init(self.layerstack)
        model = self.layerstack
        self.UpButton.clicked.connect(model.moveSelectedUp)
        model.canMoveSelectedUp.connect(self.UpButton.setEnabled)
        self.DownButton.clicked.connect(model.moveSelectedDown)
        model.canMoveSelectedDown.connect(self.DownButton.setEnabled)
        self.DeleteButton.clicked.connect(model.deleteSelected)
        model.canDeleteSelected.connect(self.DeleteButton.setEnabled)

    @property
    def dataShape(self):
        return self._dataShape

    @dataShape.setter
    def dataShape(self, s):
        if s is None:
            return
        assert len(s) == 5

        self._dataShape = s
        self.editor.dataShape = s
        if not self._viewerInitialized:
            self._viewerInitialized = True
            self.viewer.init(self.editor)
            #make sure the data shape is correctly set
            #(some signal/slot connections may be set up in the above init)
            self.editor.dataShape = s

            #FIXME: this code is broken
            #if its 2D, maximize the corresponding window
            #if len([i for i in list(self.dataShape)[1:4] if i == 1]) == 1:
            #    viewAxis = [i for i in range(1,4) if self.dataShape[i] == 1][0] - 1
            #    self.viewer.quadview.switchMinMax(viewAxis)

    def addGrayscaleLayer(self, a, name=None, direct=False):
        source, self.dataShape = createDataSource(a, True)
        layer = GrayscaleLayer(source, direct=direct)
        layer.numberOfChannels = self.dataShape[-1]
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer

    def addAlphaModulatedLayer(self, a, name=None):
        source, self.dataShape = createDataSource(a, True)
        layer = AlphaModulatedLayer(source)
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer

    def addRGBALayer(self, a, name=None):
        assert a.shape[2] >= 3
        sources = [None, None, None, None]
        for i in range(3):
            sources[i], self.dataShape = createDataSource(a[..., i], True)
        if (a.shape[-1] >= 4):
            sources[3], self.dataShape = createDataSource(a[..., 3], True)
        layer = RGBALayer(sources[0], sources[1], sources[2], sources[3])
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer

    def addRandomColorsLayer(self, a, name=None, direct=False):
        layer = self.addColorTableLayer(a,
                                        name,
                                        colortable=None,
                                        direct=direct)
        layer.colortableIsRandom = True
        layer.zeroIsTransparent = True
        return layer

    def addColorTableLayer(self,
                           a,
                           name=None,
                           colortable=None,
                           direct=False,
                           clickFunctor=None):
        if colortable is None:
            colortable = self._randomColors()
        source, self.dataShape = createDataSource(a, True)
        if clickFunctor is None:
            layer = ColortableLayer(source, colortable, direct=direct)
        else:
            layer = ClickableColortableLayer(self.editor,
                                             clickFunctor,
                                             source,
                                             colortable,
                                             direct=direct)
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer

    def addRelabelingColorTableLayer(self,
                                     a,
                                     name=None,
                                     relabeling=None,
                                     colortable=None,
                                     direct=False,
                                     clickFunctor=None,
                                     right=True):
        if colortable is None:
            colortable = self._randomColors()
        source = RelabelingArraySource(a)
        if relabeling is None:
            source.setRelabeling(numpy.zeros(numpy.max(a) + 1, dtype=a.dtype))
        else:
            source.setRelabeling(relabeling)
        if colortable is None:
            colortable = [QColor(0, 0, 0, 0).rgba(), QColor(255, 0, 0).rgba()]
        if clickFunctor is None:
            layer = ColortableLayer(source, colortable, direct=direct)
        else:
            layer = ClickableColortableLayer(self.editor,
                                             clickFunctor,
                                             source,
                                             colortable,
                                             direct=direct,
                                             right=right)
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return (layer, source)

    def addClickableSegmentationLayer(self,
                                      a,
                                      name=None,
                                      direct=False,
                                      colortable=None,
                                      reuseColors=True):
        return ClickableSegmentationLayer(a,
                                          self,
                                          name=name,
                                          direct=direct,
                                          colortable=colortable,
                                          reuseColors=reuseColors)

    def _randomColors(self, M=256):
        """Generates a pleasing color table with M entries."""

        colors = []
        for i in range(M):
            if i == 0:
                colors.append(QColor(0, 0, 0, 0).rgba())
            else:
                h, s, v = random.random(), random.random(), 1.0
                color = numpy.asarray(colorsys.hsv_to_rgb(h, s, v)) * 255
                qColor = QColor(*color)
                colors.append(qColor.rgba())
        #for the first 16 objects, use some colors that are easily distinguishable
        colors[1:17] = colortables.default16
        return colors
Example #11
0
class Viewer(QMainWindow):
    """High-level API to view multi-dimensional arrays.

    Properties:
        title -- window title

    """
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        uiDirectory = os.path.split(volumina.__file__)[0]
        if uiDirectory == '':
            uiDirectory = '.'
        loadUi(uiDirectory + '/viewer.ui', self)

        self._dataShape = None
        self._viewerInitialized = False
        self.editor = None
        self.viewingWidget = None
        self.actionQuit.triggered.connect(qApp.quit)
        
        #when connecting in renderScreenshot to a partial(...) function,
        #we need to remember the created function to be able to disconnect
        #to it later
        self._renderScreenshotDisconnect = None

        self.initLayerstackModel()

        self.actionCurrentView = QAction(QIcon(), "Only for selected view", self.menuView)
        f = self.actionCurrentView.font()
        f.setBold(True)
        self.actionCurrentView.setFont(f)

        # Lazy import here to prevent this module from ignoring volumine.NO3D flag.
        from volumina.volumeEditor import VolumeEditor
        self.editor = VolumeEditor(self.layerstack, parent=self)

        #make sure the layer stack widget, which is the right widget
        #managed by the splitter self.splitter shows up correctly
        #TODO: find a proper way of doing this within the designer
        def adjustSplitter():
            s = self.splitter.sizes()
            s = [int(0.66*s[0]), s[0]-int(0.66*s[0])]
            self.splitter.setSizes(s)
        QTimer.singleShot(0, adjustSplitter)
        
    @property
    def title(self):
        return self.windowTitle()
    
    @title.setter
    def title(self, t):
        self.setWindowTitle(t)
        
    def initLayerstackModel(self):
        self.layerstack = LayerStackModel()
        self.layerWidget.init(self.layerstack)
        model = self.layerstack
        self.UpButton.clicked.connect(model.moveSelectedUp)
        model.canMoveSelectedUp.connect(self.UpButton.setEnabled)
        self.DownButton.clicked.connect(model.moveSelectedDown)
        model.canMoveSelectedDown.connect(self.DownButton.setEnabled)
        self.DeleteButton.clicked.connect(model.deleteSelected)
        model.canDeleteSelected.connect(self.DeleteButton.setEnabled)
    
    @property
    def dataShape(self):
        return self._dataShape
    @dataShape.setter
    def dataShape(self, s):
        if s is None:
            return
        assert len(s) == 5
        
        self._dataShape = s
        self.editor.dataShape = s
        if not self._viewerInitialized:
            self._viewerInitialized = True
            self.viewer.init(self.editor)
            #make sure the data shape is correctly set
            #(some signal/slot connections may be set up in the above init)
            self.editor.dataShape = s

            #FIXME: this code is broken
            #if its 2D, maximize the corresponding window
            #if len([i for i in list(self.dataShape)[1:4] if i == 1]) == 1:
            #    viewAxis = [i for i in range(1,4) if self.dataShape[i] == 1][0] - 1
            #    self.viewer.quadview.switchMinMax(viewAxis)    
        
    def addGrayscaleLayer(self, a, name=None, direct=False):
        source,self.dataShape = createDataSource(a,True)
        layer = GrayscaleLayer(source, direct=direct)
        layer.numberOfChannels = self.dataShape[-1]
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer
        
    def addAlphaModulatedLayer(self, a, name=None, **kwargs):
        source,self.dataShape = createDataSource(a,True)
        layer = AlphaModulatedLayer(source, **kwargs)
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer
    
    def addRGBALayer(self, a, name=None):
        assert a.shape[2] >= 3
        sources = [None, None, None,None]
        for i in range(3):
            sources[i], self.dataShape = createDataSource(a[...,i], True)
        if(a.shape[-1] >= 4):
            sources[3], self.dataShape = createDataSource(a[...,3], True) 
        layer = RGBALayer(sources[0],sources[1],sources[2], sources[3])
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer

    def addRandomColorsLayer(self, a, name=None, direct=False):
        layer = self.addColorTableLayer(a, name, colortable=None, direct=direct)
        layer.colortableIsRandom = True
        layer.zeroIsTransparent = True
        return layer
    
    def addColorTableLayer(self, a, name=None, colortable=None, direct=False, clickFunctor=None):
        if colortable is None:
            colortable = self._randomColors()
        source,self.dataShape = createDataSource(a,True)
        if clickFunctor is None:
            layer = ColortableLayer(source, colortable, direct=direct)
        else:
            layer = ClickableColortableLayer(self.editor, clickFunctor, source, colortable, direct=direct)
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer
    
    def addRelabelingColorTableLayer(self, a, name=None, relabeling=None, colortable=None, direct=False, clickFunctor=None, right=True):
        if colortable is None:
            colortable = self._randomColors()
        source = RelabelingArraySource(a)
        if relabeling is None:
            source.setRelabeling(numpy.zeros(numpy.max(a)+1, dtype=a.dtype))
        else:
            source.setRelabeling(relabeling)
        if colortable is None:
            colortable = [QColor(0,0,0,0).rgba(), QColor(255,0,0).rgba()]
        if clickFunctor is None:
            layer = ColortableLayer(source, colortable, direct=direct)
        else:
            layer = ClickableColortableLayer(self.editor, clickFunctor, source, colortable, direct=direct, right=right)
        if name:
            layer.name = name 
        self.layerstack.append(layer)
        return (layer, source)
    
    def addClickableSegmentationLayer(self, a, name=None, direct=False, colortable=None, reuseColors=True):
        return ClickableSegmentationLayer(a, self, name=name, direct=direct, colortable=colortable, reuseColors=reuseColors) 
        
    def _randomColors(self, M=256):
        """Generates a pleasing color table with M entries."""

        colors = []
        for i in range(M):
            if i == 0:
                colors.append(QColor(0, 0, 0, 0).rgba())
            else:
                h, s, v = random.random(), random.random(), 1.0
                color = numpy.asarray(colorsys.hsv_to_rgb(h, s, v)) * 255
                qColor = QColor(*color)
                colors.append(qColor.rgba())
        #for the first 16 objects, use some colors that are easily distinguishable
        colors[1:17] = colortables.default16 
        return colors
Example #12
0
class Viewer(QMainWindow):
    """High-level API to view multi-dimensional arrays.

    Properties:
        title -- window title

    """
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        uiDirectory = os.path.split(volumina.__file__)[0]
        if uiDirectory == '':
            uiDirectory = '.'
        loadUi(uiDirectory + '/viewer.ui', self)

        self._dataShape = None
        self._viewerInitialized = False
        self.editor = None
        self.viewingWidget = None
        self.actionQuit.triggered.connect(qApp.quit)
        
        #when connecting in renderScreenshot to a partial(...) function,
        #we need to remember the created function to be able to disconnect
        #to it later
        self._renderScreenshotDisconnect = None

        self.initLayerstackModel()

        self.actionCurrentView = QAction(QIcon(), "Only for selected view", self.menuView)
        f = self.actionCurrentView.font()
        f.setBold(True)
        self.actionCurrentView.setFont(f)

        self.editor = VolumeEditor(self.layerstack)

        #make sure the layer stack widget, which is the right widget
        #managed by the splitter self.splitter shows up correctly
        #TODO: find a proper way of doing this within the designer
        def adjustSplitter():
            s = self.splitter.sizes()
            s = [int(0.66*s[0]), s[0]-int(0.66*s[0])]
            self.splitter.setSizes(s)
        QTimer.singleShot(0, adjustSplitter)
        
    def initLayerstackModel(self):
        self.layerstack = LayerStackModel()
        self.layerWidget.init(self.layerstack)
        model = self.layerstack
        self.UpButton.clicked.connect(model.moveSelectedUp)
        model.canMoveSelectedUp.connect(self.UpButton.setEnabled)
        self.DownButton.clicked.connect(model.moveSelectedDown)
        model.canMoveSelectedDown.connect(self.DownButton.setEnabled)
        self.DeleteButton.clicked.connect(model.deleteSelected)
        model.canDeleteSelected.connect(self.DeleteButton.setEnabled)
    
    @property
    def dataShape(self):
        return self._dataShape
    @dataShape.setter
    def dataShape(self, s):
        if s is None:
            return
        assert len(s) == 5
        
        self._dataShape = s
        self.editor.dataShape = s
        if not self._viewerInitialized:
            self._viewerInitialized = True
            self.viewer.init(self.editor)
            #make sure the data shape is correctly set
            #(some signal/slot connections may be set up in the above init)
            self.editor.dataShape = s

            #if its 2D, maximize the corresponding window
            if len([i for i in list(self.dataShape)[1:4] if i == 1]) == 1:
                viewAxis = [i for i in range(1,4) if self.dataShape[i] != 1][0] - 1
                self.viewer.quadview.switchMinMax(viewAxis)    
        
    def addGrayscaleLayer(self, a, name=None, direct=False):
        source,self.dataShape = createDataSource(a,True)
        layer = GrayscaleLayer(source, direct=direct)
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer
        
    def addAlphaModulatedLayer(self, a, name=None):
        source,self.dataShape = createDataSource(a,True)
        layer = AlphaModulatedLayer(source)
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer
    
    def addRGBALayer(self, a, name=None):
        source,self.dataShape = createDataSource(a,True)
        layer = RGBALayer(source[0],source[1],source[2])
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer

    def addRandomColorsLayer(self, a, name=None, direct=False):
        layer = self.addColorTableLayer(a, name, colortable=None, direct=direct)
        layer.colortableIsRandom = True
        layer.zeroIsTransparent = True
        return layer
    
    def addColorTableLayer(self, a, name=None, colortable=None, direct=False, clickFunctor=None):
        if colortable is None:
            colortable = self._randomColors()
        source,self.dataShape = createDataSource(a,True)
        if clickFunctor is None:
            layer = ColortableLayer(source, colortable, direct=direct)
        else:
            layer = ClickableColortableLayer(self.editor, clickFunctor, source, colortable, direct=direct)
        if name:
            layer.name = name
        self.layerstack.append(layer)
        return layer
    
    def addRelabelingColorTableLayer(self, a, name=None, relabeling=None, colortable=None, direct=False, clickFunctor=None):
        if colortable is None:
            colortable = self._randomColors()
        source = RelabelingArraySource(a)
        if relabeling is None:
            source.setRelabeling(numpy.zeros(numpy.max(a)+1, dtype=a.dtype))
        else:
            source.setRelabeling(relabeling)
        if colortable is None:
            colortable = [QColor(0,0,0,0).rgba(), QColor(255,0,0).rgba()]
        if clickFunctor is None:
            layer = ColortableLayer(source, colortable, direct=direct)
        else:
            layer = ClickableColortableLayer(self.editor, clickFunctor, source, colortable, direct=direct)
        if name:
            layer.name = name 
        self.layerstack.append(layer)
        return (layer, source)
    
    def addClickableSegmentationLayer(self, a, name=None, direct=False):
        M = a.max()
        clickedObjects = dict() #maps from object to the label that is used for it
        usedLabels = set()
        def onClick(layer, pos5D, pos):
            obj = layer.data.originalData[pos5D]
            if obj in clickedObjects:
                layer._datasources[0].setRelabelingEntry(obj, 0)
                usedLabels.remove( clickedObjects[obj] )
                del clickedObjects[obj]
            else:
                labels = sorted(list(usedLabels))
                
                #find first free entry
                if labels:
                    for l in range(1, labels[-1]+2):
                        if l not in labels:
                            break
                    assert l not in usedLabels
                else:
                    l = 1
               
                usedLabels.add(l) 
                clickedObjects[obj] = l
                layer._datasources[0].setRelabelingEntry(obj, l)
        
        colortable = volumina.layer.generateRandomColors(1000, "hsv", {"v": 1.0}, zeroIsTransparent=True)
             
        layer, source = self.addRelabelingColorTableLayer(a, clickFunctor=onClick, name=None,
            relabeling=numpy.zeros(M+1, dtype=a.dtype), colortable=colortable, direct=direct)
        if name is not None:
            layer.name = name
        layer.zeroIsTransparent = True
        layer.colortableIsRandom = True
        return layer 

    def _randomColors(self, M=256):
        """Generates a pleasing color table with M entries."""

        colors = []
        for i in range(M):
            if i == 0:
                colors.append(QColor(0, 0, 0, 0).rgba())
            else:
                h, s, v = random.random(), random.random(), 1.0
                color = numpy.asarray(colorsys.hsv_to_rgb(h, s, v)) * 255
                qColor = QColor(*color)
                colors.append(qColor.rgba())
        return colors
Example #13
0
class Viewer(QMainWindow):
    """High-level API to view multi-dimensional arrays.

    Properties:
        title -- window title

    """
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        uiDirectory = os.path.split(volumina.__file__)[0]
        if uiDirectory == '':
            uiDirectory = '.'
        loadUi(uiDirectory + '/viewer.ui', self)

        self._dataShape = None
        self.editor = None

        self.actionQuit.triggered.connect(qApp.quit)
        #when connecting in renderScreenshot to a partial(...) function,
        #we need to remember the created function to be able to disconnect
        #to it later
        self._renderScreenshotDisconnect = None

        self.initLayerstackModel()

        self.actionCurrentView = QAction(QIcon(), \
            "Only for selected view", self.menuView)
        f = self.actionCurrentView.font()
        f.setBold(True)
        self.actionCurrentView.setFont(f)

        #make sure the layer stack widget, which is the right widget
        #managed by the splitter self.splitter shows up correctly
        #TODO: find a proper way of doing this within the designer
        def adjustSplitter():
            s = self.splitter.sizes()
            s = [int(0.66*s[0]), s[0]-int(0.66*s[0])]
            self.splitter.setSizes(s)
        QTimer.singleShot(0, adjustSplitter)

    def initLayerstackModel(self):
        self.layerstack = LayerStackModel()
        self.layerWidget.init(self.layerstack)
        model = self.layerstack
        self.UpButton.clicked.connect(model.moveSelectedUp)
        model.canMoveSelectedUp.connect(self.UpButton.setEnabled)
        self.DownButton.clicked.connect(model.moveSelectedDown)
        model.canMoveSelectedDown.connect(self.DownButton.setEnabled)
        self.DeleteButton.clicked.connect(model.deleteSelected)
        model.canDeleteSelected.connect(self.DeleteButton.setEnabled)

    def renderScreenshot(self, axis, blowup=1, filename="/tmp/volumina_screenshot.png"):
        """Save the complete slice as shown by the slice view 'axis'
        in the GUI as an image
        
        axis -- 0, 1, 2 (x, y, or z slice view)
        blowup -- enlarge written image by this factor
        filename -- output file
        """

        print "Rendering screenshot for axis=%d to '%s'" % (axis, filename)
        s = self.editor.imageScenes[axis]
        self.editor.navCtrl.enableNavigation = False
        func = partial(self._renderScreenshot, s, blowup, filename)
        self._renderScreenshotDisconnect = func
        s._renderThread.patchAvailable.connect(func)
        nRequested = 0
        for patchNumber in range(len(s._tiling)):
            p = s.tileProgress(patchNumber)
            if p < 1.0:
                s.requestPatch(patchNumber)
                nRequested += 1
        print "  need to compute %d of %d patches" % (nRequested, len(s._tiling))
        if nRequested == 0:
            #If no tile needed to be requested, the 'patchAvailable' signal
            #of the render thread will never come.
            #In this case, we need to call the implementation ourselves:
            self._renderScreenshot(s, blowup, filename, patchNumber=0)

    def addLayer(self, a, display='grayscale', opacity=1.0, \
                 name='Unnamed Layer', visible=True, interpretChannelsAs=None):
        print "adding layer '%s', shape=%r, %r" % (name, a.shape, type(a))

        """Adds a new layer on top of the layer stack (such that it will be
        above all currently defined layers). The array 'a' may be a simple
        numpy.ndarray or implicitly defined via a LazyflowArraySource.

        Returns the created Layer object. The layer can either be removed
        by passing this object to self.removeLayer, or by giving a unique
        name.
        """

        aSlices = None #in case a needs to be split by a lazyflow operator


        if len(a.shape) not in [2,3,5]:
            raise RuntimeError("Cannot interpret array with: shape=%r" \
                               % a.shape)

        volumeImage = True
        if len(a.shape) == 2:
            volumeImage = False
        if len(a.shape) == 3 and a.shape[2] == 3 and interpretChannelsAs == 'RGB':
            volumeImage = False
        viewerType = "volume 5D"
        if not volumeImage: viewerType = "image 2D"
        print "  treating as %s" % viewerType

        aType = None
        if 'lazyflow' in a.__class__.__module__:
            aType = 'lazyflow'
        elif hasattr(a, 'axistags'):
            aType = 'vigra'
        elif isinstance(a, numpy.ndarray):
            aType = 'numpy'
        else:
            aType = 'generic'

        #
        # construct a canonical form for arrays:
        #
        # 2D: x,y,c (to be viewed with image viewer)
        # 5D: t,x,y,z,c (to be viewed with volume viewer)
        #

        #convert from LAZYFLOW
        if aType == 'lazyflow':
            Source = LazyflowSource
            if volumeImage:
                if len(a.shape) < 5:
                    o = Op5ifyer(a.operator.graph)
                    o.inputs['Input'].connect(a)
                    a = o.outputs['Output']
            elif not volumeImage:
                o = OpMultiArraySlicer(a.operator.graph)
                o.inputs['Input'].connect(a)
                o.inputs['AxisFlag'].setValue('c')
                aSlices = o.outputs['Slices']
                class A:
                    pass
                a = A(); a.shape = aSlices[0].shape

        #convert from VIGRANUMPY ARRAY
        elif aType == 'vigra':
            if volumeImage:
                #vigra array with axistags
                a = a.withAxes('t', 'x', 'y', 'z', 'c').view(numpy.ndarray)
            else:
                a = a.withAxes('x', 'y', 'c').view(numpy.ndarray)
            Source = ArraySource

        #convert from NUMPY ARRAY
        elif aType == 'numpy':
            if volumeImage:
                if len(a.shape) == 3:
                    a = a[numpy.newaxis, ..., numpy.newaxis]
                elif len(a.shape) != 5:
                    raise RuntimeError("Cannot interpret numpy array with shape %r as 5D volume" % (a.shape,))
            Source = ArraySource

        #convert from GENERIC ARRAY
        elif aType == 'generic': 
            # not a numpy array. Maybe h5py or something else. Embed it.
            if(hasattr(a, 'dtype')):
                a = Array5d(a, dtype=a.dtype)
            else:
                a = Array5d(a, dtype=np.uint8)
            Source = ArraySource

        #
        # initialize the proper viewer (2D or 5D)
        #

        if not volumeImage:
            init = self._initImageViewing
            shape = a.shape[0:2] #2D
            instance = ImageEditor
        else:
            init = self._initVolumeViewing
            shape = a.shape #5D
            instance = VolumeEditor

        if self.editor is None or self.editor.dataShape != shape:
            if self.editor:
                print "  new %s layer '%s', shape %r is not compatible with existing shape %r" % (viewerType, name, shape, self.editor.dataShape)
            if not isinstance(self.editor, instance) or self.editor is None:
                init()
            self.editor.dataShape = shape
            print "  --> resetting viewer to shape=%r and zero layers" % (self.editor.dataShape,) 
            self.layerstack.clear()

        #
        # create layer
        #

        if display == 'grayscale':
            if interpretChannelsAs == None:
                source = Source(a)
                type_info = numpy.iinfo(a.dtype)
                if type_info.min < 0:
                    print "WARNING: datatype is not bound to semi-positive values"
                layer = GrayscaleLayer(source, range=(0, type_info.max))
            elif interpretChannelsAs == "RGB":
                if aSlices is not None:
                    layer = RGBALayer(LazyflowSource(aSlices[0]), LazyflowSource(aSlices[1]), LazyflowSource(aSlices[2])) 
                else:
                    assert len(a.shape) == 3
                    layer = RGBALayer(Source(a[:,:,0]), Source(a[:,:,1]), Source(a[:,:,2]))
        elif display == 'randomcolors':
            if a.dtype != numpy.uint8:
                print "layer '%s': implicit conversion from %s to uint8" \
                      % (name, a.dtype)
                if a.dtype == numpy.uint32:
                    a = a.astype(numpy.uint8)
                else:
                    raise RuntimeError("unhandled dtype=%r" % a.dtype)
            source = Source(a)
            layer = ColortableLayer(source, self._randomColors())
        else:
            raise RuntimeError("unhandled type of overlay")

        layer.name = name
        layer.opacity = opacity
        layer.visible = visible
        self.layerstack.append(layer)

        return layer

    def removeLayer(self, layer):
        """Remove layer either by given 'Layer' object
        (as returned by self.addLayer), or by it's name string
        (as given to the name parameter in self.addLayer)"""

        if isinstance(layer, Layer):
            idx = self.layerstack.layerIndex(layer)
            self.layerstack.removeRows(idx, 1)
        else:
            idx = [i for i in range(len(self.layerstack)) if \
                self.layerstack.data(self.layerstack.index(i)).name == layer]
            if len(idx) > 1:
                raise RuntimeError("Trying to remove layer '%s', whose name is"
                    "ambigous as it refers to %d layers" % len(idx))
                return False
            self.layerstack.removeRows(idx[0], 1)
        return True

    @property
    def title(self):
        """Get the window title"""

        return self.windowTitle()

    @title.setter
    def title(self, t):
        """Set the window title"""
        
        self.setWindowTitle(t)

    ### private implementations

    def _initVolumeViewing(self):
        self.initLayerstackModel()

        self.editor = VolumeEditor(self.layerstack, labelsink=None)

        if not isinstance(self.viewer, VolumeEditorWidget) or self.viewer.editor is None:
            splitterSizes = self.splitter.sizes()
            self.viewer.setParent(None)
            del self.viewer
            self.viewer = VolumeEditorWidget()
            self.splitter.insertWidget(0, self.viewer)
            self.splitter.setSizes(splitterSizes)
            self.viewer.init(self.editor)

            w = self.viewer
            self.menuView.addAction(w.allZoomToFit)
            self.menuView.addAction(w.allToggleHUD)
            self.menuView.addAction(w.allCenter)
            self.menuView.addSeparator()
            self.menuView.addAction(self.actionCurrentView)
            self.menuView.addAction(w.selectedZoomToFit)
            self.menuView.addAction(w.toggleSelectedHUD)
            self.menuView.addAction(w.selectedCenter)
            self.menuView.addAction(w.selectedZoomToOriginal)
            self.menuView.addAction(w.rubberBandZoom)

            self.editor.newImageView2DFocus.connect(self._setIconToViewMenu)

    def _initImageViewing(self):

        if not isinstance(self.viewer, ImageEditorWidget):
            self.initLayerstackModel()
            
            w = self.viewer
            if isinstance(w, VolumeEditor) and w.editor is not None:
                self.menuView.removeAction(w.allZoomToFit)
                self.menuView.removeAction(w.allToggleHUD)
                self.menuView.removeAction(w.allCenter)
                self.menuView.removeAction(self.actionCurrentView)
                self.menuView.removeAction(w.selectedZoomToFit)
                self.menuView.removeAction(w.toggleSelectedHUD)
                self.menuView.removeAction(w.selectedCenter)
                self.menuView.removeAction(w.selectedZoomToOriginal)
                self.menuView.removeAction(w.rubberBandZoom)
                
            

            #remove 3D viewer
            splitterSizes = self.splitter.sizes()
            self.viewer.setParent(None)
            del self.viewer

            self.viewer = ImageEditorWidget()
            self.editor = ImageEditor(layerStackModel=self.layerstack)
            self.viewer.init(self.editor)
            self.splitter.insertWidget(0, self.viewer)
            self.splitter.setSizes(splitterSizes)
            
            if not _has_lazyflow:
                self.viewer.setEnabled(False)
                
    def _renderScreenshot(self, s, blowup, filename, patchNumber):
        progress = 0
        for patchNumber in range(len(s._tiling)):
            p = s.tileProgress(patchNumber) 
            progress += p
        progress = progress/float(len(s._tiling))
        if progress == 1.0:
            s._renderThread.patchAvailable.disconnect(self._renderScreenshotDisconnect)
            
            img = QImage(int(round((blowup*s.sceneRect().size().width()))),
                         int(round((blowup*s.sceneRect().size().height()))),
                         QImage.Format_ARGB32)
            screenshotPainter = QPainter(img)
            screenshotPainter.setRenderHint(QPainter.Antialiasing, True)
            s.render(screenshotPainter, QRectF(0, 0, img.width()-1, img.height()-1), s.sceneRect())
            print "  saving to '%s'" % filename
            img.save(filename)
            del screenshotPainter
            self.editor.navCtrl.enableNavigation = True

    def _setIconToViewMenu(self):
        focused = self.editor.imageViews[self.editor._lastImageViewFocus]
        self.actionCurrentView.setIcon(\
            QIcon(focused._hud.axisLabel.pixmap()))

    def _randomColors(self, M=256):
        """Generates a pleasing color table with M entries."""

        colors = []
        for i in range(M):
            if i == 0:
                colors.append(QColor(0, 0, 0, 0).rgba())
            else:
                h, s, v = random.random(), random.random(), 1.0
                color = numpy.asarray(colorsys.hsv_to_rgb(h, s, v)) * 255
                qColor = QColor(*color)
                colors.append(qColor.rgba())
        return colors
    
    def show(self):
        if not _has_lazyflow:
            popUp = QMessageBox(parent=self)
            popUp.setTextFormat(1)
            popUp.setText("<font size=\"4\"> Lazyflow could not be imported:</font> <br><br><b><font size=\"4\" color=\"#8A0808\">%s</font></b>"%(exceptStr))
            popUp.show()
            popUp.exec_()
        QMainWindow.show(self)