예제 #1
0
class SaveToFile(Tool):
    '''
    Save the display image to file
    '''
    icon = 'export.svg'

    def __init__(self, imageDisplay):
        Tool.__init__(self, imageDisplay)

        self._dialogs = Dialogs()

        ftypes = """Portable Network Graphics (*.png)
Windows bitmaps (*.bmp *.dib)
JPEG files (*.jpeg *.jpg *.jpe)
JPEG 2000 files (*.jp2)
Portable image format (*.pbm *.pgm *.ppm)
Sun rasters (*.sr *.ras)
TIFF files (*.tiff *.tif)"""

        self.engine = {'normal image':
                       (self.exportCV2,
                        ftypes),
                       '32bit floating-point TIFF file':
                       (self.exportFTiff,
                        'TIFF file (*.tiff)'),
                       'rendered':
                       (self.exportRendered,
                        ftypes),
                       'Numpy array':
                       (self.exportNumpy,
                        'Numpy array (*.npy)'),
                       'Txt file':
                       (lambda: self.exportNumpy(np.savetxt),
                        'Text file (*.txt)'),
                       }
        pa = self.setParameterMenu()

        self.pExportAll = pa.addChild({
            'name': 'export all image layers',
            'type': 'bool',
            'value': False})

        self.pEngine = pa.addChild({
            'name': 'Type',
            'type': 'list',
            'value': 'normal image',
            'limits': list(self.engine.keys()),
            'tip': '''normal image: export the original image array
rendered: export the current display view'''})

        self.pRange = self.pEngine.addChild({
            'name': 'Range',
            'type': 'list',
            'value': 'current',
            'limits': ['0-max', 'min-max', 'current'],
            'visible': True})

        self.pDType = self.pEngine.addChild({
            'name': 'Bit depth',
            'type': 'list',
            'value': '16 bit',
            'limits': ['8 bit', '16 bit'],
            'visible': True})
        self.pDType.sigValueChanged.connect(self._pDTypeChanged)


#         self.pCutNegativeValues = self.pEngine.addChild({
#             'name': 'Cut negative values',
#             'type': 'bool',
#             'value':False,
#             'visible':True})
        self.pStretchValues = self.pEngine.addChild({
            'name': 'Stretch values',
            'type': 'bool',
            'value': True,
            'visible': True})

        self.pOnlyImage = self.pEngine.addChild({
            'name': 'Only image',
            'type': 'bool',
            'value': False,
            'tip': 'True - export only the shown image - excluding background and axes',
            'visible': False})

        self.pEngine.sigValueChanged.connect(self._pEngineChanged)

        self.pResize = pa.addChild({
            'name': 'Resize',
            'type': 'bool',
            'value': False})

        self.pAspectRatio = self.pResize.addChild({
            'name': 'Keep Aspect Ratio',
            'type': 'bool',
            'value': True,
            'visible': False})

        self.pWidth = self.pResize.addChild({
            'name': 'Width',
            'type': 'int',
            'value': 0,
            'visible': False})
        self.pWidth.sigValueChanged.connect(self._pWidthChanged)

        self.pHeight = self.pResize.addChild({
            'name': 'Height',
            'type': 'int',
            'value': 0,
            'visible': False})
        self.pHeight.sigValueChanged.connect(self._pHeightChanged)

        self.pResize.addChild({
            'name': 'Reset',
            'type': 'action',
            'visible': False}).sigActivated.connect(self._pResetChanged)

        self.pResize.sigValueChanged.connect(lambda param, value:
                                             [ch.show(value) for ch in param.children()])

        self.pPath = pa.addChild({
            'name': 'path',
            'type': 'str',
            'value': ''})

        pChoosePath = self.pPath.addChild({
            'name': 'choose',
            'type': 'action'})
        pChoosePath.sigActivated.connect(self._pChoosePathChanged)

        self._menu.aboutToShow.connect(self._updateOutputSize)
        self._menu.aboutToShow.connect(self._updateDType)

    def _pChoosePathChanged(self, param):
        self._choosePath()
        self.activate()

    def _pResetChanged(self):
        try:
            self._menu.aboutToShow.disconnect(self._updateOutputSize)
        except:
            pass
        self._menu.aboutToShow.connect(self._updateOutputSize)
        self._updateOutputSize()

    def _pEngineChanged(self, param, val):
        #         self.pCutNegativeValues.show(val == 'normal image')
        self.pStretchValues.show(val == 'normal image')
        self.pOnlyImage.show(val == 'rendered')
        self.pRange.show(val == 'normal image')
        self.pDType.show(val == 'normal image')

    def _updateOutputSize(self):
        if self.pEngine.value() == 'rendered':
            size = self.display.size()
            w = size.width()
            h = size.height()
        else:
            w, h = self.display.widget.image.shape[1:3]
        self.aspectRatio = h / w
        self.pWidth.setValue(w, blockSignal=self._pWidthChanged)
        self.pHeight.setValue(h, blockSignal=self._pHeightChanged)

    def _updateDType(self):
        w = self.display.widget
        if (w.levelMax - w.levelMin) > 255:
            self.pDType.setValue('16 bit', blockSignal=self._pDTypeChanged)
        else:
            self.pDType.setValue('8 bit', blockSignal=self._pDTypeChanged)

    def _pDTypeChanged(self):
        # dont change dtype automatically anymore as soon as user changed param
        try:
            self._menu.aboutToShow.disconnect(self._updateDType)
        except:
            pass

    def _pHeightChanged(self, param, value):
        try:
            self._menu.aboutToShow.disconnect(self._updateOutputSize)
        except:
            pass
        if self.pAspectRatio.value():
            self.pWidth.setValue(int(round(value / self.aspectRatio)),
                                 blockSignal=self._pWidthChanged)

    def _pWidthChanged(self, param, value):
        try:
            self._menu.aboutToShow.disconnect(self._updateOutputSize)
        except:
            pass
        if self.pAspectRatio.value():
            self.pHeight.setValue(int(round(value * self.aspectRatio)),
                                  blockSignal=self._pHeightChanged)

    def _choosePath(self):
        filt = self.engine[self.pEngine.value()][1]
        kwargs = dict(filter=filt,
                     #  selectedFilter='png' #this option is not supported in PyQt5 any more
                      )
        f = self.display.filenames[0]
        if f is not None:
            kwargs['directory'] = f.dirname()

        path = self._dialogs.getSaveFileName(**kwargs)
        if path:
            self.pPath.setValue(path)

    def activate(self):
        # CHECK PATH
        if not self.pPath.value():
            self._choosePath()
            if not self.pPath.value():
                raise Exception('define a file path first!')

        self.engine[self.pEngine.value()][0]()

    def exportRendered(self):
        '''
        Use QPixmap.grabWidget(display) to save the image
        '''
        d = self.display
        try:
            # get instance back from weakref
            d = d.__repr__.__self__
        except:
            pass
        # PREPARE LAYOUT:
        d.release()
        d.hideTitleBar()

        if self.pResize.value():
            d.resize(self.pWidth.value(), self.pHeight.value())
        # SAVE:
        path = self.pPath.value()

        def grabAndSave(path2):
            if self.pOnlyImage.value():
                item = d.widget.imageItem
                b = item.sceneBoundingRect().toRect()
                w = d.widget.grab(b)#QtGui.QPixmap.grabWidget(d.widget, b)
            else:
                w = d.grab()#QtGui.QPixmap.grabWidget(d)
            w.save(path2)
            print('Saved image under %s' % path2)

        if self.pExportAll.value():
            # EACH LAYER SEPARATE
            old_i = d.widget.currentIndex
            for i in range(len(d.widget.image)):
                path2 = path.replace('.', '__%s.' % i)
                d.widget.setCurrentIndex(i)
                grabAndSave(path2)
            d.widget.setCurrentIndex(old_i)
        else:
            grabAndSave(path)
        # RESET LAYOUT:
        d.showTitleBar()
        d.embedd()

    def exportNumpy(self, method=np.save):
        '''
        Export as a numpy array *.npy
        '''
        path = self.pPath.value()
        w = self.display.widget
        image = w.image

        if not self.pExportAll.value():
            image = image[w.currentIndex]
        method(path, image)
        print('Saved image under %s' % path)

    def _export(self, fn):
        def export(img, path):
            if self.pResize.value():
                img = resize(img, (self.pWidth.value(), self.pHeight.value()))
            fn(path, img)
            print('Saved image under %s' % path)
        w = self.display.widget
        image = w.image
        path = self.pPath.value()

        if self.pExportAll.value():
            for n, img in enumerate(image):
                path2 = path.replace('.', '__%s.' % n)
                export(img, path2)
        else:
            image = image[w.currentIndex]
            export(image, path)

    def exportFTiff(self):
        '''
        Use pil.Image.fromarray(data).save() to save the image array
        '''
        def fn(path, img):
            imwrite(path, transpose(img), dtype=float)
        return self._export(fn)

    def exportCV2(self):
        '''
        Use cv2.imwrite() to save the image array
        '''
        w = self.display.widget

        def fn(path, img):
            r = self.pRange.value()
            if r == '0-max':
                r = (0, w.levelMax)
            elif r == 'min-max':
                r = (w.levelMin, w.levelMax)
            else:  # 'current'
                r = w.ui.histogram.getLevels()
            int_img = toUIntArray(img,
                                  #                                   cutNegative=self.pCutNegativeValues.value(),
                                  cutHigh=~self.pStretchValues.value(),
                                  range=r,
                                  dtype={'8 bit': np.uint8,
                                         '16 bit': np.uint16}[self.pDType.value()])
            if isColor(int_img):
                int_img = cv2.cvtColor(int_img, cv2.COLOR_RGB2BGR)
            
            cv2.imwrite(path, transpose(int_img))

        return self._export(fn)
