Merge branch 'fix_finite_cache' into develop

This commit is contained in:
Luke Campagnola 2014-03-05 10:34:46 -05:00
commit 87ff0f314c
9 changed files with 196 additions and 39 deletions

View File

@ -65,7 +65,7 @@ pyqtgraph-0.9.9 [unreleased]
- Fixed GLViewWidget exception handler
- Fixed unicode support in Dock
- Fixed PySide crash caused by emitting signal from GraphicsObject.itemChange
- Fixed possible infinite loop from FiniteCache
pyqtgraph-0.9.8 2013-11-24

View File

@ -26,8 +26,8 @@ Contributors
* Antony Lee
* Mattias Põldaru
* Thomas S.
* Mikhail Terekhov
* fabioz
* Mikhail Terekhov
Requirements
------------

View File

@ -22,17 +22,25 @@ p.setWindowTitle('pyqtgraph example: MultiPlotSpeedTest')
#p.setRange(QtCore.QRectF(0, -10, 5000, 20))
p.setLabel('bottom', 'Index', units='B')
nPlots = 10
nPlots = 100
nSamples = 500
#curves = [p.plot(pen=(i,nPlots*1.3)) for i in range(nPlots)]
curves = [pg.PlotCurveItem(pen=(i,nPlots*1.3)) for i in range(nPlots)]
for c in curves:
curves = []
for i in range(nPlots):
c = pg.PlotCurveItem(pen=(i,nPlots*1.3))
p.addItem(c)
c.setPos(0,i*6)
curves.append(c)
rgn = pg.LinearRegionItem([1,100])
p.setYRange(0, nPlots*6)
p.setXRange(0, nSamples)
p.resize(600,900)
rgn = pg.LinearRegionItem([nSamples/5.,nSamples/3.])
p.addItem(rgn)
data = np.random.normal(size=(53,5000/nPlots))
data = np.random.normal(size=(nPlots*23,nSamples))
ptr = 0
lastTime = time()
fps = None
@ -42,7 +50,8 @@ def update():
count += 1
#print "---------", count
for i in range(nPlots):
curves[i].setData(i+data[(ptr+i)%data.shape[0]])
curves[i].setData(data[(ptr+i)%data.shape[0]])
#print " setData done."
ptr += nPlots
now = time()

View File

