Skip to content

Commit

Permalink
Merge tag 'pyqtgraph-0.9.8' into pyqtgraph-core
Browse files Browse the repository at this point in the history
  • Loading branch information
campagnola committed Dec 23, 2013
2 parents ef0ee7c + 5309483 commit 757dc50
Show file tree
Hide file tree
Showing 75 changed files with 3,293 additions and 1,190 deletions.
1 change: 1 addition & 0 deletions PlotData.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class PlotData(object):
- removal of nan/inf values
- option for single value shared by entire column
- cached downsampling
- cached min / max / hasnan / isuniform
"""
def __init__(self):
self.fields = {}
Expand Down
8 changes: 7 additions & 1 deletion Point.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ def __rdiv__(self, a):
def __div__(self, a):
return self._math_('__div__', a)

def __truediv__(self, a):
return self._math_('__truediv__', a)

def __rtruediv__(self, a):
return self._math_('__rtruediv__', a)

def __rpow__(self, a):
return self._math_('__rpow__', a)

Expand Down Expand Up @@ -146,4 +152,4 @@ def copy(self):
return Point(self)

def toQPoint(self):
return QtCore.QPoint(*self)
return QtCore.QPoint(*self)
5 changes: 4 additions & 1 deletion SRTTransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,14 @@ def setRotate(self, angle):
self._state['angle'] = angle
self.update()

def __div__(self, t):
def __truediv__(self, t):
"""A / B == B^-1 * A"""
dt = t.inverted()[0] * self
return SRTTransform(dt)

def __div__(self, t):
return self.__truediv__(t)

def __mul__(self, t):
return SRTTransform(QtGui.QTransform.__mul__(self, t))

Expand Down
19 changes: 15 additions & 4 deletions SRTTransform3D.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ def setFromMatrix(self, m):
m = self.matrix().reshape(4,4)
## translation is 4th column
self._state['pos'] = m[:3,3]

## scale is vector-length of first three columns
scale = (m[:3,:3]**2).sum(axis=0)**0.5
## see whether there is an inversion
Expand All @@ -141,18 +140,30 @@ def setFromMatrix(self, m):
print("Scale: %s" % str(scale))
print("Original matrix: %s" % str(m))
raise
eigIndex = np.argwhere(np.abs(evals-1) < 1e-7)
eigIndex = np.argwhere(np.abs(evals-1) < 1e-6)
if len(eigIndex) < 1:
print("eigenvalues: %s" % str(evals))
print("eigenvectors: %s" % str(evecs))
print("index: %s, %s" % (str(eigIndex), str(evals-1)))
raise Exception("Could not determine rotation axis.")
axis = evecs[eigIndex[0,0]].real
axis = evecs[:,eigIndex[0,0]].real
axis /= ((axis**2).sum())**0.5
self._state['axis'] = axis

## trace(r) == 2 cos(angle) + 1, so:
self._state['angle'] = np.arccos((r.trace()-1)*0.5) * 180 / np.pi
cos = (r.trace()-1)*0.5 ## this only gets us abs(angle)

## The off-diagonal values can be used to correct the angle ambiguity,
## but we need to figure out which element to use:
axisInd = np.argmax(np.abs(axis))
rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd]

## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd];
## solve for sin(angle)
sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd])

## finally, we get the complete angle from arctan(sin/cos)
self._state['angle'] = np.arctan2(sin, cos) * 180 / np.pi
if self._state['angle'] == 0:
self._state['axis'] = (0,0,1)

Expand Down
10 changes: 8 additions & 2 deletions Vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Distributed under MIT/X11 license. See license.txt for more infomation.
"""

from .Qt import QtGui, QtCore
from .Qt import QtGui, QtCore, USE_PYSIDE
import numpy as np

class Vector(QtGui.QVector3D):
Expand Down Expand Up @@ -33,7 +33,13 @@ def __init__(self, *args):

def __len__(self):
return 3


def __add__(self, b):
# workaround for pyside bug. see https://bugs.launchpad.net/pyqtgraph/+bug/1223173
if USE_PYSIDE and isinstance(b, QtGui.QVector3D):
b = Vector(b)
return QtGui.QVector3D.__add__(self, b)

#def __reduce__(self):
#return (Point, (self.x(), self.y()))

Expand Down
63 changes: 56 additions & 7 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets
'useWeave': True, ## Use weave to speed up some operations, if it is available
'weaveDebug': False, ## Print full error message if weave compile fails
'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide
'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code)
}