예제 #2
0
class Table(QtWidgets.QTableWidget):
    """
    A QTableWidget with:
    * Shortcuts: copy, paste, cut, insert/delete row/column
    * Context Menu
    * Cell range operations (copy, paste on multiple cells)
    * Save/open
    * import from clipboard
    * dynamic add of new rows and cells when needed
    """
    sigPathChanged = QtCore.Signal(object)  # file path

    def __init__(self, rows=3, cols=3, colFiled=False,
                 rowFixed=False, parent=None):
        super(Table, self).__init__(rows, cols, parent)
        self._menu = _TableMenu(self)
        self._colFixed = colFiled
        self._rowFixed = rowFixed
        self._dialogs = Dialogs()

        self._path = None
    #    self.setHorizontalHeader(_Header(QtCore.Qt.Horizontal, self))
        self.setCurrentCell(0, 0)
        self.currentCellChanged.connect(self._ifAtBorderAddRow)  # )

    def restore(self, path):
        self.clearContents()
        text = open(path, 'r').read()
        table = self._textToTable(text, ',')
        self.importTable(table)

    def open(self, path):
        if not path:
            path = self._dialogs.getOpenFileName(filter='*.csv')
        if path:
            self.restore(path)
            self._setPath(path)

    def _setPath(self, path):
        self._path = path
        self.sigPathChanged.emit(path)

    def save(self):
        """
        save to file - override last saved file
        """
        self.saveAs(self._path)

    def table(self):
        l = []
        for row in range(self.rowCount()):
            rowdata = []
            for column in range(self.columnCount()):
                item = self.item(row, column)
                if item is not None:
                    rowdata.append(
                        str(item.text()).encode('utf8'))
                else:
                    rowdata.append('')
            l.append(rowdata)
        return l

    def saveAs(self, path):
        """
        save to file under given name
        """
        if not path:
            path = self._dialogs.getSaveFileName(filter='*.csv')
        if path:
            self._setPath(path)
            with open(str(self._path), 'wb') as stream:
                writer = csv.writer(stream)
                table = self.table()
                for row in table:
                    writer.writerow(row)

    def _ifAtBorderAddRow(self, row, column, lastRow, lastColumn):
        if row == self.rowCount() - 1:
            if not self._rowFixed:
                self.setRowCount(row + 2)
        if column == self.columnCount() - 1:
            if not self._colFixed:
                self.setColumnCount(column + 2)

    def mousePressEvent(self, event):
        mouseBtn = event.button()
        if mouseBtn == QtCore.Qt.RightButton:
            self._menu.show(event)
        super(Table, self).mousePressEvent(event)

    def keyPressEvent(self, event):
        if event.matches(QtGui.QKeySequence.Copy):
            self.copy()
        elif event.matches(QtGui.QKeySequence.Cut):
            self.copy()
            self.delete()
        elif event.matches(QtGui.QKeySequence.ZoomIn):  # Ctrl+Plus
            self.insertBlankCells()
        elif event.matches(QtGui.QKeySequence.ZoomOut):  # Ctrl+Minus
            self.removeBlankCells()
        elif event.matches(QtGui.QKeySequence.Delete):
            self.delete()
        elif event.matches(QtGui.QKeySequence.Paste):
            self.paste()
        else:
            QtWidgets.QTableWidget.keyPressEvent(self, event)

    def insertBlankCells(self):
        # TODO: insert only cells for selected range
        r = self.selectedRanges()
        if len(r) > 1:
            print('Cannot insert cells on multiple selections')
            return
        r = r[0]
        if r.leftColumn() == r.rightColumn():
            self.insertColumn(r.leftColumn())
        elif r.leftRow() == r.rightRow():
            self.insertRow(r.leftRow())
        else:
            print('Need one line of rows or columns to insert blank cells')

    def removeBlankCells(self):
        # TODO: remove only cells for selected range
        r = self.selectedRanges()
        if len(r) > 1:
            print('Cannot remove cells on multiple selections')
            return
        r = r[0]
        if r.leftColumn() == r.rightColumn():
            self.removeColumn(r.leftColumn())
        elif r.leftRow() == r.rightRow():
            self.removeRow(r.leftRow())
        else:
            print('Need one line of rows or columns to insert blank cells')

    def delete(self):
        for item in self.selectedItems():
            r, c = item.row(), item.column()
            self.takeItem(r, c)
        self.cleanTable()

    def cleanTable(self):
        r = self.rowCount()
        c = self.columnCount()
        # try to remove empty rows:
        if not self._rowFixed:
            while True:
                for col in range(c):
                    isempty = True
                    item = self.item(r, col)
                    if item and item.text():
                        isempty = False
                        break
                if isempty:
                    self.setRowCount(r)
                    if r == 2:  # min table resolution:2x2
                        break
                    r -= 1
                else:
                    break
        # try to remove empty columns:
        if not self._colFixed:
            while True:
                for row in range(r):
                    isempty = True
                    item = self.item(row, c)
                    if item and item.text():
                        isempty = False
                        break
                if isempty:
                    self.setColumnCount(c)
                    if c == 2:  # min table resolution:2x2
                        break
                    c -= 1
                else:
                    break

    def setColumnsFixed(self, value):
        self._colFixed = value

    def cut(self):
        self.copy()
        self.delete()

    def copy(self):
        firstRange = self.selectedRanges()[0]
        # deselect all other ranges, to show shat only the first one will
        # copied
        for otherRange in self.selectedRanges()[1:]:
            self.setRangeSelected(otherRange, False)
        nCols = firstRange.columnCount()
        nRows = firstRange.rowCount()
        if not nCols or not nRows:
            return
        text = ''
        lastRow = nRows + firstRange.topRow()
        lastCol = nCols + firstRange.leftColumn()
        for row in range(firstRange.topRow(), lastRow):
            for col in range(firstRange.leftColumn(), lastCol):
                item = self.item(row, col)
                if item:
                    text += str(item.text())
                if col != lastCol - 1:
                    text += '\t'
            text += '\n'
        QtWidgets.QApplication.clipboard().setText(text)

    def _textToTable(self, text, separator='\t'):
        """
        format csv, [[...]], ((..)) strings to a 2d table
        """
        table = None
        if text.startswith('[[') or text.startswith('(('):
            try:
                # maybe it's already formated as a list e.g. "[['1','2'],[...]]"
                # check it:
                t = eval(text)
            # has to be a 2d-list:
                if isinstance(t, list) and isinstance(t[0], list):
                    table = t
            except SyntaxError:
                # not a valid list
                pass
        if not table:
            # create the list from the clipboard-text
            # therefore the text has to be formated like this:
                # "1\t2\3\n4\t5\6\n"
            table = text.split('\n')
            n = 0
            while n < len(table):
                sline = table[n].split(separator)
                if sline != ['']:
                    table[n] = sline
                else:
                    table.pop(n)
                    n -= 1
                n += 1
        return table

    @staticmethod
    def fromArray(arr):
        assert arr.ndim < 3
        t = Table()
        t.importTable(arr)
        return t

    @staticmethod
    def fromText(text):
        t = Table()
        table = t._textToTable(text)
        if table:
            t.importTable(table)
        else:
            raise Exception('text is no table')
        return t

    def paste(self):
        # get the text from the clipboard
        text = str(QtWidgets.QApplication.clipboard().text())
        if text:
            table = self._textToTable(text)
            self.importTable(table)

    def importTable(self, table, startRow=None, startCol=None):
        if table is not None and len(table):
            if startRow is None or startCol is None:
                try:
                    # try to get array to paste in from selection
                    r = self.selectedRanges()[0]
                    startRow = r.topRow()
                    startCol = r.leftColumn()
                except IndexError:
                    startRow = 0
                    startCol = 0
            lastRow = startRow + len(table)
            lastCol = startCol + len(table[0])
            if not self._rowFixed:
                if self.rowCount() < lastRow:
                    self.setRowCount(lastRow)
            if not self._colFixed:
                if self.columnCount() < lastCol:
                    self.setColumnCount(lastCol)
            for row, line in enumerate(table):
                for col, text in enumerate(line):
                    r, c = row + startRow, col + startCol
                    self.setItemText(r, c, str(text))

    def setItemText(self, row, col, text):
        item = self.item(row, col)
        if not item:
            item = QtWidgets.QTableWidgetItem()
            self.setItem(row, col, item)
        item.setText(text)
