Improve ImageItem performance by scaling LUT instead of image when possible.
Moved eq function from flowcharts to main function library to support this. Bonus: fixed a flowchart bug (backspace deletes wrong connector) while I was in there.
This commit is contained in:
parent
4be2869773
commit
70482432b8
@ -91,6 +91,8 @@ Mesh Generation Functions
|
||||
Miscellaneous Functions
|
||||
-----------------------
|
||||
|
||||
.. autofunction:: pyqtgraph.eq
|
||||
|
||||
.. autofunction:: pyqtgraph.arrayToQPath
|
||||
|
||||
.. autofunction:: pyqtgraph.pseudoScatter
|
||||
|
@ -4,72 +4,27 @@ from ..widgets.GraphicsView import GraphicsView
|
||||
from ..GraphicsScene import GraphicsScene
|
||||
from ..graphicsItems.ViewBox import ViewBox
|
||||
|
||||
#class FlowchartGraphicsView(QtGui.QGraphicsView):
|
||||
|
||||
class FlowchartGraphicsView(GraphicsView):
|
||||
|
||||
sigHoverOver = QtCore.Signal(object)
|
||||
sigClicked = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, widget, *args):
|
||||
#QtGui.QGraphicsView.__init__(self, *args)
|
||||
GraphicsView.__init__(self, *args, useOpenGL=False)
|
||||
#self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(255,255,255)))
|
||||
self._vb = FlowchartViewBox(widget, lockAspect=True, invertY=True)
|
||||
self.setCentralItem(self._vb)
|
||||
#self.scene().addItem(self.vb)
|
||||
#self.setMouseTracking(True)
|
||||
#self.lastPos = None
|
||||
#self.setTransformationAnchor(self.AnchorViewCenter)
|
||||
#self.setRenderHints(QtGui.QPainter.Antialiasing)
|
||||
self.setRenderHint(QtGui.QPainter.Antialiasing, True)
|
||||
#self.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
|
||||
#self.setRubberBandSelectionMode(QtCore.Qt.ContainsItemBoundingRect)
|
||||
|
||||
def viewBox(self):
|
||||
return self._vb
|
||||
|
||||
|
||||
#def mousePressEvent(self, ev):
|
||||
#self.moved = False
|
||||
#self.lastPos = ev.pos()
|
||||
#return QtGui.QGraphicsView.mousePressEvent(self, ev)
|
||||
|
||||
#def mouseMoveEvent(self, ev):
|
||||
#self.moved = True
|
||||
#callSuper = False
|
||||
#if ev.buttons() & QtCore.Qt.RightButton:
|
||||
#if self.lastPos is not None:
|
||||
#dif = ev.pos() - self.lastPos
|
||||
#self.scale(1.01**-dif.y(), 1.01**-dif.y())
|
||||
#elif ev.buttons() & QtCore.Qt.MidButton:
|
||||
#if self.lastPos is not None:
|
||||
#dif = ev.pos() - self.lastPos
|
||||
#self.translate(dif.x(), -dif.y())
|
||||
#else:
|
||||
##self.emit(QtCore.SIGNAL('hoverOver'), self.items(ev.pos()))
|
||||
#self.sigHoverOver.emit(self.items(ev.pos()))
|
||||
#callSuper = True
|
||||
#self.lastPos = ev.pos()
|
||||
|
||||
#if callSuper:
|
||||
#QtGui.QGraphicsView.mouseMoveEvent(self, ev)
|
||||
|
||||
#def mouseReleaseEvent(self, ev):
|
||||
#if not self.moved:
|
||||
##self.emit(QtCore.SIGNAL('clicked'), ev)
|
||||
#self.sigClicked.emit(ev)
|
||||
#return QtGui.QGraphicsView.mouseReleaseEvent(self, ev)
|
||||
|
||||
class FlowchartViewBox(ViewBox):
|
||||
|
||||
def __init__(self, widget, *args, **kwargs):
|
||||
ViewBox.__init__(self, *args, **kwargs)
|
||||
self.widget = widget
|
||||
#self.menu = None
|
||||
#self._subMenus = None ## need a place to store the menus otherwise they dissappear (even though they've been added to other menus) ((yes, it doesn't make sense))
|
||||
|
||||
|
||||
|
||||
|
||||
def getMenu(self, ev):
|
||||
## called by ViewBox to create a new context menu
|
||||
@ -84,26 +39,3 @@ class FlowchartViewBox(ViewBox):
|
||||
menu = self.widget.buildMenu(ev.scenePos())
|
||||
menu.setTitle("Add node")
|
||||
return [menu, ViewBox.getMenu(self, ev)]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##class FlowchartGraphicsScene(QtGui.QGraphicsScene):
|
||||
#class FlowchartGraphicsScene(GraphicsScene):
|
||||
|
||||
#sigContextMenuEvent = QtCore.Signal(object)
|
||||
|
||||
#def __init__(self, *args):
|
||||
##QtGui.QGraphicsScene.__init__(self, *args)
|
||||
#GraphicsScene.__init__(self, *args)
|
||||
|
||||
#def mouseClickEvent(self, ev):
|
||||
##QtGui.QGraphicsScene.contextMenuEvent(self, ev)
|
||||
#if not ev.button() in [QtCore.Qt.RightButton]:
|
||||
#self.sigContextMenuEvent.emit(ev)
|
@ -6,7 +6,6 @@ from .Terminal import *
|
||||
from ..pgcollections import OrderedDict
|
||||
from ..debug import *
|
||||
import numpy as np
|
||||
from .eq import *
|
||||
|
||||
|
||||
def strDict(d):
|
||||
@ -261,7 +260,7 @@ class Node(QtCore.QObject):
|
||||
for k, v in args.items():
|
||||
term = self._inputs[k]
|
||||
oldVal = term.value()
|
||||
if not eq(oldVal, v):
|
||||
if not fn.eq(oldVal, v):
|
||||
changed = True
|
||||
term.setValue(v, process=False)
|
||||
if changed and '_updatesHandled_' not in args:
|
||||
|
@ -4,8 +4,7 @@ import weakref
|
||||
from ..graphicsItems.GraphicsObject import GraphicsObject
|
||||
from .. import functions as fn
|
||||
from ..Point import Point
|
||||
#from PySide import QtCore, QtGui
|
||||
from .eq import *
|
||||
|
||||
|
||||
class Terminal(object):
|
||||
def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None):
|
||||
@ -29,9 +28,6 @@ class Terminal(object):
|
||||
============== =================================================================================
|
||||
"""
|
||||
self._io = io
|
||||
#self._isOutput = opts[0] in ['out', 'io']
|
||||
#self._isInput = opts[0]] in ['in', 'io']
|
||||
#self._isIO = opts[0]=='io'
|
||||
self._optional = optional
|
||||
self._multi = multi
|
||||
self._node = weakref.ref(node)
|
||||
@ -68,7 +64,7 @@ class Terminal(object):
|
||||
"""If this is a single-value terminal, val should be a single value.
|
||||
If this is a multi-value terminal, val should be a dict of terminal:value pairs"""
|
||||
if not self.isMultiValue():
|
||||
if eq(val, self._value):
|
||||
if fn.eq(val, self._value):
|
||||
return
|
||||
self._value = val
|
||||
else:
|
||||
@ -81,11 +77,6 @@ class Terminal(object):
|
||||
if self.isInput() and process:
|
||||
self.node().update()
|
||||
|
||||
## Let the flowchart handle this.
|
||||
#if self.isOutput():
|
||||
#for c in self.connections():
|
||||
#if c.isInput():
|
||||
#c.inputChanged(self)
|
||||
self.recolor()
|
||||
|
||||
def setOpts(self, **opts):
|
||||
@ -94,7 +85,6 @@ class Terminal(object):
|
||||
self._multiable = opts.get('multiable', self._multiable)
|
||||
if 'multi' in opts:
|
||||
self.setMultiValue(opts['multi'])
|
||||
|
||||
|
||||
def connected(self, term):
|
||||
"""Called whenever this terminal has been connected to another. (note--this function is called on both terminals)"""
|
||||
@ -109,12 +99,10 @@ class Terminal(object):
|
||||
if self.isMultiValue() and term in self._value:
|
||||
del self._value[term]
|
||||
self.node().update()
|
||||
#self.recolor()
|
||||
else:
|
||||
if self.isInput():
|
||||
self.setValue(None)
|
||||
self.node().disconnected(self, term)
|
||||
#self.node().update()
|
||||
|
||||
def inputChanged(self, term, process=True):
|
||||
"""Called whenever there is a change to the input value to this terminal.
|
||||
@ -178,7 +166,6 @@ class Terminal(object):
|
||||
return term in self.connections()
|
||||
|
||||
def hasInput(self):
|
||||
#conn = self.extendedConnections()
|
||||
for t in self.connections():
|
||||
if t.isOutput():
|
||||
return True
|
||||
@ -186,17 +173,10 @@ class Terminal(object):
|
||||
|
||||
def inputTerminals(self):
|
||||
"""Return the terminal(s) that give input to this one."""
|
||||
#terms = self.extendedConnections()
|
||||
#for t in terms:
|
||||
#if t.isOutput():
|
||||
#return t
|
||||
return [t for t in self.connections() if t.isOutput()]
|
||||
|
||||
|
||||
def dependentNodes(self):
|
||||
"""Return the list of nodes which receive input from this terminal."""
|
||||
#conn = self.extendedConnections()
|
||||
#del conn[self]
|
||||
return set([t.node() for t in self.connections() if t.isInput()])
|
||||
|
||||
def connectTo(self, term, connectionItem=None):
|
||||
@ -210,12 +190,6 @@ class Terminal(object):
|
||||
for t in [self, term]:
|
||||
if t.isInput() and not t._multi and len(t.connections()) > 0:
|
||||
raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, list(t.connections().keys())))
|
||||
#if self.hasInput() and term.hasInput():
|
||||
#raise Exception('Target terminal already has input')
|
||||
|
||||
#if term in self.node().terminals.values():
|
||||
#if self.isOutput() or term.isOutput():
|
||||
#raise Exception('Can not connect an output back to the same node.')
|
||||
except:
|
||||
if connectionItem is not None:
|
||||
connectionItem.close()
|
||||
@ -223,18 +197,12 @@ class Terminal(object):
|
||||
|
||||
if connectionItem is None:
|
||||
connectionItem = ConnectionItem(self.graphicsItem(), term.graphicsItem())
|
||||
#self.graphicsItem().scene().addItem(connectionItem)
|
||||
self.graphicsItem().getViewBox().addItem(connectionItem)
|
||||
#connectionItem.setParentItem(self.graphicsItem().parent().parent())
|
||||
self._connections[term] = connectionItem
|
||||
term._connections[self] = connectionItem
|
||||
|
||||
self.recolor()
|
||||
|
||||
#if self.isOutput() and term.isInput():
|
||||
#term.inputChanged(self)
|
||||
#if term.isInput() and term.isOutput():
|
||||
#self.inputChanged(term)
|
||||
self.connected(term)
|
||||
term.connected(self)
|
||||
|
||||
@ -244,8 +212,6 @@ class Terminal(object):
|
||||
if not self.connectedTo(term):
|
||||
return
|
||||
item = self._connections[term]
|
||||
#print "removing connection", item
|
||||
#item.scene().removeItem(item)
|
||||
item.close()
|
||||
del self._connections[term]
|
||||
del term._connections[self]
|
||||
@ -254,10 +220,6 @@ class Terminal(object):
|
||||
|
||||
self.disconnected(term)
|
||||
term.disconnected(self)
|
||||
#if self.isOutput() and term.isInput():
|
||||
#term.inputChanged(self)
|
||||
#if term.isInput() and term.isOutput():
|
||||
#self.inputChanged(term)
|
||||
|
||||
|
||||
def disconnectAll(self):
|
||||
@ -270,7 +232,7 @@ class Terminal(object):
|
||||
color = QtGui.QColor(0,0,0)
|
||||
elif self.isInput() and not self.hasInput(): ## input terminal with no connected output terminals
|
||||
color = QtGui.QColor(200,200,0)
|
||||
elif self._value is None or eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error)
|
||||
elif self._value is None or fn.eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error)
|
||||
color = QtGui.QColor(255,255,255)
|
||||
elif self.valueIsAcceptable() is None: ## terminal has data, but it is unknown if the data is ok
|
||||
color = QtGui.QColor(200, 200, 0)
|
||||
@ -283,7 +245,6 @@ class Terminal(object):
|
||||
if recurse:
|
||||
for t in self.connections():
|
||||
t.recolor(color, recurse=False)
|
||||
|
||||
|
||||
def rename(self, name):
|
||||
oldName = self._name
|
||||
@ -294,17 +255,6 @@ class Terminal(object):
|
||||
def __repr__(self):
|
||||
return "<Terminal %s.%s>" % (str(self.node().name()), str(self.name()))
|
||||
|
||||
#def extendedConnections(self, terms=None):
|
||||
#"""Return list of terminals (including this one) that are directly or indirectly wired to this."""
|
||||
#if terms is None:
|
||||
#terms = {}
|
||||
#terms[self] = None
|
||||
#for t in self._connections:
|
||||
#if t in terms:
|
||||
#continue
|
||||
#terms.update(t.extendedConnections(terms))
|
||||
#return terms
|
||||
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
@ -318,18 +268,15 @@ class Terminal(object):
|
||||
return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable}
|
||||
|
||||
|
||||
#class TerminalGraphicsItem(QtGui.QGraphicsItem):
|
||||
class TerminalGraphicsItem(GraphicsObject):
|
||||
|
||||
def __init__(self, term, parent=None):
|
||||
self.term = term
|
||||
#QtGui.QGraphicsItem.__init__(self, parent)
|
||||
GraphicsObject.__init__(self, parent)
|
||||
self.brush = fn.mkBrush(0,0,0)
|
||||
self.box = QtGui.QGraphicsRectItem(0, 0, 10, 10, self)
|
||||
self.label = QtGui.QGraphicsTextItem(self.term.name(), self)
|
||||
self.label.scale(0.7, 0.7)
|
||||
#self.setAcceptHoverEvents(True)
|
||||
self.newConnection = None
|
||||
self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem
|
||||
if self.term.isRenamable():
|
||||
@ -338,7 +285,6 @@ class TerminalGraphicsItem(GraphicsObject):
|
||||
self.label.keyPressEvent = self.labelKeyPress
|
||||
self.setZValue(1)
|
||||
self.menu = None
|
||||
|
||||
|
||||
def labelFocusOut(self, ev):
|
||||
QtGui.QGraphicsTextItem.focusOutEvent(self.label, ev)
|
||||
@ -471,8 +417,6 @@ class TerminalGraphicsItem(GraphicsObject):
|
||||
break
|
||||
|
||||
if not gotTarget:
|
||||
#print "remove unused connection"
|
||||
#self.scene().removeItem(self.newConnection)
|
||||
self.newConnection.close()
|
||||
self.newConnection = None
|
||||
else:
|
||||
@ -488,12 +432,6 @@ class TerminalGraphicsItem(GraphicsObject):
|
||||
self.box.setBrush(self.brush)
|
||||
self.update()
|
||||
|
||||
#def hoverEnterEvent(self, ev):
|
||||
#self.hover = True
|
||||
|
||||
#def hoverLeaveEvent(self, ev):
|
||||
#self.hover = False
|
||||
|
||||
def connectPoint(self):
|
||||
## return the connect position of this terminal in view coords
|
||||
return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center()))
|
||||
@ -503,11 +441,9 @@ class TerminalGraphicsItem(GraphicsObject):
|
||||
item.updateLine()
|
||||
|
||||
|
||||
#class ConnectionItem(QtGui.QGraphicsItem):
|
||||
class ConnectionItem(GraphicsObject):
|
||||
|
||||
def __init__(self, source, target=None):
|
||||
#QtGui.QGraphicsItem.__init__(self)
|
||||
GraphicsObject.__init__(self)
|
||||
self.setFlags(
|
||||
self.ItemIsSelectable |
|
||||
@ -528,14 +464,12 @@ class ConnectionItem(GraphicsObject):
|
||||
'selectedColor': (200, 200, 0),
|
||||
'selectedWidth': 3.0,
|
||||
}
|
||||
#self.line = QtGui.QGraphicsLineItem(self)
|
||||
self.source.getViewBox().addItem(self)
|
||||
self.updateLine()
|
||||
self.setZValue(0)
|
||||
|
||||
def close(self):
|
||||
if self.scene() is not None:
|
||||
#self.scene().removeItem(self.line)
|
||||
self.scene().removeItem(self)
|
||||
|
||||
def setTarget(self, target):
|
||||
@ -575,8 +509,11 @@ class ConnectionItem(GraphicsObject):
|
||||
return path
|
||||
|
||||
def keyPressEvent(self, ev):
|
||||
if not self.isSelected():
|
||||
ev.ignore()
|
||||
return
|
||||
|
||||
if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace:
|
||||
#if isinstance(self.target, TerminalGraphicsItem):
|
||||
self.source.disconnect(self.target)
|
||||
ev.accept()
|
||||
else:
|
||||
@ -590,6 +527,7 @@ class ConnectionItem(GraphicsObject):
|
||||
ev.accept()
|
||||
sel = self.isSelected()
|
||||
self.setSelected(True)
|
||||
self.setFocus()
|
||||
if not sel and self.isSelected():
|
||||
self.update()
|
||||
|
||||
@ -600,12 +538,9 @@ class ConnectionItem(GraphicsObject):
|
||||
self.hovered = False
|
||||
self.update()
|
||||
|
||||
|
||||
def boundingRect(self):
|
||||
return self.shape().boundingRect()
|
||||
##return self.line.boundingRect()
|
||||
#px = self.pixelWidth()
|
||||
#return QtCore.QRectF(-5*px, 0, 10*px, self.length)
|
||||
|
||||
def viewRangeChanged(self):
|
||||
self.shapePath = None
|
||||
self.prepareGeometryChange()
|
||||
@ -628,7 +563,5 @@ class ConnectionItem(GraphicsObject):
|
||||
p.setPen(fn.mkPen(self.style['hoverColor'], width=self.style['hoverWidth']))
|
||||
else:
|
||||
p.setPen(fn.mkPen(self.style['color'], width=self.style['width']))
|
||||
|
||||
#p.drawLine(0, 0, 0, self.length)
|
||||
|
||||
p.drawPath(self.path)
|
||||
|
@ -1,36 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from numpy import ndarray, bool_
|
||||
from ..metaarray import MetaArray
|
||||
|
||||
def eq(a, b):
|
||||
"""The great missing equivalence function: Guaranteed evaluation to a single bool value."""
|
||||
if a is b:
|
||||
return True
|
||||
|
||||
try:
|
||||
e = a==b
|
||||
except ValueError:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
except:
|
||||
print("a:", str(type(a)), str(a))
|
||||
print("b:", str(type(b)), str(b))
|
||||
raise
|
||||
t = type(e)
|
||||
if t is bool:
|
||||
return e
|
||||
elif t is bool_:
|
||||
return bool(e)
|
||||
elif isinstance(e, ndarray) or (hasattr(e, 'implements') and e.implements('MetaArray')):
|
||||
try: ## disaster: if a is an empty array and b is not, then e.all() is True
|
||||
if a.shape != b.shape:
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
if (hasattr(e, 'implements') and e.implements('MetaArray')):
|
||||
return e.asarray().all()
|
||||
else:
|
||||
return e.all()
|
||||
else:
|
||||
raise Exception("== operator returned type %s" % str(type(e)))
|
@ -243,6 +243,7 @@ def mkBrush(*args, **kwds):
|
||||
color = args
|
||||
return QtGui.QBrush(mkColor(color))
|
||||
|
||||
|
||||
def mkPen(*args, **kargs):
|
||||
"""
|
||||
Convenience function for constructing QPen.
|
||||
@ -292,6 +293,7 @@ def mkPen(*args, **kargs):
|
||||
pen.setDashPattern(dash)
|
||||
return pen
|
||||
|
||||
|
||||
def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0):
|
||||
"""Generate a QColor from HSVa values. (all arguments are float 0.0-1.0)"""
|
||||
c = QtGui.QColor()
|
||||
@ -303,10 +305,12 @@ def colorTuple(c):
|
||||
"""Return a tuple (R,G,B,A) from a QColor"""
|
||||
return (c.red(), c.green(), c.blue(), c.alpha())
|
||||
|
||||
|
||||
def colorStr(c):
|
||||
"""Generate a hex string code from a QColor"""
|
||||
return ('%02x'*4) % colorTuple(c)
|
||||
|
||||
|
||||
def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255, **kargs):
|
||||
"""
|
||||
Creates a QColor from a single index. Useful for stepping through a predefined list of colors.
|
||||
@ -331,6 +335,7 @@ def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, mi
|
||||
c.setAlpha(alpha)
|
||||
return c
|
||||
|
||||
|
||||
def glColor(*args, **kargs):
|
||||
"""
|
||||
Convert a color to OpenGL color format (r,g,b,a) floats 0.0-1.0
|
||||
@ -367,6 +372,40 @@ def makeArrowPath(headLen=20, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0)
|
||||
return path
|
||||
|
||||
|
||||
def eq(a, b):
|
||||
"""The great missing equivalence function: Guaranteed evaluation to a single bool value."""
|
||||
if a is b:
|
||||
return True
|
||||
|
||||
try:
|
||||
e = a==b
|
||||
except ValueError:
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
except:
|
||||
print('failed to evaluate equivalence for:')
|
||||
print(" a:", str(type(a)), str(a))
|
||||
print(" b:", str(type(b)), str(b))
|
||||
raise
|
||||
t = type(e)
|
||||
if t is bool:
|
||||
return e
|
||||
elif t is np.bool_:
|
||||
return bool(e)
|
||||
elif isinstance(e, np.ndarray) or (hasattr(e, 'implements') and e.implements('MetaArray')):
|
||||
try: ## disaster: if a is an empty array and b is not, then e.all() is True
|
||||
if a.shape != b.shape:
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
if (hasattr(e, 'implements') and e.implements('MetaArray')):
|
||||
return e.asarray().all()
|
||||
else:
|
||||
return e.all()
|
||||
else:
|
||||
raise Exception("== operator returned type %s" % str(type(e)))
|
||||
|
||||
|
||||
def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs):
|
||||
"""
|
||||
@ -930,7 +969,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
|
||||
if levels.shape != (data.shape[-1], 2):
|
||||
raise Exception('levels must have shape (data.shape[-1], 2)')
|
||||
else:
|
||||
raise Exception("levels argument must be 1D or 2D.")
|
||||
raise Exception("levels argument must be 1D or 2D (got shape=%s)." % repr(levels.shape))
|
||||
|
||||
profile()
|
||||
|
||||
|
@ -47,6 +47,10 @@ class ImageItem(GraphicsObject):
|
||||
self.lut = None
|
||||
self.autoDownsample = False
|
||||
|
||||
# In some cases, we use a modified lookup table to handle both rescaling
|
||||
# and LUT more efficiently
|
||||
self._effectiveLut = None
|
||||
|
||||
self.drawKernel = None
|
||||
self.border = None
|
||||
self.removable = False
|
||||
@ -74,11 +78,6 @@ class ImageItem(GraphicsObject):
|
||||
"""
|
||||
self.paintMode = mode
|
||||
self.update()
|
||||
|
||||
## use setOpacity instead.
|
||||
#def setAlpha(self, alpha):
|
||||
#self.setOpacity(alpha)
|
||||
#self.updateImage()
|
||||
|
||||
def setBorder(self, b):
|
||||
self.border = fn.mkPen(b)
|
||||
@ -99,16 +98,6 @@ class ImageItem(GraphicsObject):
|
||||
return QtCore.QRectF(0., 0., 0., 0.)
|
||||
return QtCore.QRectF(0., 0., float(self.width()), float(self.height()))
|
||||
|
||||
#def setClipLevel(self, level=None):
|
||||
#self.clipLevel = level
|
||||
#self.updateImage()
|
||||
|
||||
#def paint(self, p, opt, widget):
|
||||
#pass
|
||||
#if self.pixmap is not None:
|
||||
#p.drawPixmap(0, 0, self.pixmap)
|
||||
#print "paint"
|
||||
|
||||
def setLevels(self, levels, update=True):
|
||||
"""
|
||||
Set image scaling levels. Can be one of:
|
||||
@ -119,9 +108,13 @@ class ImageItem(GraphicsObject):
|
||||
Only the first format is compatible with lookup tables. See :func:`makeARGB <pyqtgraph.makeARGB>`
|
||||
for more details on how levels are applied.
|
||||
"""
|
||||
self.levels = levels
|
||||
if update:
|
||||
self.updateImage()
|
||||
if levels is not None:
|
||||
levels = np.asarray(levels)
|
||||
if not fn.eq(levels, self.levels):
|
||||
self.levels = levels
|
||||
self._effectiveLut = None
|
||||
if update:
|
||||
self.updateImage()
|
||||
|
||||
def getLevels(self):
|
||||
return self.levels
|
||||
@ -137,9 +130,11 @@ class ImageItem(GraphicsObject):
|
||||
Ordinarily, this table is supplied by a :class:`HistogramLUTItem <pyqtgraph.HistogramLUTItem>`
|
||||
or :class:`GradientEditorItem <pyqtgraph.GradientEditorItem>`.
|
||||
"""
|
||||
self.lut = lut
|
||||
if update:
|
||||
self.updateImage()
|
||||
if lut is not self.lut:
|
||||
self.lut = lut
|
||||
self._effectiveLut = None
|
||||
if update:
|
||||
self.updateImage()
|
||||
|
||||
def setAutoDownsample(self, ads):
|
||||
"""
|
||||
@ -222,7 +217,10 @@ class ImageItem(GraphicsObject):
|
||||
else:
|
||||
gotNewData = True
|
||||
shapeChanged = (self.image is None or image.shape != self.image.shape)
|
||||
self.image = image.view(np.ndarray)
|
||||
image = image.view(np.ndarray)
|
||||
if self.image is None or image.dtype != self.image.dtype:
|
||||
self._effectiveLut = None
|
||||
self.image = image
|
||||
if self.image.shape[0] > 2**15-1 or self.image.shape[1] > 2**15-1:
|
||||
if 'autoDownsample' not in kargs:
|
||||
kargs['autoDownsample'] = True
|
||||
@ -261,6 +259,17 @@ class ImageItem(GraphicsObject):
|
||||
if gotNewData:
|
||||
self.sigImageChanged.emit()
|
||||
|
||||
def quickMinMax(self, targetSize=1e6):
|
||||
"""
|
||||
Estimate the min/max values of the image data by subsampling.
|
||||
"""
|
||||
data = self.image
|
||||
while data.size > targetSize:
|
||||
ax = np.argmax(data.shape)
|
||||
sl = [slice(None)] * data.ndim
|
||||
sl[ax] = slice(None, None, 2)
|
||||
data = data[sl]
|
||||
return nanmin(data), nanmax(data)
|
||||
|
||||
def updateImage(self, *args, **kargs):
|
||||
## used for re-rendering qimage from self.image.
|
||||
@ -297,6 +306,27 @@ class ImageItem(GraphicsObject):
|
||||
image = fn.downsample(image, yds, axis=1)
|
||||
else:
|
||||
image = self.image
|
||||
|
||||
# if the image data is a small int, then we can combine levels + lut
|
||||
# into a single lut for better performance
|
||||
if self.levels is not None and self.levels.ndim == 1 and image.dtype in (np.ubyte, np.uint16):
|
||||
if self._effectiveLut is None:
|
||||
eflsize = 2**(image.itemsize*8)
|
||||
ind = np.arange(eflsize)
|
||||
minlev, maxlev = self.levels
|
||||
if lut is None:
|
||||
efflut = fn.rescaleData(ind, scale=255./(maxlev-minlev),
|
||||
offset=minlev, dtype=np.ubyte)
|
||||
else:
|
||||
lutdtype = np.min_scalar_type(lut.shape[0]-1)
|
||||
efflut = fn.rescaleData(ind, scale=(lut.shape[0]-1)/(maxlev-minlev),
|
||||
offset=minlev, dtype=lutdtype, clip=(0, lut.shape[0]-1))
|
||||
efflut = lut[efflut]
|
||||
|
||||
self._effectiveLut = efflut
|
||||
lut = self._effectiveLut
|
||||
levels = None
|
||||
|
||||
|
||||
argb, alpha = fn.makeARGB(image.transpose((1, 0, 2)[:image.ndim]), lut=lut, levels=self.levels)
|
||||
self.qimage = fn.makeQImage(argb, alpha, transpose=False)
|
||||
|
Loading…
Reference in New Issue
Block a user