Expand Down Expand Up @@ -137,7 +139,7 @@ def importModules(path, globals, locals, excludes=()):
d = os.path.join(os.path.split(globals['__file__'])[0], path)
files = set()
for f in frozenSupport.listdir(d):
if frozenSupport.isdir(os.path.join(d, f)) and f != '__pycache__':
if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']:
files.add(f)
elif f[-3:] == '.py' and f != '__init__.py':
files.add(f[:-3])
Expand All @@ -152,7 +154,8 @@ def importModules(path, globals, locals, excludes=()):
try:
if len(path) > 0:
modName = path + '.' + modName
mod = __import__(modName, globals, locals, fromlist=['*'])
#mod = __import__(modName, globals, locals, fromlist=['*'])
mod = __import__(modName, globals, locals, ['*'], 1)
mods[modName] = mod
except:
import traceback
Expand All @@ -175,7 +178,8 @@ def importAll(path, globals, locals, excludes=()):
globals[k] = getattr(mod, k)

importAll('graphicsItems', globals(), locals())
importAll('widgets', globals(), locals(), excludes=['MatplotlibWidget', 'RemoteGraphicsView'])
importAll('widgets', globals(), locals(),
excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView'])

from .imageview import *
from .WidgetGroup import *
Expand All @@ -190,9 +194,20 @@ def importAll(path, globals, locals, excludes=()):
from .colormap import *
from .ptime import time

##############################################################
## PyQt and PySide both are prone to crashing on exit.
## There are two general approaches to dealing with this:
## 1. Install atexit handlers that assist in tearing down to avoid crashes.
## This helps, but is never perfect.
## 2. Terminate the process before python starts tearing down
## This is potentially dangerous

## Attempts to work around exit crashes:
import atexit
def cleanup():
if not getConfigOption('exitCleanup'):
return

ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore.

## Workaround for Qt exit crash:
Expand All @@ -212,6 +227,38 @@ def cleanup():
atexit.register(cleanup)


## Optional function for exiting immediately (with some manual teardown)
def exit():
"""
Causes python to exit without garbage-collecting any objects, and thus avoids
calling object destructor methods. This is a sledgehammer workaround for
a variety of bugs in PyQt and Pyside that cause crashes on exit.
This function does the following in an attempt to 'safely' terminate
the process:
* Invoke atexit callbacks
* Close all open file handles
* os._exit()
Note: there is some potential for causing damage with this function if you
are using objects that _require_ their destructors to be called (for example,
to properly terminate log files, disconnect from devices, etc). Situations
like this are probably quite rare, but use at your own risk.
"""

## first disable our own cleanup function; won't be needing it.
setConfigOptions(exitCleanup=False)

## invoke atexit callbacks
atexit._run_exitfuncs()

## close file handles
os.closerange(3, 4096) ## just guessing on the maximum descriptor count..

os._exit(0)



## Convenience functions for command-line use

Expand All @@ -235,7 +282,7 @@ def plot(*args, **kargs):
#if len(args)+len(kargs) > 0:
#w.plot(*args, **kargs)

pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom']
pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom', 'background']
pwArgs = {}
dataArgs = {}
for k in kargs:
Expand Down Expand Up @@ -265,13 +312,15 @@ def image(*args, **kargs):
return w
show = image ## for backward compatibility

def dbg():
def dbg(*args, **kwds):
"""
Create a console window and begin watching for exceptions.
All arguments are passed to :func:`ConsoleWidget.__init__() <pyqtgraph.console.ConsoleWidget.__init__>`.
"""
mkQApp()
import console
c = console.ConsoleWidget()
from . import console
c = console.ConsoleWidget(*args, **kwds)
c.catchAllExceptions()
c.show()
global consoles
Expand Down
4 changes: 2 additions & 2 deletions console/Console.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def execSingle(self, cmd):


def execMulti(self, nextLine):
self.stdout.write(nextLine+"\n")
#self.stdout.write(nextLine+"\n")
if nextLine.strip() != '':
self.multiline += "\n" + nextLine
return
Expand Down Expand Up @@ -372,4 +372,4 @@ def checkException(self, excType, exc, tb):
return False

return True


9 changes: 9 additions & 0 deletions debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ def w(*args, **kargs):
return rv
return w

def warnOnException(func):
"""Decorator which catches/ignores exceptions and prints a stack trace."""
def w(*args, **kwds):
try:
func(*args, **kwds)
except:
printExc('Ignored exception:')
return w

def getExc(indent=4, prefix='| '):
tb = traceback.format_exc()
lines = []
Expand Down
7 changes: 7 additions & 0 deletions dockarea/Dock.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ def containerChanged(self, c):

self.setOrientation(force=True)

def close(self):
"""Remove this dock from the DockArea it lives inside."""
self.setParent(None)
self.label.setParent(None)
self._container.apoptose()
self._container = None

def __repr__(self):
return "<Dock %s %s>" % (self.name(), self.stretch())

Expand Down
6 changes: 3 additions & 3 deletions dockarea/DockArea.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ def addDock(self, dock=None, position='bottom', relativeTo=None, **kwds):
Arguments:
dock The new Dock object to add. If None, then a new Dock will be
created.
position 'bottom', 'top', 'left', 'right', 'over', or 'under'
position 'bottom', 'top', 'left', 'right', 'above', or 'below'
relativeTo If relativeTo is None, then the new Dock is added to fill an
entire edge of the window. If relativeTo is another Dock, then
the new Dock is placed adjacent to it (or in a tabbed
configuration for 'over' and 'under').
configuration for 'above' and 'below').
=========== =================================================================
All extra keyword arguments are passed to Dock.__init__() if *dock* is
Expand Down Expand Up @@ -316,4 +316,4 @@ def dropEvent(self, *args):
DockDrop.dropEvent(self, *args)