예제 #3
0
class SaveToFile(Tool):
    '''
    Save the display image to file
    '''
    icon = 'export.svg'

    def __init__(self, imageDisplay):
        Tool.__init__(self, imageDisplay)

        self._dialogs = Dialogs()

        ftypes = """Portable Network Graphics (*.png)
Windows bitmaps (*.bmp *.dib)
JPEG files (*.jpeg *.jpg *.jpe)
JPEG 2000 files (*.jp2)
Portable image format (*.pbm *.pgm *.ppm)
Sun rasters (*.sr *.ras)
TIFF files (*.tiff *.tif)"""

        self.engine = {
            'normal image': (self.exportCV2, ftypes),
            '32bit floating-point TIFF file':
            (self.exportFTiff, 'TIFF file (*.tiff)'),
            'rendered': (self.exportRendered, ftypes),
            'Numpy array': (self.exportNumpy, 'Numpy array (*.npy)'),
            'Txt file':
            (lambda: self.exportNumpy(np.savetxt), 'Text file (*.txt)'),
        }
        pa = self.setParameterMenu()

        self.pExportAll = pa.addChild({
            'name': 'export all image layers',
            'type': 'bool',
            'value': False
        })

        self.pEngine = pa.addChild({
            'name':
            'Type',
            'type':
            'list',
            'value':
            'normal image',
            'limits':
            self.engine.keys(),
            'tip':
            '''normal image: export the original image array
rendered: export the current display view'''
        })

        self.pRange = self.pEngine.addChild({
            'name':
            'Range',
            'type':
            'list',
            'value':
            'current',
            'limits': ['0-max', 'min-max', 'current'],
            'visible':
            True
        })

        self.pDType = self.pEngine.addChild({
            'name': 'Bit depth',
            'type': 'list',
            'value': '16 bit',
            'limits': ['8 bit', '16 bit'],
            'visible': True
        })
        self.pDType.sigValueChanged.connect(self._pDTypeChanged)

        #         self.pCutNegativeValues = self.pEngine.addChild({
        #             'name': 'Cut negative values',
        #             'type': 'bool',
        #             'value':False,
        #             'visible':True})
        self.pStretchValues = self.pEngine.addChild({
            'name': 'Stretch values',
            'type': 'bool',
            'value': True,
            'visible': True
        })

        self.pOnlyImage = self.pEngine.addChild({
            'name': 'Only image',
            'type': 'bool',
            'value': False,
            'tip':
            'True - export only the shown image - excluding background and axes',
            'visible': False
        })

        self.pEngine.sigValueChanged.connect(self._pEngineChanged)

        self.pResize = pa.addChild({
            'name': 'Resize',
            'type': 'bool',
            'value': False
        })

        self.pAspectRatio = self.pResize.addChild({
            'name': 'Keep Aspect Ratio',
            'type': 'bool',
            'value': True,
            'visible': False
        })

        self.pWidth = self.pResize.addChild({
            'name': 'Width',
            'type': 'int',
            'value': 0,
            'visible': False
        })
        self.pWidth.sigValueChanged.connect(self._pWidthChanged)

        self.pHeight = self.pResize.addChild({
            'name': 'Height',
            'type': 'int',
            'value': 0,
            'visible': False
        })
        self.pHeight.sigValueChanged.connect(self._pHeightChanged)

        self.pResize.addChild({
            'name': 'Reset',
            'type': 'action',
            'visible': False
        }).sigActivated.connect(self._pResetChanged)

        self.pResize.sigValueChanged.connect(
            lambda param, value: [ch.show(value) for ch in param.children()])

        self.pPath = pa.addChild({'name': 'path', 'type': 'str', 'value': ''})

        pChoosePath = self.pPath.addChild({'name': 'choose', 'type': 'action'})
        pChoosePath.sigActivated.connect(self._pChoosePathChanged)

        self._menu.aboutToShow.connect(self._updateOutputSize)
        self._menu.aboutToShow.connect(self._updateDType)

    def _pChoosePathChanged(self, param):
        self._choosePath()
        self.activate()

    def _pResetChanged(self):
        try:
            self._menu.aboutToShow.disconnect(self._updateOutputSize)
        except:
            pass
        self._menu.aboutToShow.connect(self._updateOutputSize)
        self._updateOutputSize()

    def _pEngineChanged(self, param, val):
        #         self.pCutNegativeValues.show(val == 'normal image')
        self.pStretchValues.show(val == 'normal image')
        self.pOnlyImage.show(val == 'rendered')
        self.pRange.show(val == 'normal image')
        self.pDType.show(val == 'normal image')

    def _updateOutputSize(self):
        if self.pEngine.value() == 'rendered':
            size = self.display.size()
            w = size.width()
            h = size.height()
        else:
            w, h = self.display.widget.image.shape[1:3]
        self.aspectRatio = h / float(w)
        self.pWidth.setValue(w, blockSignal=self._pWidthChanged)
        self.pHeight.setValue(h, blockSignal=self._pHeightChanged)

    def _updateDType(self):
        w = self.display.widget
        if (w.levelMax - w.levelMin) > 255:
            self.pDType.setValue('16 bit', blockSignal=self._pDTypeChanged)
        else:
            self.pDType.setValue('8 bit', blockSignal=self._pDTypeChanged)

    def _pDTypeChanged(self):
        #dont change dtype automatically anymore as soon as user changed param
        try:
            self._menu.aboutToShow.disconnect(self._updateDType)
        except:
            pass

    def _pHeightChanged(self, param, value):
        try:
            self._menu.aboutToShow.disconnect(self._updateOutputSize)
        except:
            pass
        if self.pAspectRatio.value():
            self.pWidth.setValue(int(round(value / self.aspectRatio)),
                                 blockSignal=self._pWidthChanged)

    def _pWidthChanged(self, param, value):
        try:
            self._menu.aboutToShow.disconnect(self._updateOutputSize)
        except:
            pass
        if self.pAspectRatio.value():
            self.pHeight.setValue(int(round(value * self.aspectRatio)),
                                  blockSignal=self._pHeightChanged)

    def _choosePath(self):
        filt = self.engine[self.pEngine.value()][1]
        kwargs = dict(filter=filt, selectedFilter='png')
        f = self.display.filenames[0]
        if f is not None:
            kwargs['directory'] = f.dirname()

        path = self._dialogs.getSaveFileName(**kwargs)
        if path and path != '.':
            self.pPath.setValue(path)

    def activate(self):
        #CHECK PATH
        if not self.pPath.value():
            self._choosePath()
            if not self.pPath.value():
                raise Exception('define a file path first!')

        self.engine[self.pEngine.value()][0]()

    def exportRendered(self):
        '''
        Use QPixmap.grabWidget(display) to save the image
        '''
        d = self.display
        try:
            #get instance back from weakref
            d = d.__repr__.__self__
        except:
            pass
        #PREPARE LAYOUT:
        d.release()
        d.hideTitleBar()

        if self.pResize.value():
            d.resize(self.pWidth.value(), self.pHeight.value())
        #SAVE:
        path = self.pPath.value()

        def grabAndSave(path2):
            if self.pOnlyImage.value():
                item = d.widget.imageItem
                b = item.sceneBoundingRect().toRect()
                w = QtGui.QPixmap.grabWidget(d.widget, b)
            else:
                w = QtGui.QPixmap.grabWidget(d)
            w.save(path2)
            print 'Saved image under %s' % path2

        if self.pExportAll.value():
            #EACH LAYER SEPARATE
            old_i = d.widget.currentIndex
            for i in range(len(d.widget.image)):
                path2 = path.replace('.', '__%s.' % i)
                d.widget.setCurrentIndex(i)
                grabAndSave(path2)
            d.widget.setCurrentIndex(old_i)
        else:
            grabAndSave(path)
        #RESET LAYOUT:
        d.showTitleBar()
        d.embedd()

    def exportNumpy(self, method=np.save):
        '''
        Export as a numpy array *.npy
        '''
        path = self.pPath.value()
        w = self.display.widget
        image = w.image

        if not self.pExportAll.value():
            image = image[w.currentIndex]
        method(path, image)
        print 'Saved image under %s' % path

    def _export(self, fn):
        def export(img, path):
            if self.pResize.value():
                img = resize(img, (self.pWidth.value(), self.pHeight.value()))
            fn(path, img)
            print 'Saved image under %s' % path

        w = self.display.widget
        image = w.image
        path = self.pPath.value()

        if self.pExportAll.value():
            for n, img in enumerate(image):
                path2 = path.replace('.', '__%s.' % n)
                export(img, path2)
        else:
            image = image[w.currentIndex]
            export(image, path)

    def exportFTiff(self):
        '''
        Use pil.Image.fromarray(data).save() to save the image array
        '''
        def fn(path, img):
            Image.fromarray(img.T).save(path)

        return self._export(fn)

    def exportCV2(self):
        '''
        Use cv2.imwrite() to save the image array
        '''
        w = self.display.widget

        def fn(path, img):
            r = self.pRange.value()
            if r == '0-max':
                r = (0, w.levelMax)
            elif r == 'min-max':
                r = (w.levelMin, w.levelMax)
            else:  #'current'
                r = w.ui.histogram.getLevels()
            int_img = toUIntArray(
                img,
                #                                   cutNegative=self.pCutNegativeValues.value(),
                cutHigh=~self.pStretchValues.value(),
                range=r,
                dtype={
                    '8 bit': np.uint8,
                    '16 bit': np.uint16
                }[self.pDType.value()])
            cv2.imwrite(path, out(int_img))

        return self._export(fn)
