Merge branch 'fix_finite_cache' into develop
This commit is contained in:
commit
87ff0f314c
@ -65,7 +65,7 @@ pyqtgraph-0.9.9 [unreleased]
|
|||||||
- Fixed GLViewWidget exception handler
|
- Fixed GLViewWidget exception handler
|
||||||
- Fixed unicode support in Dock
|
- Fixed unicode support in Dock
|
||||||
- Fixed PySide crash caused by emitting signal from GraphicsObject.itemChange
|
- Fixed PySide crash caused by emitting signal from GraphicsObject.itemChange
|
||||||
|
- Fixed possible infinite loop from FiniteCache
|
||||||
|
|
||||||
pyqtgraph-0.9.8 2013-11-24
|
pyqtgraph-0.9.8 2013-11-24
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ Contributors
|
|||||||
* Antony Lee
|
* Antony Lee
|
||||||
* Mattias Põldaru
|
* Mattias Põldaru
|
||||||
* Thomas S.
|
* Thomas S.
|
||||||
* Mikhail Terekhov
|
|
||||||
* fabioz
|
* fabioz
|
||||||
|
* Mikhail Terekhov
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
@ -22,17 +22,25 @@ p.setWindowTitle('pyqtgraph example: MultiPlotSpeedTest')
|
|||||||
#p.setRange(QtCore.QRectF(0, -10, 5000, 20))
|
#p.setRange(QtCore.QRectF(0, -10, 5000, 20))
|
||||||
p.setLabel('bottom', 'Index', units='B')
|
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 = [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)]
|
curves = []
|
||||||
for c in curves:
|
for i in range(nPlots):
|
||||||
|
c = pg.PlotCurveItem(pen=(i,nPlots*1.3))
|
||||||
p.addItem(c)
|
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)
|
p.addItem(rgn)
|
||||||
|
|
||||||
|
|
||||||
data = np.random.normal(size=(53,5000/nPlots))
|
data = np.random.normal(size=(nPlots*23,nSamples))
|
||||||
ptr = 0
|
ptr = 0
|
||||||
lastTime = time()
|
lastTime = time()
|
||||||
fps = None
|
fps = None
|
||||||
@ -42,7 +50,8 @@ def update():
|
|||||||
count += 1
|
count += 1
|
||||||
#print "---------", count
|
#print "---------", count
|
||||||
for i in range(nPlots):
|
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."
|
#print " setData done."
|
||||||
ptr += nPlots
|
ptr += nPlots
|
||||||
now = time()
|
now = time()
|
||||||
|
@ -8,6 +8,7 @@ if __name__ == "__main__" and (__package__ is None or __package__==''):
|
|||||||
|
|
||||||
from . import initExample
|
from . import initExample
|
||||||
from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
|
from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
if USE_PYSIDE:
|
if USE_PYSIDE:
|
||||||
from .exampleLoaderTemplate_pyside import Ui_Form
|
from .exampleLoaderTemplate_pyside import Ui_Form
|
||||||
@ -53,6 +54,7 @@ examples = OrderedDict([
|
|||||||
('Video speed test', 'VideoSpeedTest.py'),
|
('Video speed test', 'VideoSpeedTest.py'),
|
||||||
('Line Plot update', 'PlotSpeedTest.py'),
|
('Line Plot update', 'PlotSpeedTest.py'),
|
||||||
('Scatter Plot update', 'ScatterPlotSpeedTest.py'),
|
('Scatter Plot update', 'ScatterPlotSpeedTest.py'),
|
||||||
|
('Multiple plots', 'MultiPlotSpeedTest.py'),
|
||||||
])),
|
])),
|
||||||
('3D Graphics', OrderedDict([
|
('3D Graphics', OrderedDict([
|
||||||
('Volumetric', 'GLVolumeItem.py'),
|
('Volumetric', 'GLVolumeItem.py'),
|
||||||
@ -283,6 +285,9 @@ except:
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if '--test' in sys.argv[1:]:
|
if '--test' in sys.argv[1:]:
|
||||||
|
# get rid of orphaned cache files first
|
||||||
|
pg.renamePyc(path)
|
||||||
|
|
||||||
files = buildFileList(examples)
|
files = buildFileList(examples)
|
||||||
if '--pyside' in sys.argv[1:]:
|
if '--pyside' in sys.argv[1:]:
|
||||||
lib = 'PySide'
|
lib = 'PySide'
|
||||||
|
@ -3,28 +3,8 @@ from ..GraphicsScene import GraphicsScene
|
|||||||
from ..Point import Point
|
from ..Point import Point
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
import weakref
|
import weakref
|
||||||
from ..pgcollections import OrderedDict
|
import operator
|
||||||
import operator, sys
|
from ..util.lru_cache import LRUCache
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GraphicsItem(object):
|
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.
|
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):
|
def __init__(self, register=True):
|
||||||
if not hasattr(self, '_qtBaseClass'):
|
if not hasattr(self, '_qtBaseClass'):
|
||||||
|
0
pyqtgraph/util/__init__.py
Normal file
0
pyqtgraph/util/__init__.py
Normal file
121
pyqtgraph/util/lru_cache.py
Normal file
121
pyqtgraph/util/lru_cache.py
Normal 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]
|
50
pyqtgraph/util/tests/test_lru_cache.py
Normal file
50
pyqtgraph/util/tests/test_lru_cache.py
Normal 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()
|
@ -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
|
|
Loading…
Reference in New Issue
Block a user