7 changes: 4 additions & 3 deletions exporters/Exporter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from pyqtgraph.widgets.FileDialog import FileDialog
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore, QtSvg
from pyqtgraph.python2_3 import asUnicode
import os, re
LastExportDirectory = None

Expand Down Expand Up @@ -56,13 +57,13 @@ def fileSaveDialog(self, filter=None, opts=None):
return

def fileSaveFinished(self, fileName):
fileName = str(fileName)
fileName = asUnicode(fileName)
global LastExportDirectory
LastExportDirectory = os.path.split(fileName)[0]

## 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()))
selectedExt = re.search(r'\*\.(\w+)\b', asUnicode(self.fileDialog.selectedNameFilter()))
if selectedExt is not None:
selectedExt = selectedExt.groups()[0].lower()
if ext != selectedExt:
Expand Down Expand Up @@ -118,7 +119,7 @@ def getPaintItems(self, root=None):
else:
childs = root.childItems()
rootItem = [root]
childs.sort(lambda a,b: cmp(a.zValue(), b.zValue()))
childs.sort(key=lambda a: a.zValue())
while len(childs) > 0:
ch = childs.pop(0)
tree = self.getPaintItems(ch)
Expand Down
16 changes: 13 additions & 3 deletions exporters/ImageExporter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .Exporter import Exporter
from pyqtgraph.parametertree import Parameter
from pyqtgraph.Qt import QtGui, QtCore, QtSvg
from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
import pyqtgraph as pg
import numpy as np

Expand All @@ -17,7 +17,11 @@ def __init__(self, item):
scene = item.scene()
else:
scene = item
bg = scene.views()[0].backgroundBrush().color()
bgbrush = scene.views()[0].backgroundBrush()
bg = bgbrush.color()
if bgbrush.style() == QtCore.Qt.NoBrush:
bg.setAlpha(0)

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)},
Expand All @@ -42,7 +46,10 @@ def parameters(self):

def export(self, fileName=None, toBytes=False, copy=False):
if fileName is None and not toBytes and not copy:
filter = ["*."+str(f) for f in QtGui.QImageWriter.supportedImageFormats()]
if USE_PYSIDE:
filter = ["*."+str(f) for f in QtGui.QImageWriter.supportedImageFormats()]
else:
filter = ["*."+bytes(f).decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()]
preferred = ['*.png', '*.tif', '*.jpg']
for p in preferred[::-1]:
if p in filter:
Expand All @@ -57,6 +64,9 @@ def export(self, fileName=None, toBytes=False, copy=False):

#self.png = QtGui.QImage(targetRect.size(), QtGui.QImage.Format_ARGB32)
#self.png.fill(pyqtgraph.mkColor(self.params['background']))
w, h = self.params['width'], self.params['height']
if w == 0 or h == 0:
raise Exception("Cannot export image with size=0 (requested export size is %dx%d)" % (w,h))
bg = np.empty((self.params['width'], self.params['height'], 4), dtype=np.ubyte)
color = self.params['background']
bg[:,:,0] = color.blue()
Expand Down
Loading

0 comments on commit 757dc50

Please sign in to comment.