예제 #4
0
class Session(QtCore.QObject):
    """Session management to be accessible in QtWidgets.QApplication.instance().session

    * extract the opened (as pyz-zipped) session in a temp folder
    * create 2nd temp-folder for sessions to be saved
    * send a close signal to all child structures when exit
    * write a log file with all output
    * enable icons in menus of gnome-sessions [linux only]
    * gives option of debug mode
    """

    #     sigPathChanged = QtCore.Signal(object) #path
    sigSave = QtCore.Signal(object)  # state dict
    sigRestore = QtCore.Signal(object)  # state dict

    def __init__(self, args, **kwargs):
        """
        Args:
            first_start_dialog (Optional[bool]):
                Show a different dialog for the first start.
            name (Optional[str]): The applications name.
            type (Optional[str]): The file type to be used for saving sessions.
            icon (Optional[str]): Path to the application icon.
        """

        QtCore.QObject.__init__(self)

        # SESSION CONSTANTS:
        self.NAME = kwargs.get('name', __main__.__name__)
        self.FTYPE = kwargs.get('ftype', 'pyz')
        self.ICON = kwargs.get('icon', None)
        # hidden app-preferences folder:
        self.dir = PathStr.home().mkdir('.%s' % self.NAME)
        self.APP_CONFIG_FILE = self.dir.join('config.txt')
        self._tmp_dir_session = None

        # session specific options:
        self.opts = _Opts({
            'maxSessions':         3,
            'enableGuiIcons':      True,
            'writeToShell':        True,
            'createLog':           False,
            'debugMode':           False,
            'autosave':            False,
            'autosaveIntervalMin': 15,
            'server':              False,
        }, self)
        # global options - same for all new and restored sessions:
        self.app_opts = {'showCloseDialog': True, 'recent sessions': []}

        if not self.APP_CONFIG_FILE.exists():
            # allow different first start dialog:
            dialog = kwargs.get('first_start_dialog', FirstStart)
            f = dialog(self)
            f.exec_()
            if not f.result():
                sys.exit()

            # create the config file
            with open(self.APP_CONFIG_FILE, 'w') as f:
                pass
        else:
            with open(self.APP_CONFIG_FILE, 'r') as f:
                r = f.read()
                if r:
                    self.app_opts.update(eval(r))

        self._icons_enabled = False
        self.log_file = None
        dirname = self.app_opts['recent sessions']
        if dirname:
            dirname = PathStr(dirname[-1]).dirname()
        self.dialogs = Dialogs(dirname)
        self.saveThread = _SaveThread()

        self._createdAutosaveFile = None
        self.tmp_dir_save_session = None
        # a work-dir for temp. storage:
