Bugfixes:
- Image exporter detects GraphicsView background color - Corrected exporter filename handling for SVG export - ViewBox no longer attempts to deregister itself while python is exiting - Speedup for conversion using np.array(MetaArray_instance) - GLGraphicsItem updates immediately when its GL options have changed - Corrected some GL shader program bugs for nVidia drivers - Fixed coordinate mapping bug in functions.transformCoordinates - Fixed PySide import error
This commit is contained in:
parent
9cb199d971
commit
80148920c9
2
Qt.py
2
Qt.py
|
@ -14,7 +14,7 @@ else:
|
|||
USE_PYSIDE = False
|
||||
except ImportError:
|
||||
try:
|
||||
import Pyside
|
||||
import PySide
|
||||
USE_PYSIDE = True
|
||||
except ImportError:
|
||||
raise Exception("PyQtGraph requires either PyQt4 or PySide; neither package could be imported.")
|
||||
|
|
|
@ -173,11 +173,14 @@ from .SignalProxy import *
|
|||
from .ptime import time
|
||||
|
||||
|
||||
## Workaround for Qt exit crash:
|
||||
## ALL QGraphicsItems must have a scene before they are deleted.
|
||||
## This is potentially very expensive, but preferred over crashing.
|
||||
import atexit
|
||||
def cleanup():
|
||||
ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore.
|
||||
|
||||
## Workaround for Qt exit crash:
|
||||
## ALL QGraphicsItems must have a scene before they are deleted.
|
||||
## This is potentially very expensive, but preferred over crashing.
|
||||
## Note: this appears to be fixed in PySide as of 2012.12, but it should be left in for a while longer..
|
||||
if QtGui.QApplication.instance() is None:
|
||||
return
|
||||
import gc
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import os
|
||||
dirs = [
|
||||
('graphicsItems', 'graphicsItems'),
|
||||
('3dgraphics', 'opengl/items'),
|
||||
('widgets', 'widgets'),
|
||||
]
|
||||
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
|
||||
for a, b in dirs:
|
||||
rst = [os.path.splitext(x)[0].lower() for x in os.listdir(os.path.join(path, 'documentation', 'source', a))]
|
||||
py = [os.path.splitext(x)[0].lower() for x in os.listdir(os.path.join(path, b))]
|
||||
print a
|
||||
for x in set(py) - set(rst):
|
||||
print " ", x
|
|
@ -1,7 +1,7 @@
|
|||
from pyqtgraph.widgets.FileDialog import FileDialog
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtGui, QtCore, QtSvg
|
||||
import os
|
||||
import os, re
|
||||
LastExportDirectory = None
|
||||
|
||||
|
||||
|
@ -55,10 +55,13 @@ class Exporter(object):
|
|||
global LastExportDirectory
|
||||
LastExportDirectory = os.path.split(fileName)[0]
|
||||
|
||||
ext = os.path.splitext(fileName)[1].lower()
|
||||
selectedExt = str(self.fileDialog.selectedNameFilter()).lstrip('*').lower()
|
||||
if ext != selectedExt:
|
||||
fileName = fileName + selectedExt
|
||||
## If file name does not match selected extension, append it now
|
||||
ext = os.path.splitext(fileName)[1].lower().lstrip('.')
|
||||
selectedExt = re.search(r'\*\.(\w+)\b', str(self.fileDialog.selectedNameFilter()))
|
||||
if selectedExt is not None:
|
||||
selectedExt = selectedExt.groups()[0].lower()
|
||||
if ext != selectedExt:
|
||||
fileName = fileName + selectedExt
|
||||
|
||||
self.export(fileName=fileName, **self.fileDialog.opts)
|
||||
|
||||
|
|
|
@ -11,12 +11,16 @@ class ImageExporter(Exporter):
|
|||
def __init__(self, item):
|
||||
Exporter.__init__(self, item)
|
||||
tr = self.getTargetRect()
|
||||
|
||||
if isinstance(item, QtGui.QGraphicsItem):
|
||||
scene = item.scene()
|
||||
else:
|
||||
scene = item
|
||||
bg = scene.views()[0].backgroundBrush().color()
|
||||
self.params = Parameter(name='params', type='group', children=[
|
||||
{'name': 'width', 'type': 'int', 'value': tr.width(), 'limits': (0, None)},
|
||||
{'name': 'height', 'type': 'int', 'value': tr.height(), 'limits': (0, None)},
|
||||
{'name': 'antialias', 'type': 'bool', 'value': True},
|
||||
{'name': 'background', 'type': 'color', 'value': (0,0,0,255)},
|
||||
{'name': 'background', 'type': 'color', 'value': bg},
|
||||
])
|
||||
self.params.param('width').sigValueChanged.connect(self.widthChanged)
|
||||
self.params.param('height').sigValueChanged.connect(self.heightChanged)
|
||||
|
|
|
@ -290,3 +290,47 @@ def suggestDType(x):
|
|||
#return '<U%d' % len(x)
|
||||
else:
|
||||
return object
|
||||
|
||||
def removePeriodic(data, f0=60.0, dt=None, harmonics=10, samples=4):
|
||||
if (hasattr(data, 'implements') and data.implements('MetaArray')):
|
||||
data1 = data.asarray()
|
||||
if dt is None:
|
||||
times = data.xvals('Time')
|
||||
dt = times[1]-times[0]
|
||||
else:
|
||||
data1 = data
|
||||
if dt is None:
|
||||
raise Exception('Must specify dt for this data')
|
||||
|
||||
ft = np.fft.fft(data1)
|
||||
|
||||
## determine frequencies in fft data
|
||||
df = 1.0 / (len(data1) * dt)
|
||||
freqs = np.linspace(0.0, (len(ft)-1) * df, len(ft))
|
||||
|
||||
## flatten spikes at f0 and harmonics
|
||||
for i in xrange(1, harmonics + 2):
|
||||
f = f0 * i # target frequency
|
||||
|
||||
## determine index range to check for this frequency
|
||||
ind1 = int(np.floor(f / df))
|
||||
ind2 = int(np.ceil(f / df)) + (samples-1)
|
||||
if ind1 > len(ft)/2.:
|
||||
break
|
||||
mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5
|
||||
for j in range(ind1, ind2+1):
|
||||
phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts.
|
||||
re = mag * np.cos(phase)
|
||||
im = mag * np.sin(phase)
|
||||
ft[j] = re + im*1j
|
||||
ft[len(ft)-j] = re - im*1j
|
||||
|
||||
data2 = np.fft.ifft(ft).real
|
||||
|
||||
if (hasattr(data, 'implements') and data.implements('MetaArray')):
|
||||
return metaarray.MetaArray(data2, info=data.infoCopy())
|
||||
else:
|
||||
return data2
|
||||
|
||||
|
||||
|
12
functions.py
12
functions.py
|
@ -491,6 +491,9 @@ def transformToArray(tr):
|
|||
## map coordinates through transform
|
||||
mapped = np.dot(m, coords)
|
||||
"""
|
||||
if isinstance(tr, np.ndarray):
|
||||
return tr
|
||||
|
||||
#return np.array([[tr.m11(), tr.m12(), tr.m13()],[tr.m21(), tr.m22(), tr.m23()],[tr.m31(), tr.m32(), tr.m33()]])
|
||||
## The order of elements given by the method names m11..m33 is misleading--
|
||||
## It is most common for x,y translation to occupy the positions 1,3 and 2,3 in
|
||||
|
@ -510,8 +513,11 @@ def transformCoordinates(tr, coords):
|
|||
The mapping will _ignore_ any perspective transformations.
|
||||
"""
|
||||
nd = coords.shape[0]
|
||||
m = transformToArray(tr)
|
||||
m = m[:m.shape[0]-1] # remove perspective
|
||||
if not isinstance(tr, np.ndarray):
|
||||
m = transformToArray(tr)
|
||||
m = m[:m.shape[0]-1] # remove perspective
|
||||
else:
|
||||
m = tr
|
||||
|
||||
## If coords are 3D and tr is 2D, assume no change for Z axis
|
||||
if m.shape == (2,3) and nd == 3:
|
||||
|
@ -537,7 +543,7 @@ def transformCoordinates(tr, coords):
|
|||
m = m[:, :-1]
|
||||
|
||||
## map coordinates and return
|
||||
mapped = (m*coords).sum(axis=0) ## apply scale/rotate
|
||||
mapped = (m*coords).sum(axis=1) ## apply scale/rotate
|
||||
mapped += translate
|
||||
return mapped
|
||||
|
||||
|
|
|
@ -281,6 +281,11 @@ class ImageItem(GraphicsObject):
|
|||
p.drawRect(self.boundingRect())
|
||||
prof.finish()
|
||||
|
||||
def save(self, fileName, *args):
|
||||
"""Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data."""
|
||||
if self.qimage is None:
|
||||
self.render()
|
||||
self.qimage.save(fileName, *args)
|
||||
|
||||
def getHistogram(self, bins=500, step=3):
|
||||
"""Returns x and y arrays containing the histogram values for the current image.
|
||||
|
|
|
@ -1222,4 +1222,15 @@ class ViewBox(GraphicsWidget):
|
|||
ViewBox.NamedViews.pop(name, None)
|
||||
ViewBox.updateAllViewLists()
|
||||
|
||||
@staticmethod
|
||||
def quit():
|
||||
## called when the application is about to exit.
|
||||
## this disables all callbacks, which might otherwise generate errors if invoked during exit.
|
||||
for k in ViewBox.AllViews:
|
||||
try:
|
||||
k.destroyed.disconnect()
|
||||
except RuntimeError: ## signal is already disconnected.
|
||||
pass
|
||||
|
||||
|
||||
from .ViewBoxMenu import ViewBoxMenu
|
||||
|
|
|
@ -343,6 +343,10 @@ class MetaArray(object):
|
|||
else:
|
||||
return np.array(self._data)
|
||||
|
||||
def __array__(self):
|
||||
## supports np.array(metaarray_instance)
|
||||
return self.asarray()
|
||||
|
||||
def view(self, typ):
|
||||
## deprecated; kept for backward compatibility
|
||||
if typ is np.ndarray:
|
||||
|
|
|
@ -86,6 +86,7 @@ class GLGraphicsItem(QtCore.QObject):
|
|||
if isinstance(opts, basestring):
|
||||
opts = GLOptions[opts]
|
||||
self.__glOpts = opts.copy()
|
||||
self.update()
|
||||
|
||||
def updateGLOptions(self, opts):
|
||||
"""
|
||||
|
|
|
@ -444,6 +444,10 @@ class MeshData(object):
|
|||
import pickle
|
||||
state = pickle.loads(state)
|
||||
for k in state:
|
||||
if isinstance(state[k], list):
|
||||
if isinstance(state[k][0], QtGui.QVector3D):
|
||||
state[k] = [[v.x(), v.y(), v.z()] for v in state[k]]
|
||||
state[k] = np.array(state[k])
|
||||
setattr(self, k, state[k])
|
||||
|
||||
|
||||
|
|
|
@ -22,3 +22,9 @@ from pyqtgraph import importAll
|
|||
#globals()[k] = getattr(mod, k)
|
||||
|
||||
importAll('items', globals(), locals())
|
||||
\
|
||||
from MeshData import MeshData
|
||||
## for backward compatibility:
|
||||
MeshData.MeshData = MeshData
|
||||
|
||||
import shaders
|
||||
|
|
|
@ -55,12 +55,18 @@ class GLMeshItem(GLGraphicsItem):
|
|||
self.faces = None
|
||||
|
||||
def setShader(self, shader):
|
||||
"""Set the shader used when rendering faces in the mesh. (see the GL shaders example)"""
|
||||
self.opts['shader'] = shader
|
||||
self.update()
|
||||
|
||||
def shader(self):
|
||||
return shaders.getShaderProgram(self.opts['shader'])
|
||||
|
||||
def setColor(self, c):
|
||||
"""Set the default color to use when no vertex or face colors are specified."""
|
||||
self.opts['color'] = c
|
||||
self.update()
|
||||
|
||||
def setMeshData(self, **kwds):
|
||||
"""
|
||||
Set mesh data for this item. This can be invoked two ways:
|
||||
|
|
|
@ -49,9 +49,9 @@ def initShaders():
|
|||
varying vec3 normal;
|
||||
void main() {
|
||||
vec4 color = gl_Color;
|
||||
color.x = (normal.x + 1) * 0.5;
|
||||
color.y = (normal.y + 1) * 0.5;
|
||||
color.z = (normal.z + 1) * 0.5;
|
||||
color.x = (normal.x + 1.0) * 0.5;
|
||||
color.y = (normal.y + 1.0) * 0.5;
|
||||
color.z = (normal.z + 1.0) * 0.5;
|
||||
gl_FragColor = color;
|
||||
}
|
||||
""")
|
||||
|
@ -73,9 +73,9 @@ def initShaders():
|
|||
varying vec3 normal;
|
||||
void main() {
|
||||
vec4 color = gl_Color;
|
||||
color.x = (normal.x + 1) * 0.5;
|
||||
color.y = (normal.y + 1) * 0.5;
|
||||
color.z = (normal.z + 1) * 0.5;
|
||||
color.x = (normal.x + 1.0) * 0.5;
|
||||
color.y = (normal.y + 1.0) * 0.5;
|
||||
color.z = (normal.z + 1.0) * 0.5;
|
||||
gl_FragColor = color;
|
||||
}
|
||||
""")
|
||||
|
@ -97,7 +97,7 @@ def initShaders():
|
|||
FragmentShader("""
|
||||
varying vec3 normal;
|
||||
void main() {
|
||||
float p = dot(normal, normalize(vec3(1, -1, -1)));
|
||||
float p = dot(normal, normalize(vec3(1.0, -1.0, -1.0)));
|
||||
p = p < 0. ? 0. : p * 0.8;
|
||||
vec4 color = gl_Color;
|
||||
color.x = color.x * (0.2 + p);
|
||||
|
@ -151,27 +151,26 @@ def initShaders():
|
|||
}
|
||||
"""),
|
||||
FragmentShader("""
|
||||
#version 140 // required for uniform blocks
|
||||
uniform float colorMap[9];
|
||||
varying vec4 pos;
|
||||
out vec4 gl_FragColor;
|
||||
in vec4 gl_Color;
|
||||
//out vec4 gl_FragColor; // only needed for later glsl versions
|
||||
//in vec4 gl_Color;
|
||||
void main() {
|
||||
vec4 color = gl_Color;
|
||||
color.x = colorMap[0] * (pos.z + colorMap[1]);
|
||||
if (colorMap[2] != 1.0)
|
||||
color.x = pow(color.x, colorMap[2]);
|
||||
color.x = color.x < 0 ? 0 : (color.x > 1 ? 1 : color.x);
|
||||
color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x);
|
||||
|
||||
color.y = colorMap[3] * (pos.z + colorMap[4]);
|
||||
if (colorMap[5] != 1.0)
|
||||
color.y = pow(color.y, colorMap[5]);
|
||||
color.y = color.y < 0 ? 0 : (color.y > 1 ? 1 : color.y);
|
||||
color.y = color.y < 0. ? 0. : (color.y > 1. ? 1. : color.y);
|
||||
|
||||
color.z = colorMap[6] * (pos.z + colorMap[7]);
|
||||
if (colorMap[8] != 1.0)
|
||||
color.z = pow(color.z, colorMap[8]);
|
||||
color.z = color.z < 0 ? 0 : (color.z > 1 ? 1 : color.z);
|
||||
color.z = color.z < 0. ? 0. : (color.z > 1. ? 1. : color.z);
|
||||
|
||||
color.w = 1.0;
|
||||
gl_FragColor = color;
|
||||
|
@ -234,9 +233,9 @@ class Shader(object):
|
|||
msg = msg.strip()
|
||||
if msg == '':
|
||||
continue
|
||||
m = re.match(r'\d+\((\d+)\)', msg)
|
||||
m = re.match(r'(\d+\:)?\d+\((\d+)\)', msg)
|
||||
if m is not None:
|
||||
line = int(m.groups()[0])
|
||||
line = int(m.groups()[1])
|
||||
errNums[line-1] = errNums[line-1] + (str(i+1),)
|
||||
#code[line-1] = '%d\t%s' % (i+1, code[line-1])
|
||||
err = err + "%d %s\n" % (i+1, msg)
|
||||
|
|
2
setup.py
2
setup.py
|
@ -11,9 +11,9 @@ setup(name='pyqtgraph',
|
|||
description='Scientific Graphics and GUI Library for Python',
|
||||
long_description="PyQtGraph is a pure-python graphics and GUI library built on PyQt4 and numpy. It is intended for use in mathematics / scientific / engineering applications. Despite being written entirely in python, the library is very fast due to its heavy leverage of numpy for number crunching and Qt's GraphicsView framework for fast display.",
|
||||
license='MIT',
|
||||
url='http://www.pyqtgraph.org',
|
||||
author='Luke Campagnola',
|
||||
author_email='luke.campagnola@gmail.com',
|
||||
url='',
|
||||
packages=all_packages,
|
||||
package_dir = {'pyqtgraph': '.'},
|
||||
package_data={'pyqtgraph': ['graphicsItems/PlotItem/*.png']},
|
||||
|
|
|
@ -108,10 +108,13 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
|||
'suffix': '',
|
||||
'siPrefix': False, ## Set to True to display numbers with SI prefix (ie, 100pA instead of 1e-10A)
|
||||
|
||||
'delay': 0.3, ## delay sending wheel update signals for 300ms
|
||||
|
||||
'delayUntilEditFinished': True, ## do not send signals until text editing has finished
|
||||
|
||||
## for compatibility with QDoubleSpinBox and QSpinBox
|
||||
'decimals': 2
|
||||
'decimals': 2,
|
||||
|
||||
}
|
||||
|
||||
self.decOpts = ['step', 'minStep']
|
||||
|
@ -125,7 +128,7 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
|||
|
||||
|
||||
self.editingFinished.connect(self.editingFinishedEvent)
|
||||
self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange)
|
||||
self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange, delay=self.opts['delay'])
|
||||
|
||||
def event(self, ev):
|
||||
ret = QtGui.QAbstractSpinBox.event(self, ev)
|
||||
|
@ -140,6 +143,7 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
|||
allowed in :func:`__init__ <pyqtgraph.SpinBox.__init__>`.
|
||||
|
||||
"""
|
||||
#print opts
|
||||
for k in opts:
|
||||
if k == 'bounds':
|
||||
#print opts[k]
|
||||
|
@ -182,7 +186,10 @@ class SpinBox(QtGui.QAbstractSpinBox):
|
|||
if ms < 1:
|
||||
ms = 1
|
||||
self.opts['minStep'] = ms
|
||||
|
||||
|
||||
if 'delay' in opts:
|
||||
self.proxy.setDelay(opts['delay'])
|
||||
|
||||
self.updateText()
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue