def drawCell(self, p, x0, x1, y0, y1, subx, suby): w, h = x1 - x0, y1 - y0 rect = QtCore.QRectF(x0, y0, w, h) # rect: pp1 = QtGui.QPainterPath() pp1.addRect(rect) # busbars: # TODO: make width variable ... currently fixed to 1e-3 ps = QtGui.QPainterPath() for yi in subx: yi = y0 + yi * (y1 - y0) ps.addRect(QtCore.QRectF(x0, yi, w, 1e-3)) for xi in suby: xi = x0 + xi * (x1 - x0) ps.addRect(QtCore.QRectF(xi, y0, 1e-3, h)) if not self.circleOffs.isNull(): # intersect rect with ellipse to create pseudo rect shape: # scale rect: c = rect.center() esize = rect.size() * 2**0.5 - self.circleOffs esize.setWidth(max(rect.width(), esize.width())) esize.setHeight(max(rect.height(), esize.height())) rect.setSize(esize) rect.moveCenter(c) pp2 = QtGui.QPainterPath() pp2.addEllipse(rect) pp1 = pp2.intersected(pp1) ps = pp2.intersected(ps) p.addPath(ps) p.addPath(pp1)
def __init__(self, nCells=(3, 4), nSublines=(0, 0), pos=(0, 0), size=(1, 1), width=(0, 0), **kwargs): QuadROI.__init__(self, pos=pos, size=size, removable=True, **kwargs) self.nCells = list(nCells) self.nSublines = list(nSublines) self._shape = None self._size = size self.width = width self.circleOffs = QtCore.QSizeF(0, 0) self.homography() self._hCellSize = HH(self.setWidth2, self.handleSize, typ='s', pen={ 'color': 'b', 'width': 3 }, parent=self) self._hCellShape = HH(self.setCircle, self.handleSize, typ='s', pos=(0.5, 1), pen={ 'color': 'c', 'width': 3 }, parent=self)
def __init__(self, display): Tool.__init__(self, display) self.timer = QtCore.QTimer() self.timer.timeout.connect(self._grabImage) self.firstTime = True pa = self.setParameterMenu() self.pGrayscale = pa.addChild({ 'name': 'Grayscale', 'type': 'bool', 'value': True}) self.pFloat = pa.addChild({ 'name': 'To float', 'type': 'bool', 'value': True}) pFrequency = pa.addChild({ 'name': 'Read frequency [Hz]', 'type': 'float', 'value': 20.0, 'limits':[0,1000]}) pFrequency.sigValueChanged.connect(self._setInterval) self._setInterval(pFrequency, pFrequency.value()) self.pBuffer = pa.addChild({ 'name': 'Buffer last n images', 'type': 'int', 'value': 0})
def painterPath(self): p = QPainterPath() roundness = int(99 * float(self._alen) / 16 / 90) r = QtCore.QRectF(self._rect) r.moveTo(self.state['pos']) p.addRoundRect(r, roundness) return p
def showDisplay(self, arg): ''' show display as frame-less window '[displaynumber], [area]' = (x,y,width,height) ''' displaynumber, pos = eval(arg) if not pos: return self.hideDisplay(displaynumber) else: (x, y, width, height) = pos d = self.framelessDisplays.get(displaynumber, None) if not d: try: d = self.currentWorkspace().displaydict()[displaynumber] d.release() d.hideTitleBar() d.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) self.framelessDisplays[displaynumber] = d except KeyError: print('displaynumber [%s] not known' % displaynumber) return d.move(QtCore.QPoint(x, y)) d.resize(width, height) d.show()
def __init__(self, display): QtCore.QThread.__init__(self) self.display = display self.progressBar = display.workspace.gui.progressBar self._status_timer = QtCore.QTimer() self._curFile_n = 0 self._firstTime = True
def start(self): ''' configure the server and check the message-inbox every self.opts['refeshrate'] ''' self.configure() self.timer = QtCore.QTimer() self.timer.timeout.connect(self.checkInbox) self.timer.start(self.opts['refreshrate'])
def getFileInfo(self, filename): ''' return general information of the given [filename], like it's size ''' f = QtCore.QFileInfo(filename) size = f.size() size_type = ['Byte', 'kB', 'MB', 'GB', 'TB', 'PB'] size_index = 0 while size > 1024: size /= 1024.0 size_index += 1 size = "%.3g %s" % (round(size, 3), size_type[size_index]) n = f.fileName() return '''File name:\t%s Folder:\t%s File type:\t%s File size:\t%s Last changed:\t%s''' % (n, f.filePath()[:-len(n)], f.suffix(), size, f.lastModified().toString()) #TODO: not used at the moment #but useful # def getMetaData(self, filename): # ''' # Find and format the meta data of [filename] # ''' # text = "" # if filename.filetype().lower() not in ('tif', 'tiff'): # #for all files except TIF # filename, realname = unicodeFilename(filename), filename # parser = createParser(filename, realname) # if not parser: # print "Unable to parse file" # try: # metadata = extractMetadata(parser) # except HachoirError, err: # #AttributeError because hachoir uses # print "Metadata extraction error: %s" % unicode(err) # metadata = None # if not metadata: # print "Unable to extract metadata" # else: # text = "\n".join(metadata.exportPlaintext()) # else: # #for TIF images # import exifread # # Open image file for reading (binary mode) # f = open(filename, 'rb') # # Return Exif tags # tag_dict = exifread.process_file(f) # for key, value in tag_dict.iteritems(): # text += "%s: %s\n" %(key, value) # return text
class _Timer(QtCore.QTimer): ''' A QTimer with 2 new signals. emitted, when the timer... * started, and * stopped ''' sigStarted = QtCore.pyqtSignal(object) #self sigStopped = QtCore.pyqtSignal(object) #self def start(self, msec=None): self.sigStarted.emit(self) if msec: QtCore.QTimer.start(self, msec) else: QtCore.QTimer.start(self) def stop(self): QtCore.QTimer.stop(self) self.sigStopped.emit(self)
def start(self): ''' configure the server and check the message-inbox every self.opts['refeshrate'] ''' self._files = PathStr(self.opts['folder']).listdir() self.timer = QtCore.QTimer() self.timer.timeout.connect(self.checkFolder) self.timer.start(self.opts['refreshrate'])
def _edgePos(self, w=None): # returns handle position t = self.roi._homography n = self.roi.nCells if w is None: w = self.roi.width p = self.pos return t.map( QtCore.QPointF(((p[0] - w[1]) / n[1]), (p[1] - w[0]) / n[0]))
def _prepare(self): r = self.boundingRect() r = QtCore.QRectF(r.x() / r.width(), r.y() / r.height(), 1, 1) #get draw params: self._edges, self._angles, self._alen = self._intersectionPointsAndAngles( r, self._ratioEllispeRectangle) #scale rect: bl = r.bottomLeft() tr = r.topRight() size = tr - bl newSize = size * self._ratioEllispeRectangle ds = 0.5 * (newSize - size) r.setBottomLeft(bl - ds) r.setTopRight(tr + ds) self._rect = r
class _ProcessThread(QtCore.QThread): ''' Thread to be used in tool.activate in order not to block the gui ''' sigDone = QtCore.pyqtSignal(object) def __init__(self, tool, runfn, donefn=None): QtCore.QThread.__init__(self) self.tool = tool self.progressBar = tool.display.workspace.gui.progressBar self.runfn = runfn self.sigDone.connect(self.done) if not donefn is None: self.sigDone.connect(donefn) def kill(self): self.progressBar.hide() self.progressBar.cancel.clicked.disconnect(self.kill) self.tool.setChecked(False) self.terminate() def start(self): self.progressBar.show() self.progressBar.cancel.clicked.connect(self.kill) self.progressBar.bar.setValue(50) self.progressBar.label.setText("Processing %s" %self.tool.__class__.__name__) QtCore.QThread.start(self) def done(self): self.progressBar.hide() self.progressBar.cancel.clicked.disconnect(self.kill) def run(self): try: out = self.runfn() except Exception: print 'tool activation aborted: %s' %traceback.format_exc() return self.progressBar.cancel.click() self.sigDone.emit(out)
def __init__(self, scriptTab, automation, refreshrate): QtCore.QThread.__init__(self) self.scriptTab = scriptTab self.automation = automation #CREATE AND EXTEND THE GLOBALS USED FOR EXECUTING THE SCRIPT: self._globals = _ExecGlobalsDict(automation.display) self._globals.update({ #'wait':self.wait, 'timed': self._getAndRegisterTimer, 'new': self._addActiveWidget }) k = self._globals.keys() for b in BUILTINS_DICT.keys(): k.remove(b) self._widgetUpdateTimer = QtCore.QTimer() self._widgetUpdateTimer.timeout.connect(self.automation._updateWidget) self.setRefreshrate(refreshrate)
def __init__(self, display): Tool.__init__(self, display) self.paths = [] #min time difference to add a line to qpainterpath #when drawing in [ms]: self.MIN_DT = 25 self._timer = QtCore.QTime() self.pa = self.setParameterMenu() self.pa.sigChildRemoved.connect(self._removePath) self.pType = self.pa.addChild({ 'name': 'Type', 'type': 'list', 'value': 'Freehand', 'limits': ['Freehand', 'Rectangle', 'Grid', 'Isolines', 'Ellipse'] }) self.pNew = self.pa.addChild({ 'name': 'New', 'type': 'action', 'value': True }) self.pNew.sigActivated.connect(self._new) self.pNew.sigActivated.connect(self._menu.resizeToContent) pMask = self.pa.addChild({'name': 'Create Mask', 'type': 'action'}) pMask.sigActivated.connect(self._createMaskFromSelection) pArea = self.pa.addChild({'name': 'Measure areas', 'type': 'action'}) pArea.sigActivated.connect(self._measurePath) self.pMask = pArea.addChild({ 'name': 'Relative to selection', 'type': 'list', 'value': '-', 'limits': ['-'] }) self.pMask.items.keys()[0].widget.showPopup = self._updatePMask
def __init__(self, display): Tool.__init__(self, display) self.paths = [] #min time difference to add a line to qpainterpath #when drawing in [ms]: self.MIN_DT = 25 self._timer = QtCore.QTime() self.pa = self.setParameterMenu() self.pa.sigChildRemoved.connect(self._removePath) pNew = self.pa.addChild({ 'name': 'New', 'type': 'action', 'value': True }) pNew.sigActivated.connect(self._newCurve) pNew.sigActivated.connect(self._menu.hide)
def __init__(self, name, widget, pkg, toolClasses): QtGui.QToolBar.__init__(self, name) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self._openContextMenu) #make ToolBar smaller: self.setIconSize(QtCore.QSize(16, 16)) self.layout().setContentsMargins(0, 0, 0, 0) self.setAllowedAreas(QtCore.Qt.TopToolBarArea) #measure toolbar width in order to find best place when added self.width = 9 #9=handle width # whether position of toolbar should be #found depending on available space: # self.needPositioning = False name[0].islower() f = name[0] if f.islower(): name = f.upper() + name[1:] self.name = name self.widget = widget self.toolClasses = toolClasses self._toolsCreated = False # there is a central setup for the visibility of all # toolbars that have the same display widget: show = getattr(pkg, 'show', True) if isinstance(show, dict): #whether to show/hide a toolbar depends on the chosen profile: session = QtGui.QApplication.instance().session profile = session.app_opts.get('profile', None) show = show.get(profile, False) if self.widget.__class__.selectedToolbars.get(name, None) is None: self.widget.__class__.selectedToolbars[name] = [ show, # QtCore.Qt.TopToolBarArea, # TO_QT_POSITION[getattr(pkg, 'position','top')], False ] #->hasBreak # #add logo to toolbar: # icon = getattr(pkg, 'icon', None) # if icon: # #icon can ge given as direct path # if not os.path.exists(icon): # #otherwise it is assumed that # #the icon is to be found in the icon directory: # icon = ICONFOLDER.join(icon) # s = ToolBarIcon(self, icon) tip = getattr(pkg, '__doc__', None) ttname = name if tip: ttname += '\n\t%s' % tip self.setToolTip(ttname) #BACKGROUND COLOR: c = getattr(pkg, 'color', None) if not c is None: #create a semi-transparent colour from #normal background color (grey) #and given color: a = 0.9 b = 1 - a p = self.palette() bg = p.color(self.backgroundRole()) c = QtGui.QColor(c) bg.setRed(a * bg.red() + b * c.red()) bg.setGreen(a * bg.green() + b * c.green()) bg.setBlue(a * bg.blue() + b * c.blue()) self.setBackgroundRole(QtGui.QPalette.Window) p.setColor(self.backgroundRole(), bg) self.setPalette(p) self.setAutoFillBackground(True) self.actionSelect = a = QtGui.QAction(name, self) s = 'contains...' for cls in self.toolClasses: s += '\n %s' % cls.__name__ a.setToolTip(s) a.setCheckable(True) a.setChecked(show) a.triggered.connect(self.setSelected)
class ReaderBase(QtCore.QThread): ''' Base class for file reader reading input files within a QThread in order not to block the screen Show progress in progress bar ''' #array, filenames, labels done = QtCore.pyqtSignal(object, object, object) preferences = None forceSetup = False #whether to show import preferences on the first time def __init__(self, display): QtCore.QThread.__init__(self) self.display = display self.progressBar = display.workspace.gui.progressBar self._status_timer = QtCore.QTimer() self._curFile_n = 0 self._firstTime = True @classmethod def check(cls, ftype, fname): return ftype in cls.ftypes def _cancel(self): self.canceled = True def toFloat(self, arr, toFloat=True, forceFloat64=False): if hasattr(self, 'preferences'): p = self.preferences toFloat = p.pToFloat.value() forceFloat64 = p.pForceFloat64.value() if not toFloat: return arr arr = np.asanyarray(arr) try: if forceFloat64: dtype = np.float64 else: dtype = { np.dtype('uint8'): np. float32, #float16 is just to coarse and cause nans and infs np.dtype('uint16'): np.float32, np.dtype('uint32'): np.float64, np.dtype('uint64'): np.float64 }[arr.dtype] return arr.astype(dtype, copy=False) except KeyError: return arr def status(self): ''' returns the ratio of progress (should be overridden in child class) ''' return 1 def overallStatus(self): ''' return the overall readout status in percent ''' return 100 * self.status() * float(self._curFile_n + 1) / len( self.filenames) def start(self, filenames): self.filenames = filenames if self.preferences and self.forceSetup and self._firstTime: d = QtGui.QDialog() d.setWindowTitle('Import preferences') l = QtGui.QVBoxLayout() t = ParameterTree(self.preferences) b = QtGui.QPushButton('OK') b.clicked.connect(d.accept) l.addWidget(t) l.addWidget(b) d.setLayout(l) d.accepted.connect(self._start) d.exec_() else: self._start() def _start(self): self.canceled = False self._firstTime = False #PROGRESS BAR: self.progressBar.show() self.progressBar.cancel.clicked.connect(self._cancel) self.progressBar.bar.setValue(0) self._status_timer.timeout.connect( lambda: self.progressBar.bar.setValue(self.overallStatus())) self._status_timer.start(1000) #[ms] -> every second self.finished.connect(self.cleanUp) QtCore.QThread.start(self) def run(self): ''' read data from given self.filenames returns datalayers, filenames, layernames ''' data_list = [] label_list = [] fname_list = [] for self._curFile_n, f in enumerate(self.filenames): if f: self.progressBar.label.setText("open file '%s'" % f) #GET DATA: try: data, labels = self.open(f) if data is None: raise IOError('data empty') if labels is not None: # multiple plots/images/etc in one file: data_list.extend([d for d in data]) label_list.extend(labels) fname_list.extend([f] * len(labels)) else: data_list.append(data) label_list.append(labels) fname_list.append(f) except (IOError, ValueError), errmsg: print 'Error while opening %s: %s' % (f, errmsg) label_list.append(None) data_list.append(None) fname_list.append(f) if self.canceled: break self.done.emit(data_list, fname_list, label_list)
class _ControlWidget(QtGui.QWidget): ''' A draggable control window with: * Button 'Previous' * Button 'Next' to be connected with the given functions ''' sigDone = QtCore.Signal(tuple) #parameter values def __init__(self, parameters, function, xVals, plotItem): QtGui.QWidget.__init__(self) #make frame-less: self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) #TODO: # #go to current screen: # d = QtGui.QApplication.desktop() # n = d.screenNumber(self) # self.setGeometry(d.screenGeometry(n)) self.xVals = xVals self.plotItem = plotItem self.function = function layout = QtGui.QGridLayout() self.setLayout(layout) self.s = [] #all spinboxes for n, (name, value) in enumerate(parameters.items()): layout.addWidget(QtGui.QLabel(name), n, 0) s = QtGui.QDoubleSpinBox() s.setValue(value) s.setMaximum(1e6) s.setMinimum(-1e6) s.valueChanged.connect(self._valueChanged) layout.addWidget(s, n, 1) self.s.append(s) btn_done = QtGui.QPushButton('Done') btn_done.clicked.connect(self.done) layout.addWidget(btn_done, n + 1, 0, 1, 2) self._valueChanged() def _getVals(self): vals = [] for s in self.s: vals.append(s.value()) return vals def _valueChanged(self): yVals = [self.function(x, *tuple(self._getVals())) for x in self.xVals] self.plotItem.setData(self.xVals, yVals) def done(self): self.close() self.sigDone.emit(tuple(self._getVals())) #make draggable: def mousePressEvent(self, event): self.offset = event.pos() def mouseMoveEvent(self, event): x = event.globalX() y = event.globalY() x_w = self.offset.x() y_w = self.offset.y() self.move(x - x_w, y - y_w)
class _StackParameter(GroupParameter): ''' Parameter containing information about all input layers and allowing to change their position within the stack ''' sigValuesChanged = QtCore.pyqtSignal(object) #values sigLayerNameChanged = QtCore.pyqtSignal(int, object) #index, name def __init__(self, display): self.display = display mAll = QtGui.QMenu('All layers') mAll.addAction('Change').triggered.connect( self.display.changeLayerFiles) mAll.addAction('Remove').triggered.connect(self.display.removeLayers) GroupParameter.__init__( self, **{ 'name': ' Layers', 'sliding': True, 'addToContextMenu': [mAll] }) #IF A LAYER IS MOVED: self.sigChildRemoved.connect( lambda parent, child, index, self=self: self.display.removeLayer( index, self.opts.get('aboutToMove', False))) self._fnInsertRemovedLayer = lambda parent, child, index, self=self: \ self.display.insertRemovedLayer(index) self.sigChildAdded.connect(self._fnInsertRemovedLayer) self.sigChildAdded.connect(self._valuesChanged) self.sigChildRemoved.connect(self._valuesChanged) def _extractFromOrigin(self, origin, index, info, changes): ''' get 'Info' and 'Changes' from origin StackParameter append origin-changes with new changes ''' if origin: ch = origin.children() if not info: info = [c.param('Info').value() for c in ch] if index is not None: info = info[index] else: info = '\n---\n'.join(info) origin_changes = '' for c in ch: v = c.param('Changes').value() if len(v): origin_changes += ';%s ' % v if changes: if type(changes) in (tuple, list): changes = changes[index] changes += '\n%s' % origin_changes else: changes = origin_changes return info, changes def _valuesChanged(self): ''' all layers create an additional stack-dimension with it's own values e.g. a set of images, where every layer represents an image at another excitation time could have stack.values = [1,5,10,15,20] [s] as soon as the position of a layer changed or a layer is removed these values (e.g.excitation times) have to updated as well ''' self.values = np.array([ch.value() for ch in self.childs]) self.sigValuesChanged.emit(self.values) # # def save(self, session, path): # ''' # save parameter values to file 'stack.txt' # ''' # session.addContentToSave(self.saveState(), *path+('stack.txt',)) # def restoreState(self, state, **kwargs): ''' set parameter values using file 'stack.txt' ''' #DON'T DO ANYTHING WHILE THE STACK IS UPDATED: self.blockSignals(True) self.clearChildren() self.blockSignals(False) self.sigChildAdded.disconnect(self._fnInsertRemovedLayer) #REBUILD STACK: # l = eval(session.getSavedContent(*path +('stack.txt',) ) ) GroupParameter.restoreState(self, state, **kwargs) self.sigChildAdded.connect(self._fnInsertRemovedLayer) def buildLayer(self, fname, label, name, data, origin=None, index=None, info=None, changes=None): ''' for every layer of the stack add a parameter containing ... * it's stack value * an layer info field * a layer changed field ''' info, changes = self._extractFromOrigin(origin, index, info, changes) try: self.sigChildAdded.disconnect(self._fnInsertRemovedLayer) except TypeError: pass #ADD OPTIONS TO THE CONTEXT MENU: mCopy = QtGui.QMenu('Copy') mMove = QtGui.QMenu('Move') if not fname or not PathStr(fname).isfile(): fname = None menu_entries = [mCopy, mMove] if self.display.reader is not None: aFile = QtGui.QAction('Change File', self) aFile.triggered.connect(lambda checked, i=len(self.childs): self. display.changeLayerFile(i)) menu_entries.append(aFile) #CREATE AND ADD COPY-LAYER-TO OPTION TO PARAMETER: pLayer = self.addChild({ 'type': 'float', 'highlight': True, 'name': name, 'value': self.display.axes.stackAxis.getNextStackValue( PathStr(fname).basename()), 'expanded': False, 'removable': True, 'autoIncrementName': True, 'renamable': True, 'readonly': True, 'addToContextMenu': menu_entries, 'filename': fname, 'layername': label, }) mCopy.aboutToShow.connect( lambda pLayer=pLayer, mCopy=mCopy, self=self: self. buildCopyToDisplayMenu(mCopy, pLayer, 'copy')) mMove.aboutToShow.connect( lambda pLayer=pLayer, mMove=mMove, self=self: self. buildCopyToDisplayMenu(mMove, pLayer, 'move')) #UPDATE STACK VALUES: pLayer.sigValueChanged.connect(self._valuesChanged) #EMIT LAYERNAMESCHANGED: pLayer.sigNameChanged.connect( lambda param, val, self=self: self.sigLayerNameChanged.emit( param.parent().children().index(param), val)) #CHECK WHETHER INSERTED LAYER COMES FROM A MOVED ONE: self.sigChildAdded.connect(self._fnInsertRemovedLayer) #ADD LAYER INFO if info is None: finfo = '' if fname is not None: try: #read info from file: finfo += self.getFileInfo( fname) + '\n' # + self.getMetaData(name) except AttributeError: pass # finfo = '' #not possible to read from file because maybe not a filename dinfo = '' if data is not None: try: dinfo = 'shape:\t%s\ndtype:\t%s' % (data.shape, data.dtype) except AttributeError: #data in not array dinfo = 'length: %s' % len(data) info = '%s---\n%s' % (finfo, dinfo) if isinstance(info, Parameter): #CASE: layer was duplicated and [info] is a parameter for ch in info.children(): pLayer.addChild(ch.duplicate()) else: #info is given as text #LAYER INFO pLayer.addChild({ 'type': 'text', 'name': 'Info', 'value': info if info != None else '', 'readonly': True }) #LAYER CHANGES pLayer.addChild({ 'type': 'text', 'name': 'Changes', #'TODO: every change through a tool/scripts operation to be added here', 'value': changes if changes else '', #'readonly':True 'expanded': bool(changes) }) def addChange(self, change, index=None): if change is not None: ch = self.children() if index is not None: ch = [ch[index]] if type(change) not in (tuple, list): change = [change] * len(ch) for n, c in enumerate(ch): p = c.param('Changes') v = p.value() if not v: p.setValue(change[n]) p.setOpts(expanded=True) else: p.setValue('%s\n%s' % (p.value(), change[n])) def buildCopyToDisplayMenu(self, menuCopy, paramFile, method): ''' clear the context menu 'copy' add 'NEW' to copy a layer to a new display add names of all other displays connect all actions to respective display methods ''' menuCopy.clear() #NEW: if method == 'copy': m = self.display.copyLayerToNewDisplay else: m = self.display.moveLayerToNewDisplay menuCopy.addAction('NEW').triggered.connect( lambda checked, paramFile=paramFile, m=m: m(paramFile.parent( ).children().index(paramFile))) #OTHER DISPLAYS: if method == 'copy': m = self.display.copyLayerToOtherDisplay else: m = self.display.moveLayerToOtherDisplay for d in self.display.workspace.displays(): if d != self.display and d.widget.__class__ == self.display.widget.__class__: menuCopy.addAction(d.name()).triggered.connect( lambda checked, paramFile=paramFile, d=d, m=m: m( paramFile.parent().children().index(paramFile), d)) def getFileInfo(self, filename): ''' return general information of the given [filename], like it's size ''' f = QtCore.QFileInfo(filename) size = f.size() size_type = ['Byte', 'kB', 'MB', 'GB', 'TB', 'PB'] size_index = 0 while size > 1024: size /= 1024.0 size_index += 1 size = "%.3g %s" % (round(size, 3), size_type[size_index]) n = f.fileName() return '''File name:\t%s Folder:\t%s File type:\t%s File size:\t%s Last changed:\t%s''' % (n, f.filePath()[:-len(n)], f.suffix(), size, f.lastModified().toString()) #TODO: not used at the moment #but useful # def getMetaData(self, filename): # ''' # Find and format the meta data of [filename] # ''' # text = "" # if filename.filetype().lower() not in ('tif', 'tiff'): # #for all files except TIF # filename, realname = unicodeFilename(filename), filename # parser = createParser(filename, realname) # if not parser: # print "Unable to parse file" # try: # metadata = extractMetadata(parser) # except HachoirError, err: # #AttributeError because hachoir uses # print "Metadata extraction error: %s" % unicode(err) # metadata = None # if not metadata: # print "Unable to extract metadata" # else: # text = "\n".join(metadata.exportPlaintext()) # else: # #for TIF images # import exifread # # Open image file for reading (binary mode) # f = open(filename, 'rb') # # Return Exif tags # tag_dict = exifread.process_file(f) # for key, value in tag_dict.iteritems(): # text += "%s: %s\n" %(key, value) # return text
class DisplayDock(Dock): ''' A Dock container to... * OPEN and DISPLAY given input within a fitting display widget (e.g. display.Image for images) * adding, (re)moving, copying input LAYERS (e.g. multiple plots or images of the same size) * choosing the right TOOLS for the corresponding display widget * creating PREFERENCES in INFORMATION about the input ''' clicked = QtCore.pyqtSignal(object) #self closed = QtCore.pyqtSignal(object) #self sigLayerChanged = QtCore.pyqtSignal(object) #self sigNewLayer = QtCore.pyqtSignal(object) #self def __init__(self, number, workspace, origin=None, index=None, names=None, title='', data=None, axes=None, info=None, changes=None, openfiles=True): ''' ============ =============================================== Arguments Description ============ =============================================== number The display number, like 1,2... workspace The workspace within the display is names Path(s) of input files to open title ... of the DisplayDock data ... to display within the corresponding widget axes Instance of AxesContainer with names and scales for the axes scaling the data origin The origin display index The index of the origin layer info informational text about the input changes Additional changes done to the data openfiles [True/False] whether to open the files given in [filenames] ============ =============================================== ''' self.workspace = workspace self.number = number self.widget = None self._isclosed = False #GET VALUES FROM ORIGIN DISPLAY data, names, axes, title = self._extractInfoFromOrigin( origin, index, data, names, axes, title) if data is None and names is None and axes is None: raise Exception('either filenames, data or axes must be given') one_layer = False #FORMAT FILENAMES TO A LIST OF FILENAMES: if names is None: if data is not None: names = [None] * len(data) else: names = [] elif isinstance(names, basestring): names = [names] one_layer = True docktitle = self._getDockTitle(title, number, names) #INIT SUPER CLASS Dock.__init__(self, docktitle) #SETUP FILE READER: self.reader = None if names and data is None: #-->load input from file self.reader = getFileReader(names) self.reader = self.reader(weakref.proxy(self)) #GET AXES FROM INPUT READER IF NOT DEFINED YET: if axes is None: axes = self.reader.axes #CREATE/FORMAT AXES IF INPUT GIVES AS DATA: if axes is None: axes = ['x', 'y', '', 'i', 'j'][:data[0].ndim + 1] elif type(axes) == int: axes = ['x', 'y', '', 'i', 'j'][:axes] self.stack = _StackParameter(self) #PARAMETERS: self.p = Parameter.create(name='', type='empty') #TAB DISPLAYING PREFERENCES AND INPUT INFORMATION: self.tab = _DisplayTab(self) #FILL PARAMETERS: self.pTitle = self.p.addChild({ 'name': 'Title', 'type': 'str', 'value': '' }) self.pTitle.sigValueChanged.connect(self._setWidgetTitle) pSize = self.pTitle.addChild({ 'name': 'Size', 'type': 'int', 'value': 11 }) pSize.sigValueChanged.connect( lambda param, size: self.widget.setTitleSize(size)) self.pTitleFromLayer = self.pTitle.addChild({ 'name': 'From layer', 'type': 'bool', 'value': False }) self.pTitleFromLayer.sigValueChanged.connect( self._pTitleFromLayerChanged) self.pLimitLayers = self.p.addChild({ 'name': 'Limit Layers', 'type': 'bool', 'value': False }) self.pLimitLayers.sigValueChanged.connect(self._pLimitLayersChanged) self.pMaxLayers = self.pLimitLayers.addChild({ 'name': 'Max. Layers', 'type': 'int', 'value': 10, 'visible': False, 'limits': [1, 1e4] }) self.pMaxLayers.sigValueChanged.connect(self._limitLayers) #HANDLING THE INPUT LAYERS: self.p.addChild(self.stack) self.axes = AxesContainer(weakref.proxy(self), axes, weakref.proxy(self.stack)) #ADD AXES PARAMETERS: self.p.addChild(self.axes.p) #LIST OF ALL FITTING DISPLAY FIGURES: widgetList = _DisplayFigureList(self) #UPDATE DATA FROM FILE USING SPECIFIED PREFERENCES: #TODO: 'update' should only update file based data and no processed data if self.reader: # pUpdate = Parameter.create(**{ # 'type':'action', # 'name':'Update', # }) if self.reader.preferences: # pUpdate.addChild(self.reader.preferences) # pUpdate.sigActivated.connect(self.updateInput) self.p.addChild(self.reader.preferences) self.p.addChild(widgetList) #INIT WIDGET self.changeWidget(widgetList.getWidget()) #, data, names) #ADD DATA if len(names): if data is None and PathStr(names[0]).isfile(): self.addFiles(names, openfiles) #TODO: what to do with mixed file/non file input? else: if one_layer: self.addLayer(data, names[0], origin=origin) else: self.addLayers(data, names, origin=origin) def otherDisplaysOfSameType(self, includeThisDisplay=False): for d in self.workspace.displays(): if (isinstance(d.widget, self.widget.__class__) and (includeThisDisplay or d.name() != self.name())): yield d def _limitLayers(self, param, val): #remove surplus layers for _ in range(len(self.stack.childs) - val): self.stack.children()[0].remove() def _pLimitLayersChanged(self, p, val): p = self.pMaxLayers p.show(val) if val: self._limitLayers(None, p.value()) def _pTitleFromLayerChanged(self, param, val): self.pTitle.setOpts(readonly=val) w = self.widget if val: w.sigTimeChanged.connect(self._setTitleFromCurrentLayer) self._setTitleFromCurrentLayer(w.currentIndex) else: try: w.sigTimeChanged.disconnect(self._setTitleFromCurrentLayer) self._setWidgetTitle(None, self.pTitle.value()) except: pass def _setWidgetTitle(self, param, title): if not title: title = None self.widget.setTitle(title) def _setTitleFromCurrentLayer(self, index): try: self.widget.setTitle(self.stack.childs[index].name()) except IndexError: pass #there are no layers @staticmethod def _getDockTitle(title, number, names): ''' create a title for this display dock names -> list instances of PathStr ''' docktitle = '[%s] ' % number if title: docktitle += title if len(names) > 1: #if multiple files imported: #FOLDER IN TITLE dirname = PathStr(names[0]).dirname() if len(dirname) > 20: dirname = '~' + dirname[-20:] docktitle += "%s files from %s" % (len(names), dirname) elif names and names != [None]: #FILENAME IN TITLE name = PathStr(names[0]).basename() if len(name) > 20: name = name[:8] + '(...)' + name[-8:] docktitle += name return docktitle def _extractInfoFromOrigin(self, origin, index, data, names, axes, docktitle): ''' get data, filenames, axes from the origin display if there value is None ''' if origin: ch = origin.stack.childs if index is None: if data is None: data = origin.widget.getData() else: if data is None: data = origin.widget.getData(index) ch = [ch[index]] if names is None: names = [c.name() for c in ch] if index is not None: names = names[0] if axes is None: axes = origin.axes.copy() if not docktitle: docktitle = 'Child' docktitle = '%s of %s' % (docktitle, origin.shortName()) return data, names, axes, docktitle def shortName(self): return '[%s]' % self.number # def _getNDataLayers(self, axes, data): # ''' # define number of data layers interpreting [data] and it's [axes] # ''' # l = len(axes) # #get number of dimensions (nDim) and shape without the need of an ndarray: # try: # x = data # shape = [] # while True: # shape.append(len(x)) # x = x[0] # except TypeError: # ndim = len(shape) # except IndexError: # #has no layers jet # return 0 # if l == ndim + 1: # #stack with one layer or no stack: # nlayers = 1 # elif l == ndim or l == ndim-1: # #multiple layers: # nlayers = shape[0] # else: # raise Exception("number of axes doesn't fit to data shape") # return nlayers def mousePressEvent(self, event): ''' emits the signal 'clicked' if it is activated ''' self.clicked.emit(weakref.proxy(self)) def close(self): ''' emits the signal 'closed' ''' Dock.close(self) self._isclosed = True self.workspace.gui.undoRedo.displayClosed(self) self.closed.emit(self) self.widget.close() def isClosed(self): return self._isclosed def __getattr__(self, attr): ''' this method is to access display layers easily in the built-in python shell if attribute cannot be found: return data or data-layer if attribute = 'l' or 'l0'...'l[layerNumber]' ''' try: Dock.__getattr__(self, attr) except AttributeError: if attr[0] == 'l': a = attr[1:] if a != '': if not a.isdigit(): raise AttributeError( "display %s doesn't has attribute %s" % (self.name(), attr)) a = int(a) return self.widget.getData(a) return self.widget.getData() else: raise AttributeError("display %s doesn't has attribute %s" % (self.name(), attr)) def __setattr__(self, attr, value): ''' this method is to access display layers easily in the built-in python shell if attribute is 'l' or 'l0'...'l[layerNumber]': set all data of a certain data layer to [value] ''' if attr[0] == 'l': a = attr[1:] if not a: #make sure that this display is updated through automation: self.tab.automation.checkWidgetIsActive(self.widget) nLayers = len(self.stack.childs) # CASE 'l' n = self.widget.getNLayers(value) # n = self._getNDataLayers(self.axes, value) # print nLayers, n,55555555555 # if not n: # raise Exception("number of axes doesn't fit to data shape") if nLayers == n: #print value return self.widget.update(value) if nLayers != 0: #remove old layers: for i in range(nLayers): self.removeLayer(i) if n > 1: for layer in value: self.addLayer(data=layer) else: return self.addLayer(data=value) elif a.isdigit(): #make sure that this display is updated through automation: self.tab.automation.checkWidgetIsActive(self.widget) nLayers = len(self.stack.childs) # CASE 'l0'...'l[n]' a = int(a) if a == nLayers: #this index doesn't exist jet - so add a new layer return self.addLayer(data=value) return self.widget.update(value, index=a) Dock.__setattr__(self, attr, value) def duplicate(self): ''' create a new display with the same data and axes ''' d = self.workspace.addDisplay(origin=self, title='Duplicate') d.p.restoreState(self.p.saveState()) d.widget.restoreState(self.widget.saveState()) return d def adaptParamsToWidget(self): s = self.widget.shows_one_layer_at_a_time self.pTitleFromLayer.show(s) if not s: #cannot set title from layer if all layers are displayed #at the same time self._pTitleFromLayerChanged(None, False) def changeWidget(self, widgetCls): ''' change the display widget class and setup the toolbar ''' self._reloadWidgetCls(widgetCls) #to init the new toolbars: self.clicked.emit(self) self.adaptParamsToWidget() def reloadWidget(self): self._reloadWidgetCls(self.widget.__class__) def _reloadWidgetCls(self, widgetCls): data = None state = None toolbars = None if self.widget: data = self.widget.getData() state = self.widget.saveState() toolbars = self.widget.toolbars names = self.layerNames() self.widget = widgetCls(weakref.proxy(self), self.axes, data=data, names=names, toolbars=toolbars) if state: self.widget.restoreState(state) Dock.setWidget(self, self.widget) def insertRemovedLayer(self, index): ''' Insert a layer moved to [index] ''' self.widget.insertMovedLayer(index) def removeLayers(self): self.stack.clearChildren() self.widget.clear() self.filenames[:] = [] def removeLayer(self, index, toMove=False): ''' remove a data layer toMove=True => this layer will be moved within the stack ''' self.widget.removeLayer(index, toMove) def copyLayerToOtherDisplay(self, index, display): display.addLayer(origin=self, index=index) def moveLayerToOtherDisplay(self, index, display): display.addLayer(origin=self, index=index) self.stack.childs[index].remove() def copyLayerToNewDisplay(self, index): self.workspace.addDisplay(origin=self, index=index) def moveLayerToNewDisplay(self, index): self.copyLayerToNewDisplay(index) self.stack.childs[index].remove() def layerNames(self): ''' return the names of all data layers ''' return [ch.name() for ch in self.stack.children()] @staticmethod def _getLayerLabel(fname, label): if fname is None: if label is None: return 'unknown' return label if PathStr(fname).isfile(): fname = PathStr(fname).basename() if label is not None: return '%s - %s' % (label, fname) return fname def addLayer(self, data=None, filename=None, label=None, origin=None, index=None, info=None, changes=None, **kwargs): ''' Add a new layer to the stack ''' data, filename, _, _ = self._extractInfoFromOrigin( origin, index, data, filename, False, False) name = self._getLayerLabel(filename, label) #ADD TO PARAMETER TREE self.stack.buildLayer(filename, label=label, name=name, data=data, origin=origin.stack if origin else None, index=index, info=info, changes=changes) #WIDGET layer = self.widget.addLayer(name=name, data=data, **kwargs) if not self.widget.moveLayerToNewImage is None: print 'Move this layer to new display' #couldn't add new layer to stack: create a new display to show it self.workspace.addDisplay(origin=self, index=len(self.filenames) - 1, data=[data]) self.stack.childs[-1].remove() else: #LIMIT LAYERS if (self.pLimitLayers.value() and len(self.stack.childs) > self.pMaxLayers.value()): self.stack.childs[0].remove() #AUTOMATION self.sigNewLayer.emit(self) return layer def backupChangedLayer(self, backup=None, changes=None, index=None, **kwargs): #UNDO/REDO ur = self.workspace.gui.undoRedo if ur.isActive(): #index = kwargs.get('index', None) widget = self.widget if backup is None: data = widget.getData(index) if data is None: return backup = data.copy() name = self.shortName() if index is not None: name += "-%s" % index name += ": %s" % changes ur.add(display=self, name=name, undoFn=lambda i=index, d=backup: self.changeLayer( data=d, changes='undo', index=i, backup=False), redoFn=lambda d, i=index: self.changeLayer( data=d, changes='redo', index=i, backup=False), dataFn=lambda i=index, w=widget: w.getData(i).copy()) def changeAllLayers(self, data=None, changes=None, backup=True, **kwargs): if backup: self.backupChangedLayer(data, changes, **kwargs) self.widget.update(data, **kwargs) self.widget.updateView() for index in range(len(self.stack.childs)): self.stack.addChange(changes, index=index) self.sigLayerChanged.emit(self) def changeLayer(self, data=None, changes=None, index=None, backup=True, **kwargs): if backup: self.backupChangedLayer(data, changes, index, **kwargs) self.widget.update(data, index=index, **kwargs) self.widget.updateView() self.stack.addChange(changes, index=index) self.sigLayerChanged.emit(self) def showToolBar(self, name): ''' show an other toolbar of the same display.widget if hidden ''' #make given toolbar tool visible found = False for t in self.widget.toolbars: if t.name.lower() == name.lower(): t.setSelected(True) found = True break if not found: raise Exception('Toolbar [%s] doesnt exist' % name) else: return t def changeLayerFiles(self): filt = '*.' + ' *.'.join(self.reader.ftypes) fnames = self.workspace.gui.dialogs.getOpenFileNames(filter=filt) if fnames: self.removeLayers() self.addFiles(fnames) def changeLayerFile(self, index): ''' change the origin file of layer of [index] ''' filt = '*.' + ' *.'.join(self.reader.ftypes) fname = self.workspace.gui.dialogs.getOpenFileName( filter=filt, directory=self.filenames[index].dirname()) if fname is not None: self._readFiles([fname], self._updateFiles) @property def filenames(self): return removeDuplicates( [c.opts['filename'] for c in self.stack.childs]) def layerIndex(self, filename, layername): for n, ch in enumerate(self.stack.childs): if ch.opts['filename'] == filename and ch.opts[ 'layername'] == layername: return n return None def updateInput(self): ''' Update the input coming from file ''' self._readFiles(self.filenames, self._updateFiles) def _updateFiles(self, data, fnames, labels): old_fnames = [c.opts['filename'] for c in self.stack.childs] old_labels = [c.opts['layername'] for c in self.stack.childs] changes = 'reloaded at ' + time.strftime("%c") if fnames == old_fnames and labels == old_labels: self.changeAllLayers(data, changes=changes, backup=True) else: #layers cannot be easily exchanged, so: for d, f, l in zip(data, fnames, labels): index = self.layerIndex(f, l) if index is not None: self.changeLayer(d, changes='reloaded at ' + time.strftime("%c"), index=index) else: #TODO: insert layer behind last filename layer self.addLayer(filename=f, label=l, data=d) def addLayers(self, datas, fnames, labels=None, **kwargs): if labels is None: labels = [None] * len(fnames) for l, f, d in zip(labels, fnames, datas): self.addLayer(data=d, filename=f, label=l, **kwargs) def addFiles(self, fnames, openfiles=True): ''' Add input from file(s) to the stack -> read input -> add layer ''' ff = self.filenames for f in list(fnames): if f in ff: print "file '%s' already in display '%s'" % (f, self.name()) #fnames.remove(f) if fnames: if not self.reader: self.reader = getFileReader(fnames) self.reader = self.reader(self) if openfiles: self._readFiles(fnames, self.addLayers) else: d = [None] * len(fnames) self.addLayers(d, fnames, d) def saveState(self): state = {} # path += ('display',str(self.number)) #layers state['stack'] = self.stack.saveState() #automation state['automation'] = self.tab.automation.saveState() #parameters state['parameters'] = self.p.saveState() # session.addContentToSave(self.p.saveState(), *path+('parameters.txt',)) #dock state['dock'] = self.label.maximized # session.addContentToSave(self.label.maximized, *path+('dock.txt',)) #to init the current toolbars: self.clicked.emit(self) #widget state['widget'] = self.widget.saveState() return state @property def tools(self): return self.widget.tools def restoreState(self, state): #layers self.stack.restoreState(state['stack']) #automation self.tab.automation.restoreState(state['automation']) #parameters #TODO: this is not clean - stack is mentioned above and already restore its parameters... self.stack._valuesChanged() self.p.restoreState(state['parameters']) #widget self.widget.restoreState(state['widget']) #dock if state['dock']: self.maximize() def openFilesFunctions(self): return [lambda f=f: self.reader.open(f)[0] for f in self.filenames] def _readFiles(self, filenames, dataFunction): try: self.reader.done.disconnect() except TypeError: pass # nothing connected so far self.reader.done.connect(dataFunction) self.reader.start(filenames)
def _intersectionPointsAndAngles(r, ratioEllispeRectangle): ''' return all 8 x and y coords of lines build by intersection of ellipse and rect ''' w = r.width() h = r.height() x1 = 0.5 * w y2 = 0.5 * h #ellipse parameters: a = x1 * ratioEllispeRectangle b = y2 * ratioEllispeRectangle #intersection coords in the 1st quadrant with center=(0,0): y1 = ((1 - x1**2 / a**2) * b**2)**0.5 x2 = ((1 - y2**2 / b**2) * a**2)**0.5 c = r.center() cx = c.x() cy = c.y() #edge points: p1 = QtCore.QPointF(cx + x1, cy + y1) p2 = QtCore.QPointF(cx + x2, cy + y2) p3 = QtCore.QPointF(cx - x2, cy + y2) p4 = QtCore.QPointF(cx - x1, cy + y1) p5 = QtCore.QPointF(cx - x1, cy - y1) p6 = QtCore.QPointF(cx - x2, cy - y2) p7 = QtCore.QPointF(cx + x2, cy - y2) p8 = QtCore.QPointF(cx + x1, cy - y1) #angle in as degree*16 (needed in .drawArc) a1 = int(QtCore.QLineF(c, p1).angle() * 16) a2 = int(QtCore.QLineF(c, p2).angle() * 16) a4 = int(QtCore.QLineF(c, p4).angle() * 16) a6 = int(QtCore.QLineF(c, p6).angle() * 16) a8 = int(QtCore.QLineF(c, p8).angle() * 16) arc_length = a1 - a2 return (p1, p2, p3, p4, p5, p6, p7, p8), (a2, a4, a6, a8), arc_length
def painterPath(self): p = self.state['pos'] s = self.state['size'] path = QPainterPath() path.addEllipse(QtCore.QRectF(p[0], p[1], s[0], s[1])) return path
class ImageWidget(DisplayWidget, ImageView, PyqtgraphgDisplayBase): ''' A pyqtgraph.ImageView with methods to add/move/remove images as layer or colour-layer ''' dimensions = (3, 4) icon = PathStr.getcwd('dataArtist').join('media', 'icons', 'image.svg') sigOverlayAdded = QtCore.pyqtSignal(object, object, object) #item, name, tip sigOverlayRemoved = QtCore.pyqtSignal(object) #item shows_one_layer_at_a_time = True def __init__(self, display, axes, data=None, names=None, **kwargs): for a in axes: a.setPen() # update colour theme ImageView.__init__(self, view=pg.PlotItem(axisItems={ 'bottom': axes[0], 'left': axes[1] })) PyqtgraphgDisplayBase.__init__(self) DisplayWidget.__init__(self, **kwargs) self.display = display self.moveLayerToNewImage = None self.cItems = OrderedDict() #colorlayerItems #for unified access within different widgets: self.item = self.imageItem self.setTitle = self.view.setTitle self._firstTime = True self._changed = False self._timeline_visible = True self._image_redefined = False self._moved_layer = None self._set_kwargs = {} self.setOpts(discreteTimeSteps=True) #make splitter an unchangeable small grey line: s = self.ui.splitter s.handle(1).setEnabled(False) s.setStyleSheet("QSplitter::handle{background-color: grey}") s.setHandleWidth(2) #TODO: better would be to init imageView with given histrogramAxis # ... but this is easier: axes[2].sigLabelChanged.connect(self.setHistogramLabel) self.setHistogramLabel(axes[2].labelText) axes[2].sigRangeChanged.connect(self.ui.histogram.axis.setRange) axes[2].sigFontSizeChanged.connect(self._setHistogramFontSize) #hide buttons self.ui.roiBtn.hide() self.ui.normBtn.hide() #fixed height for time axis: self.ui.splitter.setSizes([self.height() - 35, 35]) self.ui.splitter.setStretchFactor(0, 1) #Remove ROI plot: self.ui.roiPlot.setMouseEnabled(False, False) self.ui.roiPlot.hide() if data is not None: self.update(data) self.updateView() @staticmethod def getNLayers(data): s = data.shape l = data.ndim if l == 2: return 1 if l == 3: if s[-1] in (3, 4): return 1 if l == 4: if s[-1] in (3, 4): return s[0] return 0 def close(self): self.clear() #free memory try: ImageView.close(self) except TypeError: pass def roiClicked(self): '''not used''' pass def roiChanged(self): '''not used''' pass def saveState(self): state = DisplayWidget.saveState(self) state['view'] = self.view.vb.getState() state['histogram'] = self.ui.histogram.vb.getState() state['histoRegion'] = self.ui.histogram.getLevels() state['colorbar'] = self.ui.histogram.gradient.saveState() state['image'] = self.image return state def restoreState(self, state): self.view.vb.setState(state['view']) self.ui.histogram.vb.setState(state['histogram']) self.ui.histogram.setLevels(*state['histoRegion']) self.ui.histogram.gradient.restoreState(state['colorbar']) img = state['image'] if img is not None: self.setImage(img, autoRange=False, autoLevels=False, autoHistogramRange=False) DisplayWidget.restoreState(self, state) def showTimeline(self, show=True): self._timeline_visible = show if show and len(self.image) > 1: self.ui.roiPlot.show() else: self.ui.roiPlot.hide() def setColorLayer(self, layer, **kwargs): ''' same as addColorLayer, but replaces citem, if existent ''' name = kwargs.get('name', None) if name: item = self.cItems.get(name, None) if item: return item.setLayer(layer) return self.addColorLayer(layer, **kwargs) def addColorLayer(self, layer=None, name='Unnamed', tip='', color=None, alpha=0.5): ''' add a [layer], a np.array (2d or 3d), as colour overlay to the image ''' if layer is None: s = self.image.shape if len(s) == 4: # multi layer color image s = s[1:-1] elif len(s) == 3: if s[-1] == 3: # single layer color image s = s[:-1] else: #multi layer grey image s = s[1:] layer = np.zeros(shape=s) if isColor(layer): layer = toGray(layer) if color is None: #set colour as function of the number of colorItems: color = pg.intColor(len(self.cItems)).rgb() cItem = ColorLayerItem(layer, imageWidget=self, color=color, alpha=alpha) name = incrementName(self.cItems.keys(), name) self.cItems[name] = cItem self.view.addItem(cItem) self.sigOverlayAdded.emit(cItem, name, tip) return cItem def removeColorLayer(self, nameOrItem): if isinstance(nameOrItem, ColorLayerItem): for name, item in self.cItems.iteritems(): if item == nameOrItem: break else: name = nameOrItem item = self.cItems.pop(name) self.view.removeItem(item) self.sigOverlayRemoved.emit(item) del item def _setHistogramFontSize(self, ptSize): ''' set font size of the histogram and change the size of the viewBox that contain the image and the histogram accordingly ''' vb = self.ui.histogram.vb vb.setFixedWidth(vb.width() * (9.0 / ptSize)) self.ui.histogram.axis.setFontSize(ptSize) def insertLayer(self, index, name=None, data=None): ''' insert a new image as layer ...add new / override existing layer ...show dialog when data.shape != self.image.shape ''' if data is not None: try: self.image = np.insert(self.image, index, data, axis=0) except IndexError: index = -1 self.image = np.insert(self.image, index, data, axis=0) except (ValueError, MemoryError): if index == 0 and len(self.image) == 1: #replace, if only has one layer self.image = np.expand_dims(data, axis=0) else: if type(data) == list: data = data[0] s1 = self.image[0].shape s2 = data.shape #LAYER COLOR IS DIFFERENT: c1 = isColor(self.image[0]) c2 = isColor(data) if c1 and not c2: data = toColor(data) s2 = data.shape elif not c1 and c2: self.image = toColor(self.image) s1 = self.image[0].shape if s1 != s2: #NEW LAYER SHAPE DOESNT FIT EXISTING: ###show DIALOG##### d = DifferentShapeDialog(name, s1, s2) d.exec_() r = d.opt if r == d.optNewDisplay: self.moveLayerToNewImage = index return elif r == d.optCut: data = data[0:s1[0], 0:s1[1]] if data.shape != s1: d = np.zeros(s1) d[0:s2[0], 0:s2[1]] = data data = d elif r == d.optResize: data = cv2.resize(data, (s1[1], s1[0])) elif r == d.optWarp: data = PerspectiveTransformation( self.image[-1]).fitImg(data) self.image = np.insert(self.image, index, data, axis=0) self.currentIndex = index self.setImage(self.image) return self.image[index] def insertMovedLayer(self, index): self.insertLayer(index, data=self._moved_layer) def addLayer(self, name=None, data=None, index=None): ''' add a new image as layer the the and of the image stack ''' if self.image is not None: if index is None: index = len(self.image) self.insertLayer(index, data=data) else: self.update(data) self.updateView() if self.image is not None: self.setCurrentIndex(len(self.image)) def removeLayer(self, index, toMove=False): if self.moveLayerToNewImage is None or self.moveLayerToNewImage != index: if toMove: if self.image is not None: self._moved_layer = self.image[index] else: self._moved_layer = None self.image = np.delete(self.image, index, axis=0) s = self.image.shape[0] if s == 0: self.setImage(np.zeros((1, 3, 3))) self.image = None else: #TODO: has last index, if addWidget was called before #therefore index cannot be preserved when layers are limited i = self.currentIndex if index != 0 and i == index: i -= 1 self.setImage(self.image) self.setCurrentIndex(i) #reset: self.moveLayerToNewImage = None def clear(self): self.setImage(np.zeros((1, 3, 3))) self.image = None for name in self.cItems: self.removeColorLayer(name) def getData(self, index=None): ''' return all image layers or one specified by index ''' if self.image is None: return None if index is not None: return self.image[index] return self.image def update(self, data=None, index=None, label=None, **kwargs): ''' update either the full image or an image layer ''' if data is not None: data = np.asarray(data) if index is not None and self.image is not None: try: self.image[index] = data except (ValueError, IndexError): self.insertLayer(index, name=label, data=data) else: #set all layers if (data.ndim == 2 #single grayscale or data.ndim == 3 and data.shape[2] in (3, 4)): #single color data = np.expand_dims(data, axis=0) self.image = data self._image_redefined = True self._set_kwargs = kwargs self._set_index = index self._changed = True def updateView(self, force=False): ''' update the visual representation ''' if self.image is not None and (force or self._changed): if self._image_redefined or self._set_kwargs or self._firstTime: self.setImage(self.image, **self._set_kwargs) self._firstTime = False self._image_redefined = False elif force or self._set_index == None or self._set_index == self.currentIndex: self.imageDisp = None # needed by ImageView to set histogram levels self.updateImage(**self._set_kwargs) if self.opts['autoLevels']: self.autoLevels() self._changed = False def updateImage(self, autoHistogramRange=None): ''' overwrite original method to hide timeline when 3d image has only one layer ''' ## Redraw image on screen if self.image is None: return if autoHistogramRange == None: autoHistogramRange = self.opts['autoHistogramRange'] image = self.getProcessedImage() if autoHistogramRange: self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) if self.axes['t'] is not None: #show/hide timeline: self.ui.roiPlot.setVisible(self.image.shape[0] > 1 and self._timeline_visible) image = image[self.currentIndex] # ensure initial right scaling (not done when image is [0...1] # TODO: fix the origin bug if image.dtype == bool: return self.imageItem.updateImage(image, levels=(0., 1.)) else: self.imageItem.updateImage(image)
def _appendMenubarAndPreferences(self): m = self.menuBar() m.setFixedHeight(25) # m.setMaximumHeight(25) m.aboutWidget.setModule(dataArtist) m.aboutWidget.setInstitutionLogo( MEDIA_FOLDER.join('institution_logo.svg')) #hide the menu so toolbars can only be show/hidden via gui->view->toolbars: m.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) self.undoRedo = UndoRedo(MEDIA_FOLDER) self.gTools = GlobalTools() self.gTools.addWidget(self.undoRedo) m.setCornerWidget(self.gTools) #APPEND PREFERENCES t = m.file_preferences.tabs t.addTab(PreferencesView(self), 'View') t.addTab(self.pref_import, 'Import') t.addTab(PreferencesCommunication(self), 'Communication') #APPEND MENUBAR #MENU - FILE f = m.menu_file p = f.action_preferences action_file = QtGui.QAction('&Import', f) action_file.triggered.connect(self.openFile) action_file.setShortcut( QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_I)) f.insertAction(p, action_file) f.insertSeparator(p) #MENU VIEW v = m.menu_view #ACTION PRINT VIEW aPrintView = QtGui.QAction('Print view', v) aPrintView.setCheckable(True) aPrintView.triggered.connect( lambda checked: self.currentWorkspace().setPrintView(checked)) v.addAction(aPrintView) #SHOW/HIDE history aHistory = QtGui.QAction('Program history', v) aHistory.setShortcut(QtCore.Qt.Key_F4) aHistory.setCheckable(True) def showhideHistory(checked): s = self.currentWorkspace().middle_splitter r = s.getRange(1)[1] if checked: r /= 1.5 return s.moveSplitter(r, 1) def isHistoryVisible(): s = self.currentWorkspace().middle_splitter aHistory.setChecked(s.sizes()[1] != 0) aHistory.triggered.connect(showhideHistory) v.aboutToShow.connect(isHistoryVisible) v.addAction(aHistory) #SHOW/HIDE preferences aPref = QtGui.QAction('Dock preferences', v) aPref.setShortcut(QtCore.Qt.Key_F3) aPref.setCheckable(True) def showhidePref(checked): s = self.currentWorkspace().vert_splitter r = s.getRange(1)[1] if checked: r /= 3 else: r = 0 return s.moveSplitter(r, 1) def isPrefVisible(): w = self.currentWorkspace() s = w.vert_splitter aPref.setChecked(s.sizes()[0] != 0) aPref.setEnabled(w.displayPrefTabs.isVisible()) aPref.triggered.connect(showhidePref) v.aboutToShow.connect(isPrefVisible) v.addAction(aPref) #ACTION VIEW2CLIPBOARD aClipboard = QtGui.QAction('Copy view to clipboard', v) aClipboard.triggered.connect( lambda checked: self.currentWorkspace().copyViewToClipboard()) v.addAction(aClipboard) #ACTION Display2CLIPBOARD aClipboard = QtGui.QAction('Copy active display to clipboard', v) aClipboard.triggered.connect(lambda checked: self.currentWorkspace(). copyCurrentDisplayToClipboard()) v.addAction(aClipboard) #MENU - TOOLS t = m.menu_tools = QtGui.QMenu('Dock') m.insertMenuBefore(m.menu_workspace, t) #ADD DISPLAY mDisplay = t.addMenu('Add Display') for i, name in ( #(1, 'Dot'), (2, 'Graph'), (3, 'Image/Video'), #(4, 'Surface') #TODO: #(4, 'TODO: Surface'), #(5, 'TODO: Volume') ): mDisplay.addAction('%sD - %s' % (i - 1, name)).triggered.connect( lambda checked, i=i: self.currentWorkspace().addDisplay(axes=i )) #ADD TABLE t.addAction('Add Table').triggered.connect( lambda: self.currentWorkspace().addTableDock()) #ADD NOTEPAD t.addAction('Add Notepad').triggered.connect( lambda: self.currentWorkspace().addTextDock()) t.addSeparator() #DUPLICATE CURRENT DOCK t.addAction('Duplicate current display').triggered.connect( self._duplicateCurrentDiplay) self._m_duplDisp = t.addMenu('Move current display to other workspace') self._m_duplDisp.aboutToShow.connect(self._fillMenuDuplicateToOtherWS) #MENU - TOOLBARS self.menu_toolbars = QtGui.QMenu('Toolbars', m) self.connect(self.menu_toolbars, QtCore.SIGNAL("hovered(QAction *)"), lambda action, m=self.menu_toolbars: _showActionToolTipInMenu(m, action)) #SHOW ALL TOOLBARS - ACTION a = self.menu_toolbars.a_show = QtGui.QAction('show', m) f = a.font() f.setBold(True) a.setFont(f) a.setCheckable(True) a.setChecked(True) a.triggered.connect(self._toggleShowSelectedToolbars) self.menu_toolbars.aboutToShow.connect(self._listToolbarsInMenu) m.insertMenuBefore(m.menu_workspace, self.menu_toolbars) #MENU HELP m.menu_help.addAction('User manual').triggered.connect( lambda checked: os.startfile(HELP_FILE)) #TUTORIALS ####not used at the moment # self.m_tutorials = TutorialMenu( # tutorialFolder=PathStr.getcwd('dataArtist').join('tutorials'), # openFunction=self._openFnForTutorial, # saveFunction=self.app.session.blockingSave) # m.menu_help.addMenu(self.m_tutorials) m.menu_help.addAction('Online tutorials').triggered.connect( lambda checked: os.startfile( 'http://www.youtube.com/channel/UCjjngrC3jPdx1HL8zJ8yqLQ')) m.menu_help.addAction('Support').triggered.connect( lambda checked: os.startfile( 'https://github.com/radjkarl/dataArtist/issues'))
def setCircle(self, _dx, dy): n = self.nCells[1] o = max(0, 2 * (dy - (0.5 / n))) return self.setCircleOffs(QtCore.QSizeF(0, o))
def open(self, filename): prefs = self.preferences #VARIABLES: buff = prefs.pBuffer.value() step = prefs.pReadEveryNLine.value() stop_at_line = prefs.pHasStopLine.value() stop_n = prefs.pStopLine.value() x_col = prefs.pXColumn.value() has_x_col = prefs.pHasXColumn.value() self.n_line = 0 #number of current line step_len = 0 #number of lines in the read file_part n_defective_lines = 0 # number of corrupted lines i = 0 # current index in data array at_end = False # is file the the end labels = filename #COUNT LINES: if stop_at_line: self.nLines = stop_n else: modified = QtCore.QFileInfo(filename).lastModified().toString() #ONLY OF THIS IS A NEW FILE OR THE FILE WAS MODIFIED: if not (self._lastFilename == filename and self._lastModified == modified): self.nLines = countLines(filename, prefs.pBuffer.value()) self._lastnLines = self.nLines self._lastFilename = filename self._lastModified = modified else: self.nLines = self._lastnLines #GET NUMBER OF COLUMNS: with open(filename, 'r') as f: #FIND SEPARATOR: if prefs.pFindSeparator.value(): prefs.pSeparator.setValue(self._findSeparator(f)) separator = prefs.separators[prefs.pSeparator.value()] #DEFINE NUMBER OF COLUMNS: n_col = f.readline().count(separator) + 1 f.seek(0) #back to begin #FILTER COLUMNS: filter_columns = prefs.pFilterColumns.value() if filter_columns: c = [ch.value() for ch in prefs.pFilterColumns.children()[1:]][:n_col] #ADD X COLUMN TO THE BEGINNING: if has_x_col: if x_col not in c: c.insert(0, x_col) n_col = len(c) col_filter = itemgetter(*c) elif n_col > 1: col_filter = lambda l: l[:n_col] else: col_filter = lambda l: l[0] #GET FIRST LINE NAMES: fline = prefs.pFirstLine.value() if fline != '-': names = col_filter(f.readline().split(separator)) if fline == 'axes names': for n, ax in enumerate(self.display.axes): ax.p.setValue(names[n]) else: #plot names: labels = names if has_x_col: labels.pop(x_col) #JUMP TO START POSITION: startline = prefs.pStartLine.value() if startline: f.readline(startline) self.n_line = startline #PRINT FIRST 10 LINES: if prefs.pPrintFirstLines.value(): startPos = f.tell() print '==========' for line in f.readlines(min(self.nLines, 10)): #TODO: do the check for \n only once # ??? doesn't have every line \n at the end?? #print line[-2:] #if line[-2:] == '\n': # line = line[:-2] print '[%s]: %s' % (self.n_line, line) print '--> %s\n' % str(col_filter(line.split(separator))) f.seek(startPos) print '==========' #CREATE DATA ARRAY: shape = self.nLines / step if n_col == 0: raise Exception('no columns given') elif n_col > 1: shape = (shape, n_col) data = np.empty(shape=shape, dtype=prefs.dtypes[prefs.pDType.value()]) #MAIN LOOP: while not self.canceled: #READ PART OF THE FILE: file_piece = f.readlines(buff) l = len(file_piece) if not l: break for n, line in enumerate(file_piece[::step]): #line = line[:-1] #FILTER COLUMNS: line = col_filter(line.split(separator)) self.n_line = n + step_len #ADD LINE TO ARRAY: try: data[i] = line i += 1 except ValueError: n_defective_lines += 1 #CHECK BREAK CRITERIA: if stop_at_line and self.n_line >= stop_n: at_end = True break if at_end: break step_len += l data = data[:i] print '%s lines were corrupted' % n_defective_lines #SPLIT ARRAY IF NEEDED: if (has_x_col and n_col > 2) or (not has_x_col and n_col > 1): if has_x_col: x = data[:, x_col] #GET Y COLUMNS THROUGH REMOVING THE X COLUMN: y_cols = np.c_[data[:, :x_col], data[:, x_col + 1:]] else: y_cols = data l = [] #CREATE TUPLE OF [y_n] OR [x, y_n] arrays: for n in range(y_cols.shape[1]): y = y_cols[:, n] if has_x_col: y = np.c_[x, y] l.append(y) return tuple(l), labels return [data], labels
def homography(self): t = QtGui.QTransform() poly = QtGui.QPolygonF([QtCore.QPointF(*v) for v in self.vertices()]) QtGui.QTransform.squareToQuad(poly, t) self._homography = t return t