@ -8,6 +8,7 @@ if __name__ == "__main__" and (__package__ is None or __package__==''):
from . import initExample
from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
import pyqtgraph as pg
if USE_PYSIDE:
from .exampleLoaderTemplate_pyside import Ui_Form
@ -53,6 +54,7 @@ examples = OrderedDict([
('Video speed test', 'VideoSpeedTest.py'),
('Line Plot update', 'PlotSpeedTest.py'),
('Scatter Plot update', 'ScatterPlotSpeedTest.py'),
('Multiple plots', 'MultiPlotSpeedTest.py'),
])),
('3D Graphics', OrderedDict([
('Volumetric', 'GLVolumeItem.py'),
@ -283,6 +285,9 @@ except:
if __name__ == '__main__':
if '--test' in sys.argv[1:]:
# get rid of orphaned cache files first
pg.renamePyc(path)
files = buildFileList(examples)
if '--pyside' in sys.argv[1:]:
lib = 'PySide'

View File

@ -3,28 +3,8 @@ from ..GraphicsScene import GraphicsScene
from ..Point import Point
from .. import functions as fn
import weakref
from ..pgcollections import OrderedDict
import operator, sys
class FiniteCache(OrderedDict):
"""Caches a finite number of objects, removing
least-frequently used items."""
def __init__(self, length):
self._length = length
OrderedDict.__init__(self)
def __setitem__(self, item, val):
self.pop(item, None) # make sure item is added to end
OrderedDict.__setitem__(self, item, val)
while len(self) > self._length:
del self[list(self.keys())[0]]
def __getitem__(self, item):
val = OrderedDict.__getitem__(self, item)
del self[item]
self[item] = val ## promote this key
return val
import operator
from ..util.lru_cache import LRUCache
class GraphicsItem(object):
@ -38,7 +18,7 @@ class GraphicsItem(object):
The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task.
"""
_pixelVectorGlobalCache = FiniteCache(100)
_pixelVectorGlobalCache = LRUCache(100, 70)
def __init__(self, register=True):
if not hasattr(self, '_qtBaseClass'):

View File

121
pyqtgraph/util/lru_cache.py Normal file
View File

@ -0,0 +1,121 @@
import operator
import sys
import itertools
_IS_PY3 = sys.version_info[0] == 3
class LRUCache(object):
'''
This LRU cache should be reasonable for short collections (until around 100 items), as it does a
sort on the items if the collection would become too big (so, it is very fast for getting and
setting but when its size would become higher than the max size it does one sort based on the
internal time to decide which items should be removed -- which should be Ok if the resizeTo
isn't too close to the maxSize so that it becomes an operation that doesn't happen all the
time).
'''
def __init__(self, maxSize=100, resizeTo=70):
'''
============== =========================================================
**Arguments:**
maxSize (int) This is the maximum size of the cache. When some
item is added and the cache would become bigger than
this, it's resized to the value passed on resizeTo.
resizeTo (int) When a resize operation happens, this is the size
of the final cache.
============== =========================================================
'''
assert resizeTo < maxSize
self.maxSize = maxSize
self.resizeTo = resizeTo
self._counter = 0
self._dict = {}
if _IS_PY3:
self._nextTime = itertools.count(0).__next__
else:
self._nextTime = itertools.count(0).next
def __getitem__(self, key):
item = self._dict[key]
item[2] = self._nextTime()
return item[1]
def __len__(self):
return len(self._dict)
def __setitem__(self, key, value):
item = self._dict.get(key)
if item is None:
if len(self._dict) + 1 > self.maxSize:
self._resizeTo()
item = [key, value, self._nextTime()]
self._dict[key] = item
else:
item[1] = value
item[2] = self._nextTime()
def __delitem__(self, key):
del self._dict[key]
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def clear(self):
self._dict.clear()
if _IS_PY3:
def values(self):
return [i[1] for i in self._dict.values()]
def keys(self):
return [x[0] for x in self._dict.values()]
def _resizeTo(self):
ordered = sorted(self._dict.values(), key=operator.itemgetter(2))[:self.resizeTo]
for i in ordered:
del self._dict[i[0]]
def iteritems(self, accessTime=False):
'''
:param bool accessTime:
If True sorts the returned items by the internal access time.
'''
if accessTime:
for x in sorted(self._dict.values(), key=operator.itemgetter(2)):
yield x[0], x[1]
else:
for x in self._dict.items():
yield x[0], x[1]
else:
def values(self):
return [i[1] for i in self._dict.itervalues()]
def keys(self):
return [x[0] for x in self._dict.itervalues()]
def _resizeTo(self):
ordered = sorted(self._dict.itervalues(), key=operator.itemgetter(2))[:self.resizeTo]
for i in ordered:
del self._dict[i[0]]
def iteritems(self, accessTime=False):
'''
============= ======================================================
**Arguments**
accessTime (bool) If True sorts the returned items by the
internal access time.
============= ======================================================
'''
if accessTime:
for x in sorted(self._dict.itervalues(), key=operator.itemgetter(2)):
yield x[0], x[1]
else:
for x in self._dict.iteritems():
yield x[0], x[1]

View File

@ -0,0 +1,50 @@
from pyqtgraph.util.lru_cache import LRUCache
def testLRU():
lru = LRUCache(2, 1)
# check twice
checkLru(lru)
checkLru(lru)
def checkLru(lru):
lru[1] = 1
lru[2] = 2
lru[3] = 3
assert len(lru) == 2
assert set([2, 3]) == set(lru.keys())
assert set([2, 3]) == set(lru.values())
lru[2] = 2
assert set([2, 3]) == set(lru.values())
lru[1] = 1
set([2, 1]) == set(lru.values())
#Iterates from the used in the last access to others based on access time.
assert [(2, 2), (1, 1)] == list(lru.iteritems(accessTime=True))
lru[2] = 2
assert [(1, 1), (2, 2)] == list(lru.iteritems(accessTime=True))
del lru[2]
assert [(1, 1), ] == list(lru.iteritems(accessTime=True))
lru[2] = 2
assert [(1, 1), (2, 2)] == list(lru.iteritems(accessTime=True))
_a = lru[1]
assert [(2, 2), (1, 1)] == list(lru.iteritems(accessTime=True))
_a = lru[2]
assert [(1, 1), (2, 2)] == list(lru.iteritems(accessTime=True))
assert lru.get(2) == 2
assert lru.get(3) == None
assert [(1, 1), (2, 2)] == list(lru.iteritems(accessTime=True))
lru.clear()
assert [] == list(lru.iteritems())
if __name__ == '__main__':
testLRU()

View File

@ -1,8 +0,0 @@
import unittest
import os, sys
## make sure this instance of pyqtgraph gets imported first
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
## all tests should be defined with this class so we have the option to tweak it later.
class TestCase(unittest.TestCase):
pass