#         self.tmp_dir_work = PathStr(tempfile.mkdtemp('%s_work' % self.NAME))

        pathName = self._inspectArguments(args)
        self.setSessionPath(pathName)
        if self.opts['createLog']:
            self._setupLogFile()
        # create connectable stdout and stderr signal:
        self.streamOut = StreamSignal('out')
        self.streamErr = StreamSignal('err')
        self._enableGuiIcons()
        # Auto-save timer:
        self.timerAutosave = QtCore.QTimer()
        self.timerAutosave.timeout.connect(self._autoSave)
        self.opts.activate()
        # first thing to do after start:
        QtCore.QTimer.singleShot(0, self.restoreCurrentState)

    def setSessionPath(self, path, statename=None):
        if path:  # and path.endswith('.%s' %self.FTYPE):
            # this script was opened out from a zip-container (named as
            # '*.pyz')
            self.path = PathStr(path)

            self.dir = self.path.dirname().abspath()
            # extract the zip temporally
            ZipFile(self.path, 'r').extractall(path=self.tmp_dir_session)
            self.n_sessions = len(self.stateNames())
            # SET STATE
            snames = self.stateNames()
            if statename is None:
                # last one
                self.current_session = snames[-1]
            elif statename in snames:
                self.current_session = statename
            else:
                raise Exception(
                        "state '%s' not in saved states %s" %
                        (statename, snames))
        else:
            self.path = None
            self.n_sessions = 0
            self.current_session = None

    def writeLog(self, write=True):
        if not self.log_file:
            return
        so = self.streamOut.message
        se = self.streamErr.message
        w = self.log_file.write
        if write:
            try:
                # ensure only connected once
                so.disconnect(w)
                se.disconnect(w)
            except TypeError:
                pass
            so.connect(w)
            se.connect(w)
        else:
            try:
                so.disconnect(w)
                se.disconnect(w)
            except TypeError:
                pass

    def _enableGuiIcons(self):
        # enable icons in all QMenuBars only for this program if generally
        # disabled
        if self.opts['enableGuiIcons']:
            if os.name == 'posix':  # linux
                this_env = str(os.environ.get('DESKTOP_SESSION'))
                relevant_env = (
                    'gnome',
                    'gnome-shell',
                    'ubuntustudio',
                    'xubuntu')
                if this_env in relevant_env:
                    if 'false' in os.popen(
                            # if the menu-icons on the gnome-desktop are disabled
                            'gconftool-2 --get /desktop/gnome/interface/menus_have_icons').read():
                        print('enable menu-icons')
                        os.system(
                                'gconftool-2 --type Boolean --set /desktop/gnome/interface/menus_have_icons True')
                        self._icons_enabled = True

    def _setupLogFile(self):
        lfile = self.tmp_dir_session.join('log.txt')
        if lfile.exists():
            self.log_file = open(lfile, 'a')
        else:
            self.log_file = open(lfile, 'w')
        self.log_file.write('''

####################################
New run at %s
####################################

''' % strftime("%d.%m.%Y|%H:%M:%S", gmtime()))

    def checkMaxSessions(self, nMax=None):
        """
        check whether max. number of saved sessions is reached
        if: remove the oldest session
        """
        if nMax is None:
            nMax = self.opts['maxSessions']
        l = self.stateNames()
        if len(l) > nMax:
            for f in l[:len(l) - nMax]:
                self.tmp_dir_session.remove(str(f))

    def stateNames(self):
        """Returns:
             list: the names of all saved sessions
        """
        if self.current_session:
            s = self.tmp_dir_session
            l = s.listdir()
            l = [x for x in l if s.join(x).isdir()]
            naturalSorting(l)
        else:
            l=[]
        # bring autosave to first position:
        if 'autoSave' in l:
            l.remove('autoSave')
            l.insert(0, 'autoSave')
        return l

    def restorePreviousState(self):
        s = self.stateNames()
        if s:
            i = s.index(self.current_session)
            if i > 1:
                self.current_session = s[i - 1]
                self.restoreCurrentState()

    def restoreNextState(self):
        s = self.stateNames()
        if s:
            i = s.index(self.current_session)
            if i < len(s) - 1:
                self.current_session = s[i + 1]
                self.restoreCurrentState()

    def restoreStateName(self, name):
        """restore the state of given [name]"""
        self.current_session = name
        self.restoreCurrentState()

    def renameState(self, oldStateName, newStateName):
        s = self.tmp_dir_session.join(oldStateName)
        s.rename(newStateName)
        if self.current_session == oldStateName:
            self.current_session = newStateName
        print("==> State [%s] renamed to  [%s]" % (oldStateName, newStateName))

    def _recusiveReplacePlaceholderWithArray(self, state, arrays):
        def recursive(state):
            for key, val in list(state.items()):
                if isinstance(val, dict):
                    recursive(val)
                elif isinstance(val, str) and val.startswith('arr_'):
                    state[key] = arrays[val]

        recursive(state)

    def restoreCurrentState(self):
        if self.current_session:
            orig = self.tmp_dir_save_session
            path = self.tmp_dir_save_session = self.tmp_dir_session.join(
                    self.current_session)
            with open(path.join('state.pickle'), "rb") as f:
                state = pickle.load(f)
            p = path.join('arrays.npz')
            if p.exists():
                arrays = np.load(path.join('arrays.npz'))
                self._recusiveReplacePlaceholderWithArray(state, arrays)

            self.dialogs.restoreState(state['dialogs'])
            self.opts.update(state['session'])
            self.sigRestore.emit(state)
            self.tmp_dir_save_session = orig

            print(
                    "==> State [%s] restored from '%s'" %
                    (self.current_session, self.path))

    def addSession(self):
        self.current_session = self.n_sessions
        self.n_sessions += 1
        self.tmp_dir_save_session = self.tmp_dir_session.join(
                str(self.n_sessions)).mkdir()
        self.checkMaxSessions()

    def quit(self):
        print('exiting...')
        # RESET ICONS
        if self._icons_enabled:
            print('disable menu-icons')
            os.system(  # restore the standard-setting for seeing icons in the menus
                    'gconftool-2 --type Boolean --set /desktop/gnome/interface/menus_have_icons False')

        # WAIT FOR PROMT IF IN DEBUG MODE
        if self.opts['debugMode']:
            input("Press any key to end the session...")
        # REMOVE TEMP FOLDERS
        try:
            self.tmp_dir_session.remove()
