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)
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)
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)
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)
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)
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)