#             self.tmp_dir_work.remove()
        except OSError:
            pass  # in case the folders are used by another process

        with open(self.APP_CONFIG_FILE, 'w') as f:
            f.write(str(self.app_opts))
        # CLOSE LOG FILE
        if self.log_file:
            self.writeLog(False)
            self.log_file.close()

    def _inspectArguments(self, args):
        """inspect the command-line-args and give them to appBase"""
        if args:
            self.exec_path = PathStr(args[0])
        else:
            self.exec_path = None

        session_name = None
        args = args[1:]

        openSession = False
        for arg in args:
            if arg in ('-h', '--help'):
                self._showHelp()
            elif arg in ('-d', '--debug'):
                print('RUNNGING IN DEBUG-MODE')
                self.opts['debugMode'] = True
            elif arg in ('-l', '--log'):
                print('CREATE LOG')
                self.opts['createLog'] = True
            elif arg in ('-s', '--server'):
                self.opts['server'] = True
            elif arg in ('-o', '--open'):
                openSession = True
            elif openSession:
                session_name = arg
            else:
                print("Argument '%s' not known." % arg)
                return self._showHelp()
        return session_name

    def _showHelp(self):
        sys.exit('''
    %s-sessions can started with the following arguments:
        [-h or --help] - show the help-page
        [-d or --debug] - run in debugging-mode
        [-l or --log] - create log file
        [-n or --new] - start a new session, don'l load saved properties
        [-exec [cmd]] - execute python code from this script/executable
        ''' % self.__class__.__name__)

    def save(self):
        """save the current session
        override, if session was saved earlier"""
        if self.path:
            self._saveState(self.path)
        else:
            self.saveAs()

    def saveAs(self, filename=None):
        if filename is None:
            # ask for filename:
            filename = self.dialogs.getSaveFileName(filter="*.%s" % self.FTYPE)
        if filename:
            self.path = filename
            self._saveState(self.path)
            if self._createdAutosaveFile:
                self._createdAutosaveFile.remove()
                print(
                        "removed automatically created '%s'" %
                        self._createdAutosaveFile)
                self._createdAutosaveFile = None

    def replace(self, path):
        """
        replace current session with one given by file path
        """
        self.setSessionPath(path)
        self.restoreCurrentState()

    def open(self):
        """open a session to define in a dialog in an extra window"""
        filename = self.dialogs.getOpenFileName(filter="*.%s" % self.FTYPE)
        if filename:
            self.new(filename)

    def new(self, filename=None):
        """start a session an independent process"""
        path = (self.exec_path,)
        if self.exec_path.filetype() in ('py', 'pyw', 'pyz', self.FTYPE):
            # get the absolute path to the python-executable
            p = find_executable("python")
            path = (p, 'python') + path
        else:
            # if run in frozen env (.exe):
            # first arg if execpath of the next session:
            path += (self.exec_path,)
        if filename:
            path += ('-o', filename)
        os.spawnl(os.P_NOWAIT, *path)

    def registerMainWindow(self, win):
        win.setWindowIcon(QtGui.QIcon(self.ICON))

        self._mainWindow = win
        win.show = self._showMainWindow
        win.hide = self._hideMainWindow
        if self.opts['server']:
            server_ = Server(win)
            win.hide()
        else:
            win.show()

    @property
    def tmp_dir_session(self):
        #only create folder if needed
        if self._tmp_dir_session is None:
            # make temp-dir
            # the directory where the content of the *pyz-file will be copied:
            self._tmp_dir_session = PathStr(
                tempfile.mkdtemp(
                        '%s_session' %
                        self.NAME))
        return self._tmp_dir_session
    
    def _showMainWindow(self):
        try:
            # restore autosave
            del self._autosave
        except AttributeError:
            pass
        self._mainWindow.__class__.show(self._mainWindow)

    def _hideMainWindow(self):
        # disable autosave on hidden window
        self._autosave = self.opts['autosave']
        self.opts['autosave'] = False
        self._mainWindow.__class__.hide(self._mainWindow)

    def _saveState(self, path):
        """save current state and add a new state"""
        self.addSession()  # next session
        self._save(str(self.n_sessions), path)

    def _autoSave(self):
        """save state into 'autosave' """
        a = 'autoSave'
        path = self.path
        if not path:
            path = self.dir.join('%s.%s' % (a, self.FTYPE))
            self._createdAutosaveFile = path
        self.tmp_dir_save_session = self.tmp_dir_session.join(a).mkdir()
        self._save(a, path)

    def blockingSave(self, path):
        """
        saved session to file - returns after finish
        only called by interactiveTutorial-save at the moment
        """
        self.tmp_dir_save_session = self.tmp_dir_session.join('block').mkdir()
        state = {'session': dict(self.opts),
                 'dialogs': self.dialogs.saveState()}
        self.saveThread.prepare('0', path, self.tmp_dir_session, state)
        self.sigSave.emit(self)
        self.saveThread.run()

    def _save(self, stateName, path):
        """save into 'stateName' to pyz-path"""
        print('saving...')

        state = {'session': dict(self.opts),
                 'dialogs': self.dialogs.saveState()}

        self.sigSave.emit(state)
        self.saveThread.prepare(stateName, path, self.tmp_dir_session, state)
        self.saveThread.start()

        self.current_session = stateName

        r = self.app_opts['recent sessions']
        try:
            # is this session already exists: remove it
            r.pop(r.index(path))
        except ValueError:
            pass
        # add this session at the beginning
        r.insert(0, path)
예제 #5
0
class SaveToFile(Tool):
    '''
    Save the display image to file
    '''
    icon = 'export.svg'

    def __init__(self, imageDisplay):
        Tool.__init__(self, imageDisplay)

        self._dialogs = Dialogs()

        ftypes = """Portable Network Graphics (*.png)
Windows bitmaps (*.bmp *.dib)
JPEG files (*.jpeg *.jpg *.jpe)
JPEG 2000 files (*.jp2)
Portable image format (*.pbm *.pgm *.ppm)
Sun rasters (*.sr *.ras)
TIFF files (*.tiff *.tif)"""

        self.engine = {
            'normal image': (self.exportCV2, ftypes),
            '32bit floating-point TIFF file':
            (self.exportFTiff, 'TIFF file (*.tiff)'),
            'rendered': (self.exportRendered, ftypes),
            'Numpy array': (self.exportNumpy, 'Numpy array (*.npy)'),
            'Txt file':
            (lambda: self.exportNumpy(np.savetxt), 'Text file (*.txt)'),
            'Video': (self.exportVideo, 'Video file (*.avi *.png)'),
        }
        pa = self.setParameterMenu()

        self.pExportAll = pa.addChild({
            'name': 'export all image layers',
            'type': 'bool',
            'value': False
        })

        self.pEngine = pa.addChild({
            'name':
            'Type',
            'type':
            'list',
            'value':
            'normal image',
            'limits':
            list(self.engine.keys()),
            'tip':
            '''normal image: export the original image array
rendered: export the current display view'''
        })

        self.pRange = self.pEngine.addChild({
            'name':
            'Range',
            'type':
            'list',
            'value':
            'current',
            'limits': ['0-max', 'min-max', 'current'],
            'visible':
            True
        })

        self.pDType = self.pEngine.addChild({
            'name': 'Bit depth',
            'type': 'list',
            'value': '16 bit',
            'limits': ['8 bit', '16 bit'],
            'visible': True
        })
        self.pDType.sigValueChanged.connect(self._pDTypeChanged)

        #         self.pCutNegativeValues = self.pEngine.addChild({
        #             'name': 'Cut negative values',
        #             'type': 'bool',
        #             'value':False,
        #             'visible':True})
        self.pStretchValues = self.pEngine.addChild({
            'name':
            'Stretch values',
            'type':
            'bool',
            'value':
            True,
            'visible':
            True,
            'tip':
            '''If True, rather stretch intensities than
cut values higher than maximum image intensity'''
        })

        self.pOnlyImage = self.pEngine.addChild({
            'name': 'Only image',
            'type': 'bool',
            'value': False,
            'tip': '''True - export only the shown image
- excluding background and axes''',
            'visible': False
        })

        self.pEngine.sigValueChanged.connect(self._pEngineChanged)

        self.pResize = pa.addChild({
            'name': 'Resize',
            'type': 'bool',
            'value': False
        })

        self.pAspectRatio = self.pResize.addChild({
            'name': 'Keep Aspect Ratio',
            'type': 'bool',
            'value': True,
            'visible': False
        })

        self.pWidth = self.pResize.addChild({
            'name': 'Width',
            'type': 'int',
            'value': 0,
            'visible': False
        })
        self.pWidth.sigValueChanged.connect(self._pWidthChanged)

        self.pHeight = self.pResize.addChild({
            'name': 'Height',
            'type': 'int',
            'value': 0,
            'visible': False
        })
        self.pHeight.sigValueChanged.connect(self._pHeightChanged)

        self.pResize.addChild({
            'name': 'Reset',
            'type': 'action',
            'visible': False
        }).sigActivated.connect(self._pResetChanged)

        self.pResize.sigValueChanged.connect(
            lambda param, value: [ch.show(value) for ch in param.children()])

        self.pFrames = self.pEngine.addChild({
            'name': 'Frames per time step',
            'type': 'float',
            'value': 15,
            'limits': (1, 100),
            'visible': False
        })
        self.pAnnotate = self.pEngine.addChild({
            'name': 'Annotate',
            'type': 'bool',
            'value': True,
            'visible': False
        })

        self.pPath = pa.addChild({'name': 'path', 'type': 'str', 'value': ''})

        pChoosePath = self.pPath.addChild({'name': 'choose', 'type': 'action'})
        pChoosePath.sigActivated.connect(self._pChoosePathChanged)

        self._menu.aboutToShow.connect(self._updateOutputSize)
        self._menu.aboutToShow.connect(self._updateDType)

    def _pChoosePathChanged(self, _param):
        self._choosePath()
        self.activate()

    def _pResetChanged(self):
        try:
            self._menu.aboutToShow.disconnect(self._updateOutputSize)
        except:
            pass
        self._menu.aboutToShow.connect(self._updateOutputSize)
        self._updateOutputSize()

    def _pEngineChanged(self, _param, val):
        #         self.pCutNegativeValues.show(val == 'normal image')
        self.pStretchValues.show(val == 'normal image')
        self.pOnlyImage.show(val == 'rendered')
        self.pRange.show(val == 'normal image')
        self.pDType.show(val == 'normal image')
        self.pFrames.show(val == 'Video')
        self.pAnnotate.show(val == 'Video')

    def _updateOutputSize(self):
        if self.pEngine.value() == 'rendered':
            size = self.display.size()
            w = size.width()
            h = size.height()
        else:
            h, w = self.display.widget.image.shape[1:3]
        self.aspectRatio = h / w
        self.pWidth.setValue(w, blockSignal=self._pWidthChanged)
        self.pHeight.setValue(h, blockSignal=self._pHeightChanged)

    def _updateDType(self):
        w = self.display.widget
        if (w.levelMax - w.levelMin) > 255:
            self.pDType.setValue('16 bit', blockSignal=self._pDTypeChanged)
        else:
            self.pDType.setValue('8 bit', blockSignal=self._pDTypeChanged)

    def _pDTypeChanged(self):
        # dont change dtype automatically anymore as soon as user changed param
        try:
            self._menu.aboutToShow.disconnect(self._updateDType)
        except:
            pass

    def _pHeightChanged(self, _param, value):
        try:
            self._menu.aboutToShow.disconnect(self._updateOutputSize)
        except:
            pass
        if self.pAspectRatio.value():
            self.pWidth.setValue(int(round(value / self.aspectRatio)),
                                 blockSignal=self._pWidthChanged)

    def _pWidthChanged(self, _param, value):
        try:
            self._menu.aboutToShow.disconnect(self._updateOutputSize)
        except:
            pass
        if self.pAspectRatio.value():
            self.pHeight.setValue(int(round(value * self.aspectRatio)),
                                  blockSignal=self._pHeightChanged)

    def _choosePath(self):
        filt = self.engine[self.pEngine.value()][1]
        kwargs = dict(
            filter=filt,
            # selectedFilter='png' #this option is not supported in
            # PyQt5 any more
        )
        f = self.display.filenames[0]
        if f is not None:
            kwargs['directory'] = f.dirname()

        path = self._dialogs.getSaveFileName(**kwargs)
        if path:
            self.pPath.setValue(path)

    def activate(self):
        # CHECK PATH
        if not self.pPath.value():
            self._choosePath()
            if not self.pPath.value():
                raise Exception('define a file path first!')

        self.engine[self.pEngine.value()][0]()

    def exportRendered(self):
        '''
        Use QPixmap.grabWidget(display) to save the image
        '''
        d = self.display
        try:
            # get instance back from weakref
            d = d.__repr__.__self__
        except:
            pass
        # PREPARE LAYOUT:
        d.release()
        d.hideTitleBar()

        if self.pResize.value():
            d.resize(self.pWidth.value(), self.pHeight.value())
        # SAVE:
        path = self.pPath.value()

        def grabAndSave(path2):
            if self.pOnlyImage.value():
                item = d.widget.imageItem
                b = item.sceneBoundingRect().toRect()
                w = d.widget.grab(b)  # QtGui.QPixmap.grabWidget(d.widget, b)
            else:
                w = d.grab()  # QtGui.QPixmap.grabWidget(d)
            w.save(path2)
            print('Saved image under %s' % path2)

        if self.pExportAll.value():
            # EACH LAYER SEPARATE
            old_i = d.widget.currentIndex
            for i in range(len(d.widget.image)):
                path2 = path.replace('.', '__%s.' % i)
                d.widget.setCurrentIndex(i)
                grabAndSave(path2)
            d.widget.setCurrentIndex(old_i)
        else:
            grabAndSave(path)
        # RESET LAYOUT:
        d.showTitleBar()
        d.embedd()

    def exportNumpy(self, method=np.save):
        '''
        Export as a numpy array *.npy
        '''
        path = self.pPath.value()
        w = self.display.widget
        image = w.image

        if not self.pExportAll.value():
            image = image[w.currentIndex]
        method(path, image)
        print('Saved image under %s' % path)

    def _export(self, fn):
        def export(img, path):
            if self.pResize.value():
                img = cv2.resize(img,
                                 (self.pWidth.value(), self.pHeight.value()))
            fn(path, img)
            print('Saved image under %s' % path)

        w = self.display.widget
        image = w.image
        path = self.pPath.value()

        if self.pExportAll.value():
            for n, img in enumerate(image):
                path2 = path.replace('.', '__%s.' % n)
                export(img, path2)
        else:
            image = image[w.currentIndex]
            export(image, path)

    def exportFTiff(self):
        '''
        Use pil.Image.fromarray(data).save() to save the image array
        '''
        def fn(path, img):
            # float tiff only works if img is tiff:
            path = path.setFiletype('tiff')
            imwrite(path, img, dtype=float)
#             imwrite(path, transpose(img), dtype=float)

        return self._export(fn)

    def exportVideo(self):
        self.startThread(self._exportVideoThread)

    def _exportVideoThread(self):
        ww = self.display.widget
        n = None
        if self.pAnnotate.value():
            n = self.display.layerNames()

        videoWrite(self.pPath.value(),
                   ww.image,
                   levels=ww.item.levels,
                   shape=(self.pHeight.value(), self.pWidth.value()),
                   frames=self.pFrames.value(),
                   annotate_names=n,
                   lut=ww.item.lut,
                   updateFn=self._thread.sigUpdate)
#
#         fourcc = cv2.VideoWriter_fourcc(*'XVID')
#         ww = self.display.widget
#         im = ww.image
#         if self.pResize.value():
#             w, h = (self.pWidth.value(), self.pHeight.value())
#             im = [cv2.resize(i, (w, h)) for i in im]
#         else:
#             h, w = im.shape[1:3]
#         fr = self.pFrames.value()
#         pa = self.pPath.value()
#         assert pa[-3:] in ('avi',
#                            'png'), 'video export only supports *.avi or *.png'
#         isVideo = pa[-3:] == 'avi'
#         if isVideo:
#             cap = cv2.VideoCapture(0)
#             # im.ndim==4)
#             out = cv2.VideoWriter(pa, fourcc, fr, (w, h), isColor=1)
#
#         times = np.linspace(0, len(im), len(im) * fr)
#         interpolator = LinearInterpolateImageStack(im)
#
#         lut = ww.item.lut
#         if lut is not None:
#             lut = lut(im[0])
#
#         for n, time in enumerate(times):
#             # update progress:
#             self._thread.sigUpdate.emit(100 * n / len(times))
#             image = interpolator(time)
#
#             argb = makeRGBA(image, lut=lut,
#                             levels=ww.item.levels)[0]
#             cimg = cv2.cvtColor(argb, cv2.COLOR_RGBA2BGR)
#
#             if isVideo:
#                 out.write(cimg)
#             else:
#                 cv2.imwrite('%s_%i_%.3f.png' % (pa[:-4], n, time), cimg)
#
#         if isVideo:
#             cap.release()
#             out.release()

    def exportCV2(self):
        '''
        Use cv2.imwrite() to save the image array
        '''
        w = self.display.widget

        def fn(path, img):
            r = self.pRange.value()
            if r == '0-max':
                r = (0, w.levelMax)
            elif r == 'min-max':
                r = (w.levelMin, w.levelMax)
            else:  # 'current'
                r = w.ui.histogram.getLevels()
            int_img = toUIntArray(
                img,
                # cutNegative=self.pCutNegativeValues.value(),
                cutHigh=~self.pStretchValues.value(),
                range=r,
                dtype={
                    '8 bit': np.uint8,
                    '16 bit': np.uint16
                }[self.pDType.value()])
            if isColor(int_img):
                int_img = cv2.cvtColor(int_img, cv2.COLOR_RGB2BGR)

            cv2.imwrite(path, int_img)

        return self._export(fn)
예제 #6
0
class Export(Tool):
    '''
    Save the display image to file
    '''
    icon = 'export.svg' 

    def __init__(self, imageDisplay):
        Tool.__init__(self, imageDisplay)
       
        self._dialogs = Dialogs()

        ftypes = """Portable Network Graphics (*.png)
Windows bitmaps (*.bmp *.dib)
JPEG files (*.jpeg *.jpg *.jpe)
JPEG 2000 files (*.jp2)
Portable image format (*.pbm *.pgm *.ppm)
Sun rasters (*.sr *.ras)
TIFF files (*.tiff *.tif)"""

        self.engine = {'original':
                        (self.exportOriginal, 
                            ftypes),
                       'rendered': 
                            (self.exportRendered, 
                             ftypes),
                       'Numpy array':
                            (self.exportNumpy,
                             'Numpy array (*.npy)'),
                       'Txt file': 
                            (lambda:self.exportNumpy(np.savetxt), 
                             'Text file (*.txt)'),
                       }
        pa = self.setParameterMenu() 

        self.pExportAll = pa.addChild({
            'name': 'export all image layers',
            'type': 'bool',
            'value':False})
        
        self.pEngine = pa.addChild({
            'name': 'Type',
            'type': 'list',
            'value':'original',
            'limits':self.engine.keys(),
            'tip':'''original: export the original image array
rendered: export the current display view'''})
        
        self.pCutNegativeValues = self.pEngine.addChild({
            'name': 'Cut negative values',
            'type': 'bool',
            'value':False,
            'visible':True})
        self.pStretchValues = self.pEngine.addChild({
            'name': 'Stretch values',
            'type': 'bool',
            'value':False,
            'visible':True})
        
        self.pOnlyImage = self.pEngine.addChild({
            'name': 'Only image',
            'type': 'bool',
            'value':False,
            'tip':'True - export only the shown image - excluding background and axes',
            'visible':False})

        self.pEngine.sigValueChanged.connect(self._pEngineChanged)        

        self.pResize = pa.addChild({
            'name': 'Resize',
            'type': 'bool',
            'value':False})
        
        self.pAspectRatio = self.pResize.addChild({
            'name': 'Keep Aspect Ratio',
            'type': 'bool',
            'value':True,
            'visible':False}) 
         
        self.pWidth = self.pResize.addChild({
            'name': 'Width',
            'type': 'int',
            'value': 0,
            'visible':False})
        self.pWidth.sigValueChanged.connect(self._pWidthChanged)
         
        self.pHeight = self.pResize.addChild({
            'name': 'Height',
            'type': 'int',
            'value': 0,
            'visible':False})
        self.pHeight.sigValueChanged.connect(self._pHeightChanged)

        self.pResize.sigValueChanged.connect(lambda param, value: 
                            [ch.show(value) for ch in param.children()] )

        self.pPath = pa.addChild({
            'name': 'path',
            'type': 'str',
            'value':''})
        
        pChoosePath = self.pPath.addChild({
            'name': 'choose',
            'type': 'action'})
        pChoosePath.sigActivated.connect(self._pChoosePathChanged)

        self._menu.aboutToShow.connect(self._updateDisplayDize)
   
   
    def _pChoosePathChanged(self, param):
        self._choosePath()
        self.activate()
       
       
    def _pEngineChanged(self, param, val):
        self.pCutNegativeValues.show(val == 'original')
        self.pStretchValues.show(val == 'original')
        self.pOnlyImage.show(val == 'rendered')
        
            

    def _updateDisplayDize(self):
        size = self.display.size()
        w = size.width()
        h = size.height()
        self.aspectRatio = h / float(w)
        self.pWidth.setValue(w, blockSignal=self._pWidthChanged) 
        self.pHeight.setValue(h, blockSignal=self._pHeightChanged)
        
 
    def _pHeightChanged(self, param, value):
        if self.pAspectRatio.value():
            self.pWidth.setValue(value/self.aspectRatio, 
                                 blockSignal=self._pWidthChanged)
        

    def _pWidthChanged(self, param, value):
        if self.pAspectRatio.value():
            self.pHeight.setValue(value*self.aspectRatio, 
                                  blockSignal=self._pHeightChanged)
  

    def _choosePath(self):
        filt = self.engine[self.pEngine.value()][1]
        kwargs = dict(filter=filt, selectedFilter='png')
        f = self.display.filenames[0]
        if f is not None:
            kwargs['directory'] = f.dirname()
            
        path = self._dialogs.getSaveFileName(**kwargs)
        if path and path != '.':
            self.pPath.setValue(path)


    def activate(self):
        #CHECK PATH
        if not self.pPath.value():
            self._choosePath()
            if not self.pPath.value():
                raise Exception('define a file path first!')

        self.engine[ self.pEngine.value() ][0] ()



    def exportRendered(self):
        '''
        Use QPixmap.grabWidget(display) to save the image
        '''
        d = self.display
        try:
            #get instance back from weakref
            d = d.__repr__.__self__
        except:
            pass
        #PREPARE LAYOUT:
        d.release()
        d.hideTitleBar()
        
        if self.pResize.value():
            d.resize(self.pWidth.value(), self.pHeight.value())            
        #SAVE:
        path = self.pPath.value()
        
        def grabAndSave(path2):
            if self.pOnlyImage.value():
                item = d.widget.imageItem
                b = item.sceneBoundingRect().toRect()
                w = QtGui.QPixmap.grabWidget(d.widget, b)
            else:
                w = QtGui.QPixmap.grabWidget(d)
            w.save(path2)
            print 'Saved image under %s' %path2
        
        if self.pExportAll.value():
            #EACH LAYER SEPARATE
            old_i = d.widget.currentIndex
            for i in range(len(d.widget.image)):
                path2 = path.replace('.', '__%s.' %i)
                d.widget.setCurrentIndex(i)
                grabAndSave(path2)
            d.widget.setCurrentIndex(old_i)
        else:
            grabAndSave(path)
        #RESET LAYOUT:
        d.showTitleBar()
        d.embedd()
            

    def exportNumpy(self, method=np.save):
        '''
        Export as a numpy array *.npy
        '''
        path = self.pPath.value()
        w = self.display.widget
        image = w.image

        if not self.pExportAll.value():
            image = image[w.currentIndex]
        method(path, image)
        print 'Saved image under %s' %path


    def exportOriginal(self):
        '''
        Use cv2.imwrite() to save the image array
        '''
        def export(img, path):
            if self.pResize.value():
                img = cv2.resize(img, (self.pHeight.value(), self.pWidth.value()) )
            else:
                img = img.copy()
            int_img = toUIntArray(img, 
                                  cutNegative=self.pCutNegativeValues.value(), 
                                  cutHigh=~self.pStretchValues.value())
            cv2.imwrite(path,out(int_img))
            print 'Saved image under %s' %path
        w = self.display.widget            
        image = w.image
        path = self.pPath.value()
        
        if self.pExportAll.value():
            for n, img in enumerate(image):
                path2 = path.replace('.', '__%s.' %n)
                export(img, path2)
        else:
            image = image[w.currentIndex]
            export(image, path)