merge from dev
This commit is contained in:
commit
0f97ac77e2
|
@ -1,4 +1,5 @@
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
from pyqtgraph.python2_3 import sortList
|
||||||
#try:
|
#try:
|
||||||
#from PyQt4 import QtOpenGL
|
#from PyQt4 import QtOpenGL
|
||||||
#HAVE_OPENGL = True
|
#HAVE_OPENGL = True
|
||||||
|
@ -505,18 +506,19 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||||
menusToAdd = []
|
menusToAdd = []
|
||||||
while item is not self:
|
while item is not self:
|
||||||
item = item.parentItem()
|
item = item.parentItem()
|
||||||
|
|
||||||
if item is None:
|
if item is None:
|
||||||
item = self
|
item = self
|
||||||
|
|
||||||
if not hasattr(item, "getContextMenus"):
|
if not hasattr(item, "getContextMenus"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
subMenus = item.getContextMenus(event)
|
subMenus = item.getContextMenus(event)
|
||||||
|
if subMenus is None:
|
||||||
|
continue
|
||||||
if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus
|
if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus
|
||||||
subMenus = [subMenus]
|
subMenus = [subMenus]
|
||||||
|
|
||||||
for sm in subMenus:
|
for sm in subMenus:
|
||||||
menusToAdd.append(sm)
|
menusToAdd.append(sm)
|
||||||
|
|
||||||
|
|
30
Qt.py
30
Qt.py
|
@ -1,15 +1,23 @@
|
||||||
## Do all Qt imports from here to allow easier PyQt / PySide compatibility
|
## Do all Qt imports from here to allow easier PyQt / PySide compatibility
|
||||||
|
|
||||||
#from PySide import QtGui, QtCore, QtOpenGL, QtSvg
|
USE_PYSIDE = False ## If False, import PyQt4. If True, import PySide
|
||||||
from PyQt4 import QtGui, QtCore
|
## Note that when switching between PyQt and PySide, all template
|
||||||
try:
|
## files (*.ui) must be rebuilt for the target library.
|
||||||
from PyQt4 import QtSvg
|
|
||||||
except ImportError:
|
if USE_PYSIDE:
|
||||||
pass
|
from PySide import QtGui, QtCore, QtOpenGL, QtSvg
|
||||||
try:
|
import PySide
|
||||||
from PyQt4 import QtOpenGL
|
VERSION_INFO = 'PySide ' + PySide.__version__
|
||||||
except ImportError:
|
else:
|
||||||
pass
|
from PyQt4 import QtGui, QtCore
|
||||||
|
try:
|
||||||
|
from PyQt4 import QtSvg
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
from PyQt4 import QtOpenGL
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
if not hasattr(QtCore, 'Signal'):
|
|
||||||
QtCore.Signal = QtCore.pyqtSignal
|
QtCore.Signal = QtCore.pyqtSignal
|
||||||
|
VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
|
||||||
|
|
|
@ -76,7 +76,7 @@ class SRTTransform(QtGui.QTransform):
|
||||||
m = pg.SRTTransform3D(m)
|
m = pg.SRTTransform3D(m)
|
||||||
angle, axis = m.getRotation()
|
angle, axis = m.getRotation()
|
||||||
if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1):
|
if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1):
|
||||||
print angle, axis
|
print("angle: %s axis: %s" % (str(angle), str(axis)))
|
||||||
raise Exception("Can only convert 4x4 matrix to 3x3 if rotation is around Z-axis.")
|
raise Exception("Can only convert 4x4 matrix to 3x3 if rotation is around Z-axis.")
|
||||||
self._state = {
|
self._state = {
|
||||||
'pos': Point(m.getTranslation()),
|
'pos': Point(m.getTranslation()),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from Qt import QtCore, QtGui
|
from .Qt import QtCore, QtGui
|
||||||
from Vector import Vector
|
from .Vector import Vector
|
||||||
from SRTTransform import SRTTransform
|
from .SRTTransform import SRTTransform
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy.linalg
|
import scipy.linalg
|
||||||
|
@ -136,15 +136,15 @@ class SRTTransform3D(QtGui.QMatrix4x4):
|
||||||
try:
|
try:
|
||||||
evals, evecs = scipy.linalg.eig(r)
|
evals, evecs = scipy.linalg.eig(r)
|
||||||
except:
|
except:
|
||||||
print "Rotation matrix:", r
|
print("Rotation matrix: %s" % str(r))
|
||||||
print "Scale:", scale
|
print("Scale: %s" % str(scale))
|
||||||
print "Original matrix:", m
|
print("Original matrix: %s" % str(m))
|
||||||
raise
|
raise
|
||||||
eigIndex = np.argwhere(np.abs(evals-1) < 1e-7)
|
eigIndex = np.argwhere(np.abs(evals-1) < 1e-7)
|
||||||
if len(eigIndex) < 1:
|
if len(eigIndex) < 1:
|
||||||
print "eigenvalues:", evals
|
print("eigenvalues: %s" % str(evals))
|
||||||
print "eigenvectors:", evecs
|
print("eigenvectors: %s" % str(evecs))
|
||||||
print "index:", eigIndex, evals-1
|
print("index: %s, %s" % (str(eigIndex), str(evals-1)))
|
||||||
raise Exception("Could not determine rotation axis.")
|
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
|
axis /= ((axis**2).sum())**0.5
|
||||||
|
@ -259,23 +259,23 @@ if __name__ == '__main__':
|
||||||
tr3 = QtGui.QTransform()
|
tr3 = QtGui.QTransform()
|
||||||
tr3.translate(20, 0)
|
tr3.translate(20, 0)
|
||||||
tr3.rotate(45)
|
tr3.rotate(45)
|
||||||
print "QTransform -> Transform:", SRTTransform(tr3)
|
print("QTransform -> Transform: %s" % str(SRTTransform(tr3)))
|
||||||
|
|
||||||
print "tr1:", tr1
|
print("tr1: %s" % str(tr1))
|
||||||
|
|
||||||
tr2.translate(20, 0)
|
tr2.translate(20, 0)
|
||||||
tr2.rotate(45)
|
tr2.rotate(45)
|
||||||
print "tr2:", tr2
|
print("tr2: %s" % str(tr2))
|
||||||
|
|
||||||
dt = tr2/tr1
|
dt = tr2/tr1
|
||||||
print "tr2 / tr1 = ", dt
|
print("tr2 / tr1 = %s" % str(dt))
|
||||||
|
|
||||||
print "tr2 * tr1 = ", tr2*tr1
|
print("tr2 * tr1 = %s" % str(tr2*tr1))
|
||||||
|
|
||||||
tr4 = SRTTransform()
|
tr4 = SRTTransform()
|
||||||
tr4.scale(-1, 1)
|
tr4.scale(-1, 1)
|
||||||
tr4.rotate(30)
|
tr4.rotate(30)
|
||||||
print "tr1 * tr4 = ", tr1*tr4
|
print("tr1 * tr4 = %s" % str(tr1*tr4))
|
||||||
|
|
||||||
w1 = widgets.TestROI((19,19), (22, 22), invertible=True)
|
w1 = widgets.TestROI((19,19), (22, 22), invertible=True)
|
||||||
#w2 = widgets.TestROI((0,0), (150, 150))
|
#w2 = widgets.TestROI((0,0), (150, 150))
|
||||||
|
|
|
@ -5,7 +5,7 @@ Copyright 2010 Luke Campagnola
|
||||||
Distributed under MIT/X11 license. See license.txt for more infomation.
|
Distributed under MIT/X11 license. See license.txt for more infomation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from Qt import QtGui, QtCore
|
from .Qt import QtGui, QtCore
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
class Vector(QtGui.QVector3D):
|
class Vector(QtGui.QVector3D):
|
||||||
|
|
|
@ -10,6 +10,7 @@ of a large group of widgets.
|
||||||
|
|
||||||
from .Qt import QtCore, QtGui
|
from .Qt import QtCore, QtGui
|
||||||
import weakref, inspect
|
import weakref, inspect
|
||||||
|
from .python2_3 import asUnicode
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['WidgetGroup']
|
__all__ = ['WidgetGroup']
|
||||||
|
|
36
__init__.py
36
__init__.py
|
@ -1,18 +1,21 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
REVISION = None
|
||||||
|
|
||||||
### import all the goodies and add some helper functions for easy CLI use
|
### import all the goodies and add some helper functions for easy CLI use
|
||||||
|
|
||||||
## 'Qt' is a local module; it is intended mainly to cover up the differences
|
## 'Qt' is a local module; it is intended mainly to cover up the differences
|
||||||
## between PyQt4 and PySide.
|
## between PyQt4 and PySide.
|
||||||
from .Qt import QtGui
|
from .Qt import QtGui
|
||||||
|
|
||||||
## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause)
|
## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause)
|
||||||
#if QtGui.QApplication.instance() is None:
|
#if QtGui.QApplication.instance() is None:
|
||||||
#app = QtGui.QApplication([])
|
#app = QtGui.QApplication([])
|
||||||
|
|
||||||
import sys
|
import os, sys
|
||||||
|
|
||||||
## check python version
|
## check python version
|
||||||
if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] != 7):
|
## Allow anything >= 2.7
|
||||||
|
if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] < 7):
|
||||||
raise Exception("Pyqtgraph requires Python version 2.7 (this is %d.%d)" % (sys.version_info[0], sys.version_info[1]))
|
raise Exception("Pyqtgraph requires Python version 2.7 (this is %d.%d)" % (sys.version_info[0], sys.version_info[1]))
|
||||||
|
|
||||||
## helpers for 2/3 compatibility
|
## helpers for 2/3 compatibility
|
||||||
|
@ -30,13 +33,15 @@ else:
|
||||||
useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff.
|
useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff.
|
||||||
|
|
||||||
CONFIG_OPTIONS = {
|
CONFIG_OPTIONS = {
|
||||||
'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl.
|
'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl.
|
||||||
'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox
|
'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox
|
||||||
'foregroundColor': (200,200,200),
|
'foreground': (150, 150, 150), ## default foreground color for axes, labels, etc.
|
||||||
'backgroundColor': (0,0,0),
|
'background': (0, 0, 0), ## default background for GraphicsWidget
|
||||||
'antialias': False,
|
'antialias': False,
|
||||||
|
'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def setConfigOption(opt, value):
|
def setConfigOption(opt, value):
|
||||||
CONFIG_OPTIONS[opt] = value
|
CONFIG_OPTIONS[opt] = value
|
||||||
|
|
||||||
|
@ -44,6 +49,23 @@ def getConfigOption(opt):
|
||||||
return CONFIG_OPTIONS[opt]
|
return CONFIG_OPTIONS[opt]
|
||||||
|
|
||||||
|
|
||||||
|
def systemInfo():
|
||||||
|
print("sys.platform: %s" % sys.platform)
|
||||||
|
print("sys.version: %s" % sys.version)
|
||||||
|
from .Qt import VERSION_INFO
|
||||||
|
print("qt bindings: %s" % VERSION_INFO)
|
||||||
|
|
||||||
|
global REVISION
|
||||||
|
if REVISION is None: ## this code was probably checked out from bzr; look up the last-revision file
|
||||||
|
lastRevFile = os.path.join(os.path.dirname(__file__), '.bzr', 'branch', 'last-revision')
|
||||||
|
if os.path.exists(lastRevFile):
|
||||||
|
REVISION = open(lastRevFile, 'r').read().strip()
|
||||||
|
|
||||||
|
print("pyqtgraph: %s" % REVISION)
|
||||||
|
print("config:")
|
||||||
|
import pprint
|
||||||
|
pprint.pprint(CONFIG_OPTIONS)
|
||||||
|
|
||||||
## Rename orphaned .pyc files. This is *probably* safe :)
|
## Rename orphaned .pyc files. This is *probably* safe :)
|
||||||
|
|
||||||
def renamePyc(startDir):
|
def renamePyc(startDir):
|
||||||
|
@ -105,7 +127,7 @@ def importAll(path, excludes=()):
|
||||||
globals()[k] = getattr(mod, k)
|
globals()[k] = getattr(mod, k)
|
||||||
|
|
||||||
importAll('graphicsItems')
|
importAll('graphicsItems')
|
||||||
importAll('widgets', excludes=['MatplotlibWidget'])
|
importAll('widgets', excludes=['MatplotlibWidget', 'RemoteGraphicsView'])
|
||||||
|
|
||||||
from .imageview import *
|
from .imageview import *
|
||||||
from .WidgetGroup import *
|
from .WidgetGroup import *
|
||||||
|
|
|
@ -13,6 +13,7 @@ import re, os, sys
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
GLOBAL_PATH = None # so not thread safe.
|
GLOBAL_PATH = None # so not thread safe.
|
||||||
from . import units
|
from . import units
|
||||||
|
from .python2_3 import asUnicode
|
||||||
|
|
||||||
class ParseError(Exception):
|
class ParseError(Exception):
|
||||||
def __init__(self, message, lineNum, line, fileName=None):
|
def __init__(self, message, lineNum, line, fileName=None):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from PyQt4 import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
from pyqtgraph.python2_3 import asUnicode
|
||||||
|
|
||||||
class CmdInput(QtGui.QLineEdit):
|
class CmdInput(QtGui.QLineEdit):
|
||||||
|
|
||||||
|
@ -25,10 +26,10 @@ class CmdInput(QtGui.QLineEdit):
|
||||||
self.execCmd()
|
self.execCmd()
|
||||||
else:
|
else:
|
||||||
QtGui.QLineEdit.keyPressEvent(self, ev)
|
QtGui.QLineEdit.keyPressEvent(self, ev)
|
||||||
self.history[0] = unicode(self.text())
|
self.history[0] = asUnicode(self.text())
|
||||||
|
|
||||||
def execCmd(self):
|
def execCmd(self):
|
||||||
cmd = unicode(self.text())
|
cmd = asUnicode(self.text())
|
||||||
if len(self.history) == 1 or cmd != self.history[1]:
|
if len(self.history) == 1 or cmd != self.history[1]:
|
||||||
self.history.insert(1, cmd)
|
self.history.insert(1, cmd)
|
||||||
#self.lastCmd = cmd
|
#self.lastCmd = cmd
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
|
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import sys, re, os, time, traceback
|
import sys, re, os, time, traceback, subprocess
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import template
|
from . import template
|
||||||
import pyqtgraph.exceptionHandling as exceptionHandling
|
import pyqtgraph.exceptionHandling as exceptionHandling
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
EDITOR = "pykate {fileName}:{lineNum}"
|
|
||||||
|
|
||||||
class ConsoleWidget(QtGui.QWidget):
|
class ConsoleWidget(QtGui.QWidget):
|
||||||
"""
|
"""
|
||||||
Widget displaying console output and accepting command input.
|
Widget displaying console output and accepting command input.
|
||||||
|
@ -24,7 +22,7 @@ class ConsoleWidget(QtGui.QWidget):
|
||||||
be baffling and frustrating to users since it would appear the program has frozen.
|
be baffling and frustrating to users since it would appear the program has frozen.
|
||||||
- some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces
|
- some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces
|
||||||
- ability to add extra features like exception stack introspection
|
- ability to add extra features like exception stack introspection
|
||||||
- ability to have multiple interactive prompts for remotely generated processes
|
- ability to have multiple interactive prompts, including for spawned sub-processes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None, namespace=None, historyFile=None, text=None, editor=None):
|
def __init__(self, parent=None, namespace=None, historyFile=None, text=None, editor=None):
|
||||||
|
@ -72,8 +70,8 @@ class ConsoleWidget(QtGui.QWidget):
|
||||||
self.ui.historyList.itemDoubleClicked.connect(self.cmdDblClicked)
|
self.ui.historyList.itemDoubleClicked.connect(self.cmdDblClicked)
|
||||||
self.ui.exceptionBtn.toggled.connect(self.ui.exceptionGroup.setVisible)
|
self.ui.exceptionBtn.toggled.connect(self.ui.exceptionGroup.setVisible)
|
||||||
|
|
||||||
self.ui.catchAllExceptionsBtn.toggled.connect(self.catchAllToggled)
|
self.ui.catchAllExceptionsBtn.toggled.connect(self.catchAllExceptions)
|
||||||
self.ui.catchNextExceptionBtn.toggled.connect(self.catchNextToggled)
|
self.ui.catchNextExceptionBtn.toggled.connect(self.catchNextException)
|
||||||
self.ui.clearExceptionBtn.clicked.connect(self.clearExceptionClicked)
|
self.ui.clearExceptionBtn.clicked.connect(self.clearExceptionClicked)
|
||||||
self.ui.exceptionStackList.itemClicked.connect(self.stackItemClicked)
|
self.ui.exceptionStackList.itemClicked.connect(self.stackItemClicked)
|
||||||
self.ui.exceptionStackList.itemDoubleClicked.connect(self.stackItemDblClicked)
|
self.ui.exceptionStackList.itemDoubleClicked.connect(self.stackItemDblClicked)
|
||||||
|
@ -229,15 +227,25 @@ class ConsoleWidget(QtGui.QWidget):
|
||||||
def flush(self):
|
def flush(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def catchAllToggled(self, b):
|
def catchAllExceptions(self, catch=True):
|
||||||
if b:
|
"""
|
||||||
|
If True, the console will catch all unhandled exceptions and display the stack
|
||||||
|
trace. Each exception caught clears the last.
|
||||||
|
"""
|
||||||
|
self.ui.catchAllExceptionsBtn.setChecked(catch)
|
||||||
|
if catch:
|
||||||
self.ui.catchNextExceptionBtn.setChecked(False)
|
self.ui.catchNextExceptionBtn.setChecked(False)
|
||||||
exceptionHandling.register(self.allExceptionsHandler)
|
exceptionHandling.register(self.allExceptionsHandler)
|
||||||
else:
|
else:
|
||||||
exceptionHandling.unregister(self.allExceptionsHandler)
|
exceptionHandling.unregister(self.allExceptionsHandler)
|
||||||
|
|
||||||
def catchNextToggled(self, b):
|
def catchNextException(self, catch=True):
|
||||||
if b:
|
"""
|
||||||
|
If True, the console will catch the next unhandled exception and display the stack
|
||||||
|
trace.
|
||||||
|
"""
|
||||||
|
self.ui.catchNextExceptionBtn.setChecked(catch)
|
||||||
|
if catch:
|
||||||
self.ui.catchAllExceptionsBtn.setChecked(False)
|
self.ui.catchAllExceptionsBtn.setChecked(False)
|
||||||
exceptionHandling.register(self.nextExceptionHandler)
|
exceptionHandling.register(self.nextExceptionHandler)
|
||||||
else:
|
else:
|
||||||
|
@ -254,11 +262,15 @@ class ConsoleWidget(QtGui.QWidget):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def stackItemDblClicked(self, item):
|
def stackItemDblClicked(self, item):
|
||||||
global EDITOR
|
editor = self.editor
|
||||||
|
if editor is None:
|
||||||
|
editor = pg.getConfigOption('editorCommand')
|
||||||
|
if editor is None:
|
||||||
|
return
|
||||||
tb = self.currentFrame()
|
tb = self.currentFrame()
|
||||||
lineNum = tb.tb_lineno
|
lineNum = tb.tb_lineno
|
||||||
fileName = tb.tb_frame.f_code.co_filename
|
fileName = tb.tb_frame.f_code.co_filename
|
||||||
os.system(EDITOR.format(fileName=fileName, lineNum=lineNum))
|
subprocess.Popen(self.editor.format(fileName=fileName, lineNum=lineNum), shell=True)
|
||||||
|
|
||||||
|
|
||||||
def allExceptionsHandler(self, *args):
|
def allExceptionsHandler(self, *args):
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
from Console import ConsoleWidget
|
from .Console import ConsoleWidget
|
|
@ -91,7 +91,7 @@ class Ui_Form(object):
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||||
|
|
||||||
def retranslateUi(self, Form):
|
def retranslateUi(self, Form):
|
||||||
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
|
Form.setWindowTitle(QtGui.QApplication.translate("Console", "Console", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.historyBtn.setText(QtGui.QApplication.translate("Form", "History..", None, QtGui.QApplication.UnicodeUTF8))
|
self.historyBtn.setText(QtGui.QApplication.translate("Form", "History..", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.exceptionBtn.setText(QtGui.QApplication.translate("Form", "Exceptions..", None, QtGui.QApplication.UnicodeUTF8))
|
self.exceptionBtn.setText(QtGui.QApplication.translate("Form", "Exceptions..", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.exceptionGroup.setTitle(QtGui.QApplication.translate("Form", "Exception Handling", None, QtGui.QApplication.UnicodeUTF8))
|
self.exceptionGroup.setTitle(QtGui.QApplication.translate("Form", "Exception Handling", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
@ -101,4 +101,4 @@ class Ui_Form(object):
|
||||||
self.runSelectedFrameCheck.setText(QtGui.QApplication.translate("Form", "Run commands in selected stack frame", None, QtGui.QApplication.UnicodeUTF8))
|
self.runSelectedFrameCheck.setText(QtGui.QApplication.translate("Form", "Run commands in selected stack frame", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.exceptionInfoLabel.setText(QtGui.QApplication.translate("Form", "Exception Info", None, QtGui.QApplication.UnicodeUTF8))
|
self.exceptionInfoLabel.setText(QtGui.QApplication.translate("Form", "Exception Info", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
from CmdInput import CmdInput
|
from .CmdInput import CmdInput
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Form</string>
|
<string>Console</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<property name="margin">
|
<property name="margin">
|
||||||
|
@ -153,7 +153,7 @@
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>CmdInput</class>
|
<class>CmdInput</class>
|
||||||
<extends>QLineEdit</extends>
|
<extends>QLineEdit</extends>
|
||||||
<header>CmdInput</header>
|
<header>.CmdInput</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
from .Container import *
|
from .Container import *
|
||||||
from .DockDrop import *
|
from .DockDrop import *
|
||||||
|
from .Dock import Dock
|
||||||
import pyqtgraph.debug as debug
|
import pyqtgraph.debug as debug
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
import sys
|
|
||||||
|
|
||||||
## Make sure pyqtgraph is importable
|
|
||||||
p = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
p = os.path.join(p, '..', '..')
|
|
||||||
sys.path.insert(0, p)
|
|
||||||
|
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
|
||||||
|
|
||||||
from .DockArea import *
|
|
||||||
from .Dock import *
|
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
|
||||||
win = QtGui.QMainWindow()
|
|
||||||
area = DockArea()
|
|
||||||
win.setCentralWidget(area)
|
|
||||||
win.resize(800,800)
|
|
||||||
from .Dock import Dock
|
|
||||||
d1 = Dock("Dock1", size=(200,200))
|
|
||||||
d2 = Dock("Dock2", size=(100,100))
|
|
||||||
d3 = Dock("Dock3", size=(1,1))
|
|
||||||
d4 = Dock("Dock4", size=(50,50))
|
|
||||||
d5 = Dock("Dock5", size=(100,100))
|
|
||||||
d6 = Dock("Dock6", size=(300,300))
|
|
||||||
area.addDock(d1, 'left')
|
|
||||||
area.addDock(d2, 'right')
|
|
||||||
area.addDock(d3, 'bottom')
|
|
||||||
area.addDock(d4, 'right')
|
|
||||||
area.addDock(d5, 'left', d1)
|
|
||||||
area.addDock(d6, 'top', d4)
|
|
||||||
|
|
||||||
area.moveDock(d6, 'above', d4)
|
|
||||||
d3.hideTitleBar()
|
|
||||||
|
|
||||||
print("===build complete====")
|
|
||||||
|
|
||||||
for d in [d1, d2, d3, d4, d5]:
|
|
||||||
w = QtGui.QWidget()
|
|
||||||
l = QtGui.QVBoxLayout()
|
|
||||||
w.setLayout(l)
|
|
||||||
btns = []
|
|
||||||
for i in range(4):
|
|
||||||
btns.append(QtGui.QPushButton("%s Button %d"%(d.name(), i)))
|
|
||||||
l.addWidget(btns[-1])
|
|
||||||
d.w = (w, l, btns)
|
|
||||||
d.addWidget(w)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import pyqtgraph as pg
|
|
||||||
p = pg.PlotWidget()
|
|
||||||
d6.addWidget(p)
|
|
||||||
|
|
||||||
print("===widgets added===")
|
|
||||||
|
|
||||||
|
|
||||||
#s = area.saveState()
|
|
||||||
|
|
||||||
|
|
||||||
#print "\n\n-------restore----------\n\n"
|
|
||||||
#area.restoreState(s)
|
|
||||||
s = None
|
|
||||||
def save():
|
|
||||||
global s
|
|
||||||
s = area.saveState()
|
|
||||||
|
|
||||||
def load():
|
|
||||||
global s
|
|
||||||
area.restoreState(s)
|
|
||||||
|
|
||||||
|
|
||||||
#d6.container().setCurrentIndex(0)
|
|
||||||
#d2.label.setTabPos(40)
|
|
||||||
|
|
||||||
#win2 = QtGui.QMainWindow()
|
|
||||||
#area2 = DockArea()
|
|
||||||
#win2.setCentralWidget(area2)
|
|
||||||
#win2.resize(800,800)
|
|
||||||
|
|
||||||
|
|
||||||
win.show()
|
|
||||||
#win2.show()
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import sys, os
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
path = os.path.dirname(os.path.abspath(__file__))
|
path = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.insert(0, os.path.join(path, '..', '..', '..'))
|
sys.path.insert(0, os.path.join(path, '..', '..', '..'))
|
||||||
|
print sys.path
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -43,5 +43,12 @@ While I consider this approach somewhat lazy, it is often the case that 'lazy' i
|
||||||
Embedding widgets inside PyQt applications
|
Embedding widgets inside PyQt applications
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
For the serious application developer, all of the functionality in pyqtgraph is available via widgets that can be embedded just like any other Qt widgets. Most importantly, see: :class:`PlotWidget <pyqtgraph.PlotWidget>`, :class:`ImageView <pyqtgraph.ImageView>`, :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`, and :class:`GraphicsView <pyqtgraph.GraphicsView>`. Pyqtgraph's widgets can be included in Designer's ui files via the "Promote To..." functionality.
|
For the serious application developer, all of the functionality in pyqtgraph is available via :ref:`widgets <api_widgets>` that can be embedded just like any other Qt widgets. Most importantly, see: :class:`PlotWidget <pyqtgraph.PlotWidget>`, :class:`ImageView <pyqtgraph.ImageView>`, :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`, and :class:`GraphicsView <pyqtgraph.GraphicsView>`. Pyqtgraph's widgets can be included in Designer's ui files via the "Promote To..." functionality:
|
||||||
|
|
||||||
|
#. In Designer, create a QGraphicsView widget ("Graphics View" under the "Display Widgets" category).
|
||||||
|
#. Right-click on the QGraphicsView and select "Promote To...".
|
||||||
|
#. Under "Promoted class name", enter the class name you wish to use ("PlotWidget", "GraphicsLayoutWidget", etc).
|
||||||
|
#. Under "Header file", enter "pyqtgraph".
|
||||||
|
#. Click "Add", then click "Promote".
|
||||||
|
|
||||||
|
See the designer documentation for more information on promoting widgets.
|
||||||
|
|
|
@ -1,22 +1,47 @@
|
||||||
Line, Fill, and Color
|
Line, Fill, and Color
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Many functions and methods in pyqtgraph accept arguments specifying the line style (pen), fill style (brush), or color.
|
Qt relies on its QColor, QPen and QBrush classes for specifying line and fill styles for all of its drawing.
|
||||||
|
Internally, pyqtgraph uses the same system but also allows many shorthand methods of specifying
|
||||||
|
the same style options.
|
||||||
|
|
||||||
For these function arguments, the following values may be used:
|
Many functions and methods in pyqtgraph accept arguments specifying the line style (pen), fill style (brush), or color.
|
||||||
|
For most of these function arguments, the following values may be used:
|
||||||
|
|
||||||
* single-character string representing color (b, g, r, c, m, y, k, w)
|
* single-character string representing color (b, g, r, c, m, y, k, w)
|
||||||
* (r, g, b) or (r, g, b, a) tuple
|
* (r, g, b) or (r, g, b, a) tuple
|
||||||
* single greyscale value (0.0 - 1.0)
|
* single greyscale value (0.0 - 1.0)
|
||||||
* (index, maximum) tuple for automatically iterating through colors (see functions.intColor)
|
* (index, maximum) tuple for automatically iterating through colors (see :func:`intColor <pyqtgraph.intColor>`)
|
||||||
* QColor
|
* QColor
|
||||||
* QPen / QBrush where appropriate
|
* QPen / QBrush where appropriate
|
||||||
|
|
||||||
Notably, more complex pens and brushes can be easily built using the mkPen() / mkBrush() functions or with Qt's QPen and QBrush classes::
|
Notably, more complex pens and brushes can be easily built using the
|
||||||
|
:func:`mkPen() <pyqtgraph.mkPen>` / :func:`mkBrush() <pyqtgraph.mkBrush>` functions or with Qt's QPen and QBrush classes::
|
||||||
|
|
||||||
mkPen('y', width=3, style=QtCore.Qt.DashLine) ## Make a dashed yellow line 2px wide
|
mkPen('y', width=3, style=QtCore.Qt.DashLine) ## Make a dashed yellow line 2px wide
|
||||||
mkPen(0.5) ## solid grey line 1px wide
|
mkPen(0.5) ## solid grey line 1px wide
|
||||||
mkPen(color=(200, 200, 255), style=QtCore.Qt.DotLine) ## Dotted pale-blue line
|
mkPen(color=(200, 200, 255), style=QtCore.Qt.DotLine) ## Dotted pale-blue line
|
||||||
|
|
||||||
See the Qt documentation for 'QPen' and 'PenStyle' for more options.
|
See the Qt documentation for 'QPen' and 'PenStyle' for more line-style options and 'QBrush' for more fill options.
|
||||||
Colors can also be built using mkColor(), intColor(), hsvColor(), or Qt's QColor class
|
Colors can also be built using :func:`mkColor() <pyqtgraph.mkColor>`,
|
||||||
|
:func:`intColor() <pyqtgraph.intColor>`, :func:`hsvColor() <pyqtgraph.hsvColor>`, or Qt's QColor class.
|
||||||
|
|
||||||
|
|
||||||
|
Default Background and Foreground Colors
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
By default, pyqtgraph uses a black background for its plots and grey for axes, text, and plot lines.
|
||||||
|
These defaults can be changed using pyqtgraph.setConfigOption()::
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
## Switch to using white background and black foreground
|
||||||
|
pg.setConfigOption('background', 'w')
|
||||||
|
pg.setConfigOption('foreground', 'k')
|
||||||
|
|
||||||
|
## The following plot has inverted colors
|
||||||
|
pg.plot([1,4,2,3,5])
|
||||||
|
|
||||||
|
(Note that this must be set *before* creating any widgets)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
.. _api_widgets:
|
||||||
|
|
||||||
Pyqtgraph's Widgets
|
Pyqtgraph's Widgets
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
|
|
@ -4,41 +4,73 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||||
|
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import user
|
import numpy as np
|
||||||
|
|
||||||
app = QtGui.QApplication([])
|
app = QtGui.QApplication([])
|
||||||
view = pg.GraphicsView()
|
view = pg.GraphicsView()
|
||||||
l = pg.GraphicsLayout(border=pg.mkPen(0, 0, 255))
|
l = pg.GraphicsLayout(border=(100,100,100))
|
||||||
view.setCentralItem(l)
|
view.setCentralItem(l)
|
||||||
view.show()
|
view.show()
|
||||||
|
view.resize(800,600)
|
||||||
|
|
||||||
|
## Title at top
|
||||||
|
text = """
|
||||||
|
This example demonstrates the use of GraphicsLayout to arrange items in a grid.<br>
|
||||||
|
The items added to the layout must be subclasses of QGraphicsWidget (this includes <br>
|
||||||
|
PlotItem, ViewBox, LabelItem, and GrphicsLayout itself).
|
||||||
|
"""
|
||||||
|
l.addLabel(text, col=1, colspan=4)
|
||||||
|
l.nextRow()
|
||||||
|
|
||||||
|
## Put vertical label on left side
|
||||||
|
l.addLabel('Long Vertical Label', angle=-90, rowspan=3)
|
||||||
|
|
||||||
## Add 3 plots into the first row (automatic position)
|
## Add 3 plots into the first row (automatic position)
|
||||||
p1 = l.addPlot()
|
p1 = l.addPlot(title="Plot 1")
|
||||||
p2 = l.addPlot()
|
p2 = l.addPlot(title="Plot 2")
|
||||||
p3 = l.addPlot()
|
vb = l.addViewBox(lockAspect=True)
|
||||||
|
img = pg.ImageItem(np.random.normal(size=(100,100)))
|
||||||
|
vb.addItem(img)
|
||||||
|
vb.autoRange()
|
||||||
|
|
||||||
## Add a viewbox into the second row (automatic position)
|
|
||||||
|
## Add a sub-layout into the second row (automatic position)
|
||||||
|
## The added item should avoid the first column, which is already filled
|
||||||
l.nextRow()
|
l.nextRow()
|
||||||
vb = l.addViewBox(colspan=3)
|
l2 = l.addLayout(colspan=3, border=(50,0,0))
|
||||||
|
l2.setContentsMargins(10, 10, 10, 10)
|
||||||
|
l2.addLabel("Sub-layout: this layout demonstrates the use of shared axes and axis labels", colspan=3)
|
||||||
|
l2.nextRow()
|
||||||
|
l2.addLabel('Vertical Axis Label', angle=-90, rowspan=2)
|
||||||
|
p21 = l2.addPlot()
|
||||||
|
p22 = l2.addPlot()
|
||||||
|
l2.nextRow()
|
||||||
|
p23 = l2.addPlot()
|
||||||
|
p24 = l2.addPlot()
|
||||||
|
l2.nextRow()
|
||||||
|
l2.addLabel("HorizontalAxisLabel", col=1, colspan=2)
|
||||||
|
|
||||||
|
## hide axes on some plots
|
||||||
|
p21.hideAxis('bottom')
|
||||||
|
p22.hideAxis('bottom')
|
||||||
|
p22.hideAxis('left')
|
||||||
|
p24.hideAxis('left')
|
||||||
|
p21.hideButtons()
|
||||||
|
p22.hideButtons()
|
||||||
|
p23.hideButtons()
|
||||||
|
p24.hideButtons()
|
||||||
|
|
||||||
|
|
||||||
## Add 2 more plots into the third row (manual position)
|
## Add 2 more plots into the third row (manual position)
|
||||||
p4 = l.addPlot(row=2, col=0)
|
p4 = l.addPlot(row=3, col=1)
|
||||||
p5 = l.addPlot(row=2, col=1, colspan=2)
|
p5 = l.addPlot(row=3, col=2, colspan=2)
|
||||||
|
|
||||||
|
## show some content in the plots
|
||||||
|
|
||||||
## show some content
|
|
||||||
p1.plot([1,3,2,4,3,5])
|
p1.plot([1,3,2,4,3,5])
|
||||||
p2.plot([1,3,2,4,3,5])
|
p2.plot([1,3,2,4,3,5])
|
||||||
p3.plot([1,3,2,4,3,5])
|
|
||||||
p4.plot([1,3,2,4,3,5])
|
p4.plot([1,3,2,4,3,5])
|
||||||
p5.plot([1,3,2,4,3,5])
|
p5.plot([1,3,2,4,3,5])
|
||||||
|
|
||||||
b = QtGui.QGraphicsRectItem(0, 0, 1, 1)
|
|
||||||
b.setPen(pg.mkPen(255,255,0))
|
|
||||||
vb.addItem(b)
|
|
||||||
vb.setRange(QtCore.QRectF(-1, -1, 3, 3))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Start Qt event loop unless running in interactive mode.
|
## Start Qt event loop unless running in interactive mode.
|
||||||
|
|
19
examples/RemoteGraphicsView.py
Normal file
19
examples/RemoteGraphicsView.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
import pyqtgraph as pg
|
||||||
|
app = pg.mkQApp()
|
||||||
|
|
||||||
|
v = pg.RemoteGraphicsView()
|
||||||
|
v.show()
|
||||||
|
|
||||||
|
plt = v.pg.PlotItem()
|
||||||
|
v.setCentralItem(plt)
|
||||||
|
plt.plot([1,4,2,3,6,2,3,4,2,3], pen='g')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
|
@ -11,11 +11,14 @@ examples = OrderedDict([
|
||||||
('Command-line usage', 'CLIexample.py'),
|
('Command-line usage', 'CLIexample.py'),
|
||||||
('Basic Plotting', 'Plotting.py'),
|
('Basic Plotting', 'Plotting.py'),
|
||||||
('ImageView', 'ImageView.py'),
|
('ImageView', 'ImageView.py'),
|
||||||
('ParameterTree', '../parametertree'),
|
('ParameterTree', 'parametertree.py'),
|
||||||
('Crosshair / Mouse interaction', 'crosshair.py'),
|
('Crosshair / Mouse interaction', 'crosshair.py'),
|
||||||
('Video speed test', 'VideoSpeedTest.py'),
|
('Video speed test', 'VideoSpeedTest.py'),
|
||||||
('Plot speed test', 'PlotSpeedTest.py'),
|
('Plot speed test', 'PlotSpeedTest.py'),
|
||||||
('Data Slicing', 'DataSlicing.py'),
|
('Data Slicing', 'DataSlicing.py'),
|
||||||
|
('Plot Customization', 'customPlot.py'),
|
||||||
|
('Dock widgets', 'dockarea.py'),
|
||||||
|
('Console', 'ConsoleWidget.py'),
|
||||||
('GraphicsItems', OrderedDict([
|
('GraphicsItems', OrderedDict([
|
||||||
('Scatter Plot', 'ScatterPlot.py'),
|
('Scatter Plot', 'ScatterPlot.py'),
|
||||||
#('PlotItem', 'PlotItem.py'),
|
#('PlotItem', 'PlotItem.py'),
|
||||||
|
@ -46,7 +49,7 @@ examples = OrderedDict([
|
||||||
#('VerticalLabel', '../widgets/VerticalLabel.py'),
|
#('VerticalLabel', '../widgets/VerticalLabel.py'),
|
||||||
('JoystickButton', 'JoystickButton.py'),
|
('JoystickButton', 'JoystickButton.py'),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
('GraphicsScene', 'GraphicsScene.py'),
|
('GraphicsScene', 'GraphicsScene.py'),
|
||||||
('Flowcharts', 'Flowchart.py'),
|
('Flowcharts', 'Flowchart.py'),
|
||||||
#('Canvas', '../canvas'),
|
#('Canvas', '../canvas'),
|
||||||
|
@ -67,9 +70,9 @@ class ExampleLoader(QtGui.QMainWindow):
|
||||||
self.populateTree(self.ui.exampleTree.invisibleRootItem(), examples)
|
self.populateTree(self.ui.exampleTree.invisibleRootItem(), examples)
|
||||||
self.ui.exampleTree.expandAll()
|
self.ui.exampleTree.expandAll()
|
||||||
|
|
||||||
self.resize(900,500)
|
self.resize(1000,500)
|
||||||
self.show()
|
self.show()
|
||||||
self.ui.splitter.setSizes([150,750])
|
self.ui.splitter.setSizes([250,750])
|
||||||
self.ui.loadBtn.clicked.connect(self.loadFile)
|
self.ui.loadBtn.clicked.connect(self.loadFile)
|
||||||
self.ui.exampleTree.currentItemChanged.connect(self.showFile)
|
self.ui.exampleTree.currentItemChanged.connect(self.showFile)
|
||||||
self.ui.exampleTree.itemDoubleClicked.connect(self.loadFile)
|
self.ui.exampleTree.itemDoubleClicked.connect(self.loadFile)
|
||||||
|
|
52
examples/customPlot.py
Normal file
52
examples/customPlot.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##
|
||||||
|
## This example demonstrates the creation of a plot with a customized
|
||||||
|
## AxisItem and ViewBox.
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
import numpy as np
|
||||||
|
import time
|
||||||
|
|
||||||
|
class DateAxis(pg.AxisItem):
|
||||||
|
def tickStrings(self, values, scale, spacing):
|
||||||
|
return [time.strftime('%b %Y', time.localtime(x)) for x in values]
|
||||||
|
|
||||||
|
class CustomViewBox(pg.ViewBox):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
pg.ViewBox.__init__(self, *args, **kwds)
|
||||||
|
self.setMouseMode(self.RectMode)
|
||||||
|
|
||||||
|
## reimplement right-click to zoom out
|
||||||
|
def mouseClickEvent(self, ev):
|
||||||
|
if ev.button() == QtCore.Qt.RightButton:
|
||||||
|
self.autoRange()
|
||||||
|
|
||||||
|
def mouseDragEvent(self, ev):
|
||||||
|
if ev.button() == QtCore.Qt.RightButton:
|
||||||
|
ev.ignore()
|
||||||
|
else:
|
||||||
|
pg.ViewBox.mouseDragEvent(self, ev)
|
||||||
|
|
||||||
|
|
||||||
|
app = pg.mkQApp()
|
||||||
|
|
||||||
|
axis = DateAxis(orientation='bottom')
|
||||||
|
vb = CustomViewBox()
|
||||||
|
|
||||||
|
pw = pg.PlotWidget(viewBox=vb, axisItems={'bottom': axis}, enableMenu=False, title="PlotItem with custom axis and ViewBox<br>Menu disabled, mouse behavior changed: left-drag to zoom, right-click to reset zoom")
|
||||||
|
dates = np.arange(8) * (3600*24*356)
|
||||||
|
pw.plot(x=dates, y=[1,6,2,4,3,5,6,8], symbol='o')
|
||||||
|
pw.show()
|
||||||
|
|
||||||
|
r = pg.PolyLineROI([(0,0), (10, 10)])
|
||||||
|
pw.addItem(r)
|
||||||
|
|
||||||
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
116
examples/dockarea.py
Normal file
116
examples/dockarea.py
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This example demonstrates the use of pyqtgraph's dock widget system.
|
||||||
|
|
||||||
|
The dockarea system allows the design of user interfaces which can be rearranged by
|
||||||
|
the user at runtime. Docks can be moved, resized, stacked, and torn out of the main
|
||||||
|
window. This is similar in principle to the docking system built into Qt, but
|
||||||
|
offers a more deterministic dock placement API (in Qt it is very difficult to
|
||||||
|
programatically generate complex dock arrangements). Additionally, Qt's docks are
|
||||||
|
designed to be used as small panels around the outer edge of a window. Pyqtgraph's
|
||||||
|
docks were created with the notion that the entire window (or any portion of it)
|
||||||
|
would consist of dockable components.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
import pyqtgraph.console
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from pyqtgraph.dockarea import *
|
||||||
|
|
||||||
|
app = QtGui.QApplication([])
|
||||||
|
win = QtGui.QMainWindow()
|
||||||
|
area = DockArea()
|
||||||
|
win.setCentralWidget(area)
|
||||||
|
win.resize(1000,500)
|
||||||
|
|
||||||
|
## Create docks, place them into the window one at a time.
|
||||||
|
## Note that size arguments are only a suggestion; docks will still have to
|
||||||
|
## fill the entire dock area and obey the limits of their internal widgets.
|
||||||
|
d1 = Dock("Dock1", size=(1, 1)) ## give this dock the minimum possible size
|
||||||
|
d2 = Dock("Dock2 - Console", size=(500,300))
|
||||||
|
d3 = Dock("Dock3", size=(500,400))
|
||||||
|
d4 = Dock("Dock4 (tabbed) - Plot", size=(500,200))
|
||||||
|
d5 = Dock("Dock5 - Image", size=(500,200))
|
||||||
|
d6 = Dock("Dock6 (tabbed) - Plot", size=(500,200))
|
||||||
|
area.addDock(d1, 'left') ## place d1 at left edge of dock area (it will fill the whole space since there are no other docks yet)
|
||||||
|
area.addDock(d2, 'right') ## place d2 at right edge of dock area
|
||||||
|
area.addDock(d3, 'bottom', d1)## place d3 at bottom edge of d1
|
||||||
|
area.addDock(d4, 'right') ## place d4 at right edge of dock area
|
||||||
|
area.addDock(d5, 'left', d1) ## place d5 at left edge of d1
|
||||||
|
area.addDock(d6, 'top', d4) ## place d5 at top edge of d4
|
||||||
|
|
||||||
|
## Test ability to move docks programatically after they have been placed
|
||||||
|
area.moveDock(d4, 'top', d2) ## move d4 to top edge of d2
|
||||||
|
area.moveDock(d6, 'above', d4) ## move d6 to stack on top of d4
|
||||||
|
area.moveDock(d5, 'top', d2) ## move d5 to top edge of d2
|
||||||
|
|
||||||
|
|
||||||
|
## Add widgets into each dock
|
||||||
|
|
||||||
|
## first dock gets save/restore buttons
|
||||||
|
w1 = pg.LayoutWidget()
|
||||||
|
label = QtGui.QLabel(""" -- DockArea Example --
|
||||||
|
This window has 6 Dock widgets in it. Each dock can be dragged
|
||||||
|
by its title bar to occupy a different space within the window
|
||||||
|
but note that one dock has its title bar hidden). Additionally,
|
||||||
|
the borders between docks may be dragged to resize. Docks that are dragged on top
|
||||||
|
of one another are stacked in a tabbed layout. Double-click a dock title
|
||||||
|
bar to place it in its own window.
|
||||||
|
""")
|
||||||
|
saveBtn = QtGui.QPushButton('Save dock state')
|
||||||
|
restoreBtn = QtGui.QPushButton('Restore dock state')
|
||||||
|
restoreBtn.setEnabled(False)
|
||||||
|
w1.addWidget(label, row=0, col=0)
|
||||||
|
w1.addWidget(saveBtn, row=1, col=0)
|
||||||
|
w1.addWidget(restoreBtn, row=2, col=0)
|
||||||
|
d1.addWidget(w1)
|
||||||
|
state = None
|
||||||
|
def save():
|
||||||
|
global state
|
||||||
|
state = area.saveState()
|
||||||
|
restoreBtn.setEnabled(True)
|
||||||
|
def load():
|
||||||
|
global state
|
||||||
|
area.restoreState(state)
|
||||||
|
saveBtn.clicked.connect(save)
|
||||||
|
restoreBtn.clicked.connect(load)
|
||||||
|
|
||||||
|
|
||||||
|
w2 = pg.console.ConsoleWidget()
|
||||||
|
d2.addWidget(w2)
|
||||||
|
|
||||||
|
## Hide title bar on dock 3
|
||||||
|
d3.hideTitleBar()
|
||||||
|
w3 = pg.PlotWidget(title="Plot inside dock with no title bar")
|
||||||
|
w3.plot(np.random.normal(size=100))
|
||||||
|
d3.addWidget(w3)
|
||||||
|
|
||||||
|
w4 = pg.PlotWidget(title="Dock 4 plot")
|
||||||
|
w4.plot(np.random.normal(size=100))
|
||||||
|
d4.addWidget(w4)
|
||||||
|
|
||||||
|
w5 = pg.ImageView()
|
||||||
|
w5.setImage(np.random.normal(size=(100,100)))
|
||||||
|
d5.addWidget(w5)
|
||||||
|
|
||||||
|
w6 = pg.PlotWidget(title="Dock 6 plot")
|
||||||
|
w6.plot(np.random.normal(size=100))
|
||||||
|
d6.addWidget(w6)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
win.show()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
|
@ -1,3 +1,3 @@
|
||||||
## make this version of pyqtgraph importable before any others
|
## make this version of pyqtgraph importable before any others
|
||||||
import sys, os
|
import sys, os
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
|
||||||
|
|
|
@ -1,38 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import initExample ## Add path to library (just for examples; you do not need this)
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph.multiprocess as mp
|
import pyqtgraph.multiprocess as mp
|
||||||
from pyqtgraph.multiprocess.parallelizer import Parallelize #, Parallelizer
|
import pyqtgraph as pg
|
||||||
import time
|
import time
|
||||||
|
|
||||||
print "\n=================\nParallelize"
|
|
||||||
tasks = [1,2,4,8]
|
|
||||||
results = [None] * len(tasks)
|
|
||||||
size = 2000000
|
|
||||||
|
|
||||||
start = time.time()
|
|
||||||
with Parallelize(enumerate(tasks), results=results, workers=1) as tasker:
|
|
||||||
for i, x in tasker:
|
|
||||||
print i, x
|
|
||||||
tot = 0
|
|
||||||
for j in xrange(size):
|
|
||||||
tot += j * x
|
|
||||||
results[i] = tot
|
|
||||||
print results
|
|
||||||
print "serial:", time.time() - start
|
|
||||||
|
|
||||||
start = time.time()
|
|
||||||
with Parallelize(enumerate(tasks), results=results) as tasker:
|
|
||||||
for i, x in tasker:
|
|
||||||
print i, x
|
|
||||||
tot = 0
|
|
||||||
for j in xrange(size):
|
|
||||||
tot += j * x
|
|
||||||
results[i] = tot
|
|
||||||
print results
|
|
||||||
print "parallel:", time.time() - start
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
63
examples/parallelize.py
Normal file
63
examples/parallelize.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
import numpy as np
|
||||||
|
import pyqtgraph.multiprocess as mp
|
||||||
|
import pyqtgraph as pg
|
||||||
|
import time
|
||||||
|
|
||||||
|
print "\n=================\nParallelize"
|
||||||
|
|
||||||
|
## Do a simple task:
|
||||||
|
## for x in range(N):
|
||||||
|
## sum([x*i for i in range(M)])
|
||||||
|
##
|
||||||
|
## We'll do this three times
|
||||||
|
## - once without Parallelize
|
||||||
|
## - once with Parallelize, but forced to use a single worker
|
||||||
|
## - once with Parallelize automatically determining how many workers to use
|
||||||
|
##
|
||||||
|
|
||||||
|
tasks = range(10)
|
||||||
|
results = [None] * len(tasks)
|
||||||
|
results2 = results[:]
|
||||||
|
results3 = results[:]
|
||||||
|
size = 2000000
|
||||||
|
|
||||||
|
pg.mkQApp()
|
||||||
|
|
||||||
|
### Purely serial processing
|
||||||
|
start = time.time()
|
||||||
|
with pg.ProgressDialog('processing serially..', maximum=len(tasks)) as dlg:
|
||||||
|
for i, x in enumerate(tasks):
|
||||||
|
tot = 0
|
||||||
|
for j in xrange(size):
|
||||||
|
tot += j * x
|
||||||
|
results[i] = tot
|
||||||
|
dlg += 1
|
||||||
|
if dlg.wasCanceled():
|
||||||
|
raise Exception('processing canceled')
|
||||||
|
print "Serial time: %0.2f" % (time.time() - start)
|
||||||
|
|
||||||
|
### Use parallelize, but force a single worker
|
||||||
|
### (this simulates the behavior seen on windows, which lacks os.fork)
|
||||||
|
start = time.time()
|
||||||
|
with mp.Parallelize(enumerate(tasks), results=results2, workers=1, progressDialog='processing serially (using Parallelizer)..') as tasker:
|
||||||
|
for i, x in tasker:
|
||||||
|
tot = 0
|
||||||
|
for j in xrange(size):
|
||||||
|
tot += j * x
|
||||||
|
tasker.results[i] = tot
|
||||||
|
print "\nParallel time, 1 worker: %0.2f" % (time.time() - start)
|
||||||
|
print "Results match serial: ", results2 == results
|
||||||
|
|
||||||
|
### Use parallelize with multiple workers
|
||||||
|
start = time.time()
|
||||||
|
with mp.Parallelize(enumerate(tasks), results=results3, progressDialog='processing in parallel..') as tasker:
|
||||||
|
for i, x in tasker:
|
||||||
|
tot = 0
|
||||||
|
for j in xrange(size):
|
||||||
|
tot += j * x
|
||||||
|
tasker.results[i] = tot
|
||||||
|
print "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start)
|
||||||
|
print "Results match serial: ", results3 == results
|
||||||
|
|
160
examples/parametertree.py
Normal file
160
examples/parametertree.py
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This example demonstrates the use of pyqtgraph's parametertree system. This provides
|
||||||
|
a simple way to generate user interfaces that control sets of parameters. The example
|
||||||
|
demonstrates a variety of different parameter types (int, float, list, etc.)
|
||||||
|
as well as some customized parameter types
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
|
||||||
|
|
||||||
|
import collections
|
||||||
|
app = QtGui.QApplication([])
|
||||||
|
import pyqtgraph.parametertree.parameterTypes as pTypes
|
||||||
|
from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
|
||||||
|
|
||||||
|
|
||||||
|
## test subclassing parameters
|
||||||
|
## This parameter automatically generates two child parameters which are always reciprocals of each other
|
||||||
|
class ComplexParameter(pTypes.GroupParameter):
|
||||||
|
def __init__(self, **opts):
|
||||||
|
opts['type'] = 'bool'
|
||||||
|
opts['value'] = True
|
||||||
|
pTypes.GroupParameter.__init__(self, **opts)
|
||||||
|
|
||||||
|
self.addChild({'name': 'A = 1/B', 'type': 'float', 'value': 7, 'suffix': 'Hz', 'siPrefix': True})
|
||||||
|
self.addChild({'name': 'B = 1/A', 'type': 'float', 'value': 1/7., 'suffix': 's', 'siPrefix': True})
|
||||||
|
self.a = self.param('A = 1/B')
|
||||||
|
self.b = self.param('B = 1/A')
|
||||||
|
self.a.sigValueChanged.connect(self.aChanged)
|
||||||
|
self.b.sigValueChanged.connect(self.bChanged)
|
||||||
|
|
||||||
|
def aChanged(self):
|
||||||
|
self.b.setValue(1.0 / self.a.value(), blockSignal=self.bChanged)
|
||||||
|
|
||||||
|
def bChanged(self):
|
||||||
|
self.a.setValue(1.0 / self.b.value(), blockSignal=self.aChanged)
|
||||||
|
|
||||||
|
|
||||||
|
## test add/remove
|
||||||
|
## this group includes a menu allowing the user to add new parameters into its child list
|
||||||
|
class ScalableGroup(pTypes.GroupParameter):
|
||||||
|
def __init__(self, **opts):
|
||||||
|
opts['type'] = 'group'
|
||||||
|
opts['addText'] = "Add"
|
||||||
|
opts['addList'] = ['str', 'float', 'int']
|
||||||
|
pTypes.GroupParameter.__init__(self, **opts)
|
||||||
|
|
||||||
|
def addNew(self, typ):
|
||||||
|
val = {
|
||||||
|
'str': '',
|
||||||
|
'float': 0.0,
|
||||||
|
'int': 0
|
||||||
|
}[typ]
|
||||||
|
self.addChild(dict(name="ScalableParam %d" % (len(self.childs)+1), type=typ, value=val, removable=True, renamable=True))
|
||||||
|
|
||||||
|
|
||||||
|
## test column spanning (widget sub-item that spans all columns)
|
||||||
|
class TextParameterItem(pTypes.WidgetParameterItem):
|
||||||
|
def __init__(self, param, depth):
|
||||||
|
pTypes.WidgetParameterItem.__init__(self, param, depth)
|
||||||
|
self.subItem = QtGui.QTreeWidgetItem()
|
||||||
|
self.addChild(self.subItem)
|
||||||
|
|
||||||
|
def treeWidgetChanged(self):
|
||||||
|
self.treeWidget().setFirstItemColumnSpanned(self.subItem, True)
|
||||||
|
self.treeWidget().setItemWidget(self.subItem, 0, self.textBox)
|
||||||
|
self.setExpanded(True)
|
||||||
|
|
||||||
|
def makeWidget(self):
|
||||||
|
self.textBox = QtGui.QTextEdit()
|
||||||
|
self.textBox.setMaximumHeight(100)
|
||||||
|
self.textBox.value = lambda: str(self.textBox.toPlainText())
|
||||||
|
self.textBox.setValue = self.textBox.setPlainText
|
||||||
|
self.textBox.sigChanged = self.textBox.textChanged
|
||||||
|
return self.textBox
|
||||||
|
|
||||||
|
class TextParameter(Parameter):
|
||||||
|
type = 'text'
|
||||||
|
itemClass = TextParameterItem
|
||||||
|
|
||||||
|
registerParameterType('text', TextParameter)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
params = [
|
||||||
|
{'name': 'Basic parameter data types', 'type': 'group', 'children': [
|
||||||
|
{'name': 'Integer', 'type': 'int', 'value': 10},
|
||||||
|
{'name': 'Float', 'type': 'float', 'value': 10.5, 'step': 0.1},
|
||||||
|
{'name': 'String', 'type': 'str', 'value': "hi"},
|
||||||
|
{'name': 'List', 'type': 'list', 'values': [1,2,3], 'value': 2},
|
||||||
|
{'name': 'Named List', 'type': 'list', 'values': {"one": 1, "two": 2, "three": 3}, 'value': 2},
|
||||||
|
{'name': 'Boolean', 'type': 'bool', 'value': True, 'tip': "This is a checkbox"},
|
||||||
|
{'name': 'Color', 'type': 'color', 'value': "FF0", 'tip': "This is a color button"},
|
||||||
|
{'name': 'Subgroup', 'type': 'group', 'children': [
|
||||||
|
{'name': 'Sub-param 1', 'type': 'int', 'value': 10},
|
||||||
|
{'name': 'Sub-param 2', 'type': 'float', 'value': 1.2e6},
|
||||||
|
]},
|
||||||
|
]},
|
||||||
|
{'name': 'Numerical Parameter Options', 'type': 'group', 'children': [
|
||||||
|
{'name': 'Units + SI prefix', 'type': 'float', 'value': 1.2e-6, 'step': 1e-6, 'siPrefix': True, 'suffix': 'V'},
|
||||||
|
{'name': 'Limits (min=7;max=15)', 'type': 'int', 'value': 11, 'limits': (7, 15), 'default': -6},
|
||||||
|
{'name': 'DEC stepping', 'type': 'float', 'value': 1.2e6, 'dec': True, 'step': 1, 'siPrefix': True, 'suffix': 'Hz'},
|
||||||
|
|
||||||
|
]},
|
||||||
|
{'name': 'Extra Parameter Options', 'type': 'group', 'children': [
|
||||||
|
{'name': 'Read-only', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'readonly': True},
|
||||||
|
{'name': 'Renamable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'renamable': True},
|
||||||
|
{'name': 'Removable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'removable': True},
|
||||||
|
]},
|
||||||
|
ComplexParameter(name='Custom parameter group (reciprocal values)'),
|
||||||
|
ScalableGroup(name="Expandable Parameter Group", children=[
|
||||||
|
{'name': 'ScalableParam 1', 'type': 'str', 'value': "default param 1"},
|
||||||
|
{'name': 'ScalableParam 2', 'type': 'str', 'value': "default param 2"},
|
||||||
|
]),
|
||||||
|
{'name': 'Custom parameter class (text box)', 'type': 'text', 'value': 'Some text...'},
|
||||||
|
]
|
||||||
|
|
||||||
|
## Create tree of Parameter objects
|
||||||
|
p = Parameter(name='params', type='group', children=params)
|
||||||
|
|
||||||
|
## If anything changes in the tree, print a message
|
||||||
|
def change(param, changes):
|
||||||
|
print("tree changes:")
|
||||||
|
for param, change, data in changes:
|
||||||
|
path = p.childPath(param)
|
||||||
|
if path is not None:
|
||||||
|
childName = '.'.join(path)
|
||||||
|
else:
|
||||||
|
childName = param.name()
|
||||||
|
print(' parameter: %s'% childName)
|
||||||
|
print(' change: %s'% change)
|
||||||
|
print(' data: %s'% str(data))
|
||||||
|
print(' ----------')
|
||||||
|
|
||||||
|
p.sigTreeStateChanged.connect(change)
|
||||||
|
|
||||||
|
## Create two ParameterTree widgets, both accessing the same data
|
||||||
|
t = ParameterTree()
|
||||||
|
t.setParameters(p, showTop=False)
|
||||||
|
t.show()
|
||||||
|
t.resize(400,600)
|
||||||
|
t2 = ParameterTree()
|
||||||
|
t2.setParameters(p, showTop=False)
|
||||||
|
t2.show()
|
||||||
|
t2.resize(400,600)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
|
@ -51,16 +51,16 @@ class ExceptionHandler:
|
||||||
def __call__(self, *args):
|
def __call__(self, *args):
|
||||||
## call original exception handler first (prints exception)
|
## call original exception handler first (prints exception)
|
||||||
global original_excepthook, callbacks, clear_tracebacks
|
global original_excepthook, callbacks, clear_tracebacks
|
||||||
print "=====", time.strftime("%Y.%m.%d %H:%m:%S", time.localtime(time.time())), "====="
|
print("===== %s =====" % str(time.strftime("%Y.%m.%d %H:%m:%S", time.localtime(time.time()))))
|
||||||
ret = original_excepthook(*args)
|
ret = original_excepthook(*args)
|
||||||
|
|
||||||
for cb in callbacks:
|
for cb in callbacks:
|
||||||
try:
|
try:
|
||||||
cb(*args)
|
cb(*args)
|
||||||
except:
|
except:
|
||||||
print " --------------------------------------------------------------"
|
print(" --------------------------------------------------------------")
|
||||||
print " Error occurred during exception callback", cb
|
print(" Error occurred during exception callback %s" % str(cb))
|
||||||
print " --------------------------------------------------------------"
|
print(" --------------------------------------------------------------")
|
||||||
traceback.print_exception(*sys.exc_info())
|
traceback.print_exception(*sys.exc_info())
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,8 @@ class Flowchart(Node):
|
||||||
if terminals is None:
|
if terminals is None:
|
||||||
terminals = {}
|
terminals = {}
|
||||||
self.filePath = filePath
|
self.filePath = filePath
|
||||||
Node.__init__(self, name) ## create node without terminals; we'll add these later
|
Node.__init__(self, name, allowAddInput=True, allowAddOutput=True) ## create node without terminals; we'll add these later
|
||||||
|
|
||||||
|
|
||||||
self.inputWasSet = False ## flag allows detection of changes in the absence of input change.
|
self.inputWasSet = False ## flag allows detection of changes in the absence of input change.
|
||||||
self._nodes = {}
|
self._nodes = {}
|
||||||
|
@ -457,7 +458,7 @@ class Flowchart(Node):
|
||||||
state = Node.saveState(self)
|
state = Node.saveState(self)
|
||||||
state['nodes'] = []
|
state['nodes'] = []
|
||||||
state['connects'] = []
|
state['connects'] = []
|
||||||
state['terminals'] = self.saveTerminals()
|
#state['terminals'] = self.saveTerminals()
|
||||||
|
|
||||||
for name, node in self._nodes.items():
|
for name, node in self._nodes.items():
|
||||||
cls = type(node)
|
cls = type(node)
|
||||||
|
@ -470,7 +471,7 @@ class Flowchart(Node):
|
||||||
conn = self.listConnections()
|
conn = self.listConnections()
|
||||||
for a, b in conn:
|
for a, b in conn:
|
||||||
state['connects'].append((a.node().name(), a.name(), b.node().name(), b.name()))
|
state['connects'].append((a.node().name(), a.name(), b.node().name(), b.name()))
|
||||||
|
|
||||||
state['inputNode'] = self.inputNode.saveState()
|
state['inputNode'] = self.inputNode.saveState()
|
||||||
state['outputNode'] = self.outputNode.saveState()
|
state['outputNode'] = self.outputNode.saveState()
|
||||||
|
|
||||||
|
@ -486,7 +487,8 @@ class Flowchart(Node):
|
||||||
nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0]))
|
nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0]))
|
||||||
for n in nodes:
|
for n in nodes:
|
||||||
if n['name'] in self._nodes:
|
if n['name'] in self._nodes:
|
||||||
self._nodes[n['name']].moveBy(*n['pos'])
|
#self._nodes[n['name']].graphicsItem().moveBy(*n['pos'])
|
||||||
|
self._nodes[n['name']].restoreState(n['state'])
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
node = self.createNode(n['class'], name=n['name'])
|
node = self.createNode(n['class'], name=n['name'])
|
||||||
|
@ -498,7 +500,7 @@ class Flowchart(Node):
|
||||||
self.inputNode.restoreState(state.get('inputNode', {}))
|
self.inputNode.restoreState(state.get('inputNode', {}))
|
||||||
self.outputNode.restoreState(state.get('outputNode', {}))
|
self.outputNode.restoreState(state.get('outputNode', {}))
|
||||||
|
|
||||||
self.restoreTerminals(state['terminals'])
|
#self.restoreTerminals(state['terminals'])
|
||||||
for n1, t1, n2, t2 in state['connects']:
|
for n1, t1, n2, t2 in state['connects']:
|
||||||
try:
|
try:
|
||||||
self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2])
|
self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2])
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
#from PySide import QtCore, QtGui
|
|
||||||
from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject
|
from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject
|
||||||
import pyqtgraph.functions as fn
|
import pyqtgraph.functions as fn
|
||||||
from .Terminal import *
|
from .Terminal import *
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pyqtgraph.debug import *
|
from pyqtgraph.debug import *
|
||||||
import numpy as np
|
import numpy as np
|
||||||
#from pyqtgraph.ObjectWorkaround import QObjectWorkaround
|
|
||||||
from .eq import *
|
from .eq import *
|
||||||
|
|
||||||
#TETRACYCLINE = True
|
|
||||||
|
|
||||||
def strDict(d):
|
def strDict(d):
|
||||||
return dict([(str(k), v) for k, v in d.items()])
|
return dict([(str(k), v) for k, v in d.items()])
|
||||||
|
@ -32,8 +29,8 @@ class Node(QtCore.QObject):
|
||||||
self.bypassButton = None ## this will be set by the flowchart ctrl widget..
|
self.bypassButton = None ## this will be set by the flowchart ctrl widget..
|
||||||
self._graphicsItem = None
|
self._graphicsItem = None
|
||||||
self.terminals = OrderedDict()
|
self.terminals = OrderedDict()
|
||||||
self._inputs = {}
|
self._inputs = OrderedDict()
|
||||||
self._outputs = {}
|
self._outputs = OrderedDict()
|
||||||
self._allowAddInput = allowAddInput ## flags to allow the user to add/remove terminals
|
self._allowAddInput = allowAddInput ## flags to allow the user to add/remove terminals
|
||||||
self._allowAddOutput = allowAddOutput
|
self._allowAddOutput = allowAddOutput
|
||||||
self._allowRemove = allowRemove
|
self._allowRemove = allowRemove
|
||||||
|
@ -85,24 +82,16 @@ class Node(QtCore.QObject):
|
||||||
def terminalRenamed(self, term, oldName):
|
def terminalRenamed(self, term, oldName):
|
||||||
"""Called after a terminal has been renamed"""
|
"""Called after a terminal has been renamed"""
|
||||||
newName = term.name()
|
newName = term.name()
|
||||||
#print "node", self, "handling rename..", newName, oldName
|
|
||||||
for d in [self.terminals, self._inputs, self._outputs]:
|
for d in [self.terminals, self._inputs, self._outputs]:
|
||||||
if oldName not in d:
|
if oldName not in d:
|
||||||
continue
|
continue
|
||||||
#print " got one"
|
|
||||||
d[newName] = d[oldName]
|
d[newName] = d[oldName]
|
||||||
del d[oldName]
|
del d[oldName]
|
||||||
|
|
||||||
self.graphicsItem().updateTerminals()
|
self.graphicsItem().updateTerminals()
|
||||||
#self.emit(QtCore.SIGNAL('terminalRenamed'), term, oldName)
|
|
||||||
self.sigTerminalRenamed.emit(term, oldName)
|
self.sigTerminalRenamed.emit(term, oldName)
|
||||||
|
|
||||||
def addTerminal(self, name, **opts):
|
def addTerminal(self, name, **opts):
|
||||||
#print "Node.addTerminal called. name:", name, "opts:", opts
|
|
||||||
#global TETRACYCLINE
|
|
||||||
#print "TETRACYCLINE: ", TETRACYCLINE
|
|
||||||
#if TETRACYCLINE:
|
|
||||||
#print "Creating Terminal..."
|
|
||||||
name = self.nextTerminalName(name)
|
name = self.nextTerminalName(name)
|
||||||
term = Terminal(self, name, **opts)
|
term = Terminal(self, name, **opts)
|
||||||
self.terminals[name] = term
|
self.terminals[name] = term
|
||||||
|
@ -278,12 +267,20 @@ class Node(QtCore.QObject):
|
||||||
|
|
||||||
def saveState(self):
|
def saveState(self):
|
||||||
pos = self.graphicsItem().pos()
|
pos = self.graphicsItem().pos()
|
||||||
return {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()}
|
state = {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()}
|
||||||
|
termsEditable = self._allowAddInput | self._allowAddOutput
|
||||||
|
for term in self._inputs.values() + self._outputs.values():
|
||||||
|
termsEditable |= term._renamable | term._removable | term._multiable
|
||||||
|
if termsEditable:
|
||||||
|
state['terminals'] = self.saveTerminals()
|
||||||
|
return state
|
||||||
|
|
||||||
def restoreState(self, state):
|
def restoreState(self, state):
|
||||||
pos = state.get('pos', (0,0))
|
pos = state.get('pos', (0,0))
|
||||||
self.graphicsItem().setPos(*pos)
|
self.graphicsItem().setPos(*pos)
|
||||||
self.bypass(state.get('bypass', False))
|
self.bypass(state.get('bypass', False))
|
||||||
|
if 'terminals' in state:
|
||||||
|
self.restoreTerminals(state['terminals'])
|
||||||
|
|
||||||
def saveTerminals(self):
|
def saveTerminals(self):
|
||||||
terms = OrderedDict()
|
terms = OrderedDict()
|
||||||
|
@ -309,8 +306,8 @@ class Node(QtCore.QObject):
|
||||||
for t in self.terminals.values():
|
for t in self.terminals.values():
|
||||||
t.close()
|
t.close()
|
||||||
self.terminals = OrderedDict()
|
self.terminals = OrderedDict()
|
||||||
self._inputs = {}
|
self._inputs = OrderedDict()
|
||||||
self._outputs = {}
|
self._outputs = OrderedDict()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Cleans up after the node--removes terminals, graphicsItem, widget"""
|
"""Cleans up after the node--removes terminals, graphicsItem, widget"""
|
||||||
|
@ -493,10 +490,6 @@ class NodeGraphicsItem(GraphicsObject):
|
||||||
self.hovered = False
|
self.hovered = False
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
#def mouseReleaseEvent(self, ev):
|
|
||||||
#ret = QtGui.QGraphicsItem.mouseReleaseEvent(self, ev)
|
|
||||||
#return ret
|
|
||||||
|
|
||||||
def keyPressEvent(self, ev):
|
def keyPressEvent(self, ev):
|
||||||
if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace:
|
if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace:
|
||||||
ev.accept()
|
ev.accept()
|
||||||
|
@ -513,13 +506,8 @@ class NodeGraphicsItem(GraphicsObject):
|
||||||
return GraphicsObject.itemChange(self, change, val)
|
return GraphicsObject.itemChange(self, change, val)
|
||||||
|
|
||||||
|
|
||||||
#def contextMenuEvent(self, ev):
|
|
||||||
#ev.accept()
|
|
||||||
#self.menu.popup(ev.screenPos())
|
|
||||||
|
|
||||||
def getMenu(self):
|
def getMenu(self):
|
||||||
return self.menu
|
return self.menu
|
||||||
|
|
||||||
|
|
||||||
def getContextMenus(self, event):
|
def getContextMenus(self, event):
|
||||||
return [self.menu]
|
return [self.menu]
|
||||||
|
@ -548,25 +536,3 @@ class NodeGraphicsItem(GraphicsObject):
|
||||||
def addOutputFromMenu(self): ## called when add output is clicked in context menu
|
def addOutputFromMenu(self): ## called when add output is clicked in context menu
|
||||||
self.node.addOutput(renamable=True, removable=True, multiable=False)
|
self.node.addOutput(renamable=True, removable=True, multiable=False)
|
||||||
|
|
||||||
#def menuTriggered(self, action):
|
|
||||||
##print "node.menuTriggered called. action:", action
|
|
||||||
#act = str(action.text())
|
|
||||||
#if act == "Add input":
|
|
||||||
#self.node.addInput()
|
|
||||||
#self.updateActionMenu()
|
|
||||||
#elif act == "Add output":
|
|
||||||
#self.node.addOutput()
|
|
||||||
#self.updateActionMenu()
|
|
||||||
#elif act == "Remove node":
|
|
||||||
#self.node.close()
|
|
||||||
#else: ## only other option is to remove a terminal
|
|
||||||
#self.node.removeTerminal(act)
|
|
||||||
#self.terminalMenu.removeAction(action)
|
|
||||||
|
|
||||||
#def updateActionMenu(self):
|
|
||||||
#for t in self.node.terminals:
|
|
||||||
#if t not in [str(a.text()) for a in self.terminalMenu.actions()]:
|
|
||||||
#self.terminalMenu.addAction(t)
|
|
||||||
#for a in self.terminalMenu.actions():
|
|
||||||
#if str(a.text()) not in self.node.terminals:
|
|
||||||
#self.terminalMenu.removeAction(a)
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Terminal:
|
||||||
self._value = {} ## dictionary of terminal:value pairs.
|
self._value = {} ## dictionary of terminal:value pairs.
|
||||||
else:
|
else:
|
||||||
self._value = None
|
self._value = None
|
||||||
|
|
||||||
self.valueOk = None
|
self.valueOk = None
|
||||||
self.recolor()
|
self.recolor()
|
||||||
|
|
||||||
|
@ -70,6 +70,8 @@ class Terminal:
|
||||||
return
|
return
|
||||||
self._value = val
|
self._value = val
|
||||||
else:
|
else:
|
||||||
|
if not isinstance(self._value, dict):
|
||||||
|
self._value = {}
|
||||||
if val is not None:
|
if val is not None:
|
||||||
self._value.update(val)
|
self._value.update(val)
|
||||||
|
|
||||||
|
@ -132,9 +134,14 @@ class Terminal:
|
||||||
def isMultiValue(self):
|
def isMultiValue(self):
|
||||||
return self._multi
|
return self._multi
|
||||||
|
|
||||||
def setMultiValue(self, b):
|
def setMultiValue(self, multi):
|
||||||
"""Set whether this is a multi-value terminal."""
|
"""Set whether this is a multi-value terminal."""
|
||||||
self._multi = b
|
self._multi = multi
|
||||||
|
if not multi and len(self.inputTerminals()) > 1:
|
||||||
|
self.disconnectAll()
|
||||||
|
|
||||||
|
for term in self.inputTerminals():
|
||||||
|
self.inputChanged(term)
|
||||||
|
|
||||||
def isOutput(self):
|
def isOutput(self):
|
||||||
return self._io == 'out'
|
return self._io == 'out'
|
||||||
|
@ -407,6 +414,8 @@ class TerminalGraphicsItem(GraphicsObject):
|
||||||
multiAct = QtGui.QAction("Multi-value", self.menu)
|
multiAct = QtGui.QAction("Multi-value", self.menu)
|
||||||
multiAct.setCheckable(True)
|
multiAct.setCheckable(True)
|
||||||
multiAct.setChecked(self.term.isMultiValue())
|
multiAct.setChecked(self.term.isMultiValue())
|
||||||
|
multiAct.setEnabled(self.term.isMultiable())
|
||||||
|
|
||||||
multiAct.triggered.connect(self.toggleMulti)
|
multiAct.triggered.connect(self.toggleMulti)
|
||||||
self.menu.addAction(multiAct)
|
self.menu.addAction(multiAct)
|
||||||
self.menu.multiAct = multiAct
|
self.menu.multiAct = multiAct
|
||||||
|
|
|
@ -240,7 +240,7 @@ class EvalNode(Node):
|
||||||
def saveState(self):
|
def saveState(self):
|
||||||
state = Node.saveState(self)
|
state = Node.saveState(self)
|
||||||
state['text'] = str(self.text.toPlainText())
|
state['text'] = str(self.text.toPlainText())
|
||||||
state['terminals'] = self.saveTerminals()
|
#state['terminals'] = self.saveTerminals()
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def restoreState(self, state):
|
def restoreState(self, state):
|
||||||
|
@ -282,7 +282,7 @@ class ColumnJoinNode(Node):
|
||||||
|
|
||||||
def addInput(self):
|
def addInput(self):
|
||||||
#print "ColumnJoinNode.addInput called."
|
#print "ColumnJoinNode.addInput called."
|
||||||
term = Node.addInput(self, 'input', renamable=True)
|
term = Node.addInput(self, 'input', renamable=True, removable=True)
|
||||||
#print "Node.addInput returned. term:", term
|
#print "Node.addInput returned. term:", term
|
||||||
item = QtGui.QTreeWidgetItem([term.name()])
|
item = QtGui.QTreeWidgetItem([term.name()])
|
||||||
item.term = term
|
item.term = term
|
||||||
|
@ -322,16 +322,14 @@ class ColumnJoinNode(Node):
|
||||||
|
|
||||||
def restoreState(self, state):
|
def restoreState(self, state):
|
||||||
Node.restoreState(self, state)
|
Node.restoreState(self, state)
|
||||||
inputs = [inp.name() for inp in self.inputs()]
|
inputs = self.inputs()
|
||||||
|
order = [name for name in state['order'] if name in inputs]
|
||||||
for name in inputs:
|
for name in inputs:
|
||||||
if name not in state['order']:
|
if name not in order:
|
||||||
self.removeTerminal(name)
|
order.append(name)
|
||||||
for name in state['order']:
|
|
||||||
if name not in inputs:
|
|
||||||
Node.addInput(self, name, renamable=True)
|
|
||||||
|
|
||||||
self.tree.clear()
|
self.tree.clear()
|
||||||
for name in state['order']:
|
for name in order:
|
||||||
term = self[name]
|
term = self[name]
|
||||||
item = QtGui.QTreeWidgetItem([name])
|
item = QtGui.QTreeWidgetItem([name])
|
||||||
item.term = term
|
item.term = term
|
||||||
|
|
|
@ -153,7 +153,7 @@ def denoise(data, radius=2, threshold=4):
|
||||||
|
|
||||||
r2 = radius * 2
|
r2 = radius * 2
|
||||||
d1 = data.view(np.ndarray)
|
d1 = data.view(np.ndarray)
|
||||||
d2 = data[radius:] - data[:-radius] #a derivative
|
d2 = d1[radius:] - d1[:-radius] #a derivative
|
||||||
#d3 = data[r2:] - data[:-r2]
|
#d3 = data[r2:] - data[:-r2]
|
||||||
#d4 = d2 - d3
|
#d4 = d2 - d3
|
||||||
stdev = d2.std()
|
stdev = d2.std()
|
||||||
|
|
72
functions.py
72
functions.py
|
@ -5,6 +5,7 @@ Copyright 2010 Luke Campagnola
|
||||||
Distributed under MIT/X11 license. See license.txt for more infomation.
|
Distributed under MIT/X11 license. See license.txt for more infomation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .python2_3 import asUnicode
|
||||||
Colors = {
|
Colors = {
|
||||||
'b': (0,0,255,255),
|
'b': (0,0,255,255),
|
||||||
'g': (0,255,0,255),
|
'g': (0,255,0,255),
|
||||||
|
@ -357,28 +358,36 @@ def makeArrowPath(headLen=20, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def affineSlice(data, shape, origin, vectors, axes, **kargs):
|
def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs):
|
||||||
"""
|
"""
|
||||||
Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data.
|
Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data.
|
||||||
|
|
||||||
The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets.
|
The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates (see the scipy documentation for more information about this).
|
||||||
|
|
||||||
For a graphical interface to this function, see :func:`ROI.getArrayRegion`
|
For a graphical interface to this function, see :func:`ROI.getArrayRegion <pyqtgraph.ROI.getArrayRegion>`
|
||||||
|
|
||||||
|
============== ====================================================================================================
|
||||||
Arguments:
|
Arguments:
|
||||||
|
*data* (ndarray) the original dataset
|
||||||
|
*shape* the shape of the slice to take (Note the return value may have more dimensions than len(shape))
|
||||||
|
*origin* the location in the original dataset that will become the origin of the sliced data.
|
||||||
|
*vectors* list of unit vectors which point in the direction of the slice axes. Each vector must have the same
|
||||||
|
length as *axes*. If the vectors are not unit length, the result will be scaled relative to the
|
||||||
|
original data. If the vectors are not orthogonal, the result will be sheared relative to the
|
||||||
|
original data.
|
||||||
|
*axes* The axes in the original dataset which correspond to the slice *vectors*
|
||||||
|
*order* The order of spline interpolation. Default is 1 (linear). See scipy.ndimage.map_coordinates
|
||||||
|
for more information.
|
||||||
|
*returnCoords* If True, return a tuple (result, coords) where coords is the array of coordinates used to select
|
||||||
|
values from the original dataset.
|
||||||
|
*All extra keyword arguments are passed to scipy.ndimage.map_coordinates.*
|
||||||
|
--------------------------------------------------------------------------------------------------------------------
|
||||||
|
============== ====================================================================================================
|
||||||
|
|
||||||
| *data* (ndarray): the original dataset
|
Note the following must be true:
|
||||||
| *shape*: the shape of the slice to take (Note the return value may have more dimensions than len(shape))
|
|
||||||
| *origin*: the location in the original dataset that will become the origin in the sliced data.
|
|
||||||
| *vectors*: list of unit vectors which point in the direction of the slice axes
|
|
||||||
|
|
||||||
* each vector must have the same length as *axes*
|
| len(shape) == len(vectors)
|
||||||
* If the vectors are not unit length, the result will be scaled.
|
| len(origin) == len(axes) == len(vectors[i])
|
||||||
* If the vectors are not orthogonal, the result will be sheared.
|
|
||||||
|
|
||||||
*axes*: the axes in the original dataset which correspond to the slice *vectors*
|
|
||||||
|
|
||||||
All extra keyword arguments are passed to scipy.ndimage.map_coordinates
|
|
||||||
|
|
||||||
Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes
|
Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes
|
||||||
|
|
||||||
|
@ -391,10 +400,6 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs):
|
||||||
|
|
||||||
affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3))
|
affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3))
|
||||||
|
|
||||||
Note the following must be true:
|
|
||||||
|
|
||||||
| len(shape) == len(vectors)
|
|
||||||
| len(origin) == len(axes) == len(vectors[0])
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# sanity check
|
# sanity check
|
||||||
|
@ -436,7 +441,7 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs):
|
||||||
for inds in np.ndindex(*extraShape):
|
for inds in np.ndindex(*extraShape):
|
||||||
ind = (Ellipsis,) + inds
|
ind = (Ellipsis,) + inds
|
||||||
#print data[ind].shape, x.shape, output[ind].shape, output.shape
|
#print data[ind].shape, x.shape, output[ind].shape, output.shape
|
||||||
output[ind] = scipy.ndimage.map_coordinates(data[ind], x, **kargs)
|
output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs)
|
||||||
|
|
||||||
tr = list(range(output.ndim))
|
tr = list(range(output.ndim))
|
||||||
trb = []
|
trb = []
|
||||||
|
@ -447,9 +452,18 @@ def affineSlice(data, shape, origin, vectors, axes, **kargs):
|
||||||
tr2 = tuple(trb+tr)
|
tr2 = tuple(trb+tr)
|
||||||
|
|
||||||
## Untranspose array before returning
|
## Untranspose array before returning
|
||||||
return output.transpose(tr2)
|
output = output.transpose(tr2)
|
||||||
|
if returnCoords:
|
||||||
|
return (output, x)
|
||||||
|
else:
|
||||||
|
return output
|
||||||
|
|
||||||
|
def transformToArray(tr):
|
||||||
|
"""
|
||||||
|
Given a QTransform, return a 3x3 numpy array.
|
||||||
|
"""
|
||||||
|
return np.array([[tr.m11(), tr.m12(), tr.m13()],[tr.m21(), tr.m22(), tr.m23()],[tr.m31(), tr.m32(), tr.m33()]])
|
||||||
|
|
||||||
def solve3DTransform(points1, points2):
|
def solve3DTransform(points1, points2):
|
||||||
"""
|
"""
|
||||||
Find a 3D transformation matrix that maps points1 onto points2
|
Find a 3D transformation matrix that maps points1 onto points2
|
||||||
|
@ -1275,3 +1289,19 @@ def isosurface(data, level):
|
||||||
|
|
||||||
return facets
|
return facets
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def invertQTransform(tr):
|
||||||
|
"""Return a QTransform that is the inverse of *tr*.
|
||||||
|
Rasises an exception if tr is not invertible.
|
||||||
|
|
||||||
|
Note that this function is preferred over QTransform.inverted() due to
|
||||||
|
bugs in that method. (specifically, Qt has floating-point precision issues
|
||||||
|
when determining whether a matrix is invertible)
|
||||||
|
"""
|
||||||
|
#return tr.inverted()[0]
|
||||||
|
arr = np.array([[tr.m11(), tr.m12(), tr.m13()], [tr.m21(), tr.m22(), tr.m23()], [tr.m31(), tr.m32(), tr.m33()]])
|
||||||
|
inv = scipy.linalg.inv(arr)
|
||||||
|
return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1])
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
from pyqtgraph.python2_3 import asUnicode
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from pyqtgraph.Point import Point
|
from pyqtgraph.Point import Point
|
||||||
import pyqtgraph.debug as debug
|
import pyqtgraph.debug as debug
|
||||||
import weakref
|
import weakref
|
||||||
import pyqtgraph.functions as fn
|
import pyqtgraph.functions as fn
|
||||||
|
import pyqtgraph as pg
|
||||||
from .GraphicsWidget import GraphicsWidget
|
from .GraphicsWidget import GraphicsWidget
|
||||||
|
|
||||||
__all__ = ['AxisItem']
|
__all__ = ['AxisItem']
|
||||||
|
@ -65,8 +67,6 @@ class AxisItem(GraphicsWidget):
|
||||||
|
|
||||||
self.setRange(0, 1)
|
self.setRange(0, 1)
|
||||||
|
|
||||||
if pen is None:
|
|
||||||
pen = QtGui.QPen(QtGui.QColor(100, 100, 100))
|
|
||||||
self.setPen(pen)
|
self.setPen(pen)
|
||||||
|
|
||||||
self._linkedView = None
|
self._linkedView = None
|
||||||
|
@ -189,23 +189,31 @@ class AxisItem(GraphicsWidget):
|
||||||
self.setMaximumWidth(w)
|
self.setMaximumWidth(w)
|
||||||
self.setMinimumWidth(w)
|
self.setMinimumWidth(w)
|
||||||
|
|
||||||
|
def pen(self):
|
||||||
|
if self._pen is None:
|
||||||
|
return fn.mkPen(pg.getConfigOption('foreground'))
|
||||||
|
return self._pen
|
||||||
|
|
||||||
def setPen(self, pen):
|
def setPen(self, pen):
|
||||||
self.pen = pen
|
"""
|
||||||
|
Set the pen used for drawing text, axes, ticks, and grid lines.
|
||||||
|
if pen == None, the default will be used (see :func:`setConfigOption
|
||||||
|
<pyqtgraph.setConfigOption>`)
|
||||||
|
"""
|
||||||
|
self._pen = pen
|
||||||
self.picture = None
|
self.picture = None
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setScale(self, scale=None):
|
def setScale(self, scale=None):
|
||||||
"""
|
"""
|
||||||
Set the value scaling for this axis.
|
Set the value scaling for this axis. Values on the axis are multiplied
|
||||||
The scaling value 1) multiplies the values displayed along the axis
|
by this scale factor before being displayed as text. By default,
|
||||||
and 2) changes the way units are displayed in the label.
|
this scaling value is automatically determined based on the visible range
|
||||||
|
and the axis units are updated to reflect the chosen scale factor.
|
||||||
|
|
||||||
For example: If the axis spans values from -0.1 to 0.1 and has units set
|
For example: If the axis spans values from -0.1 to 0.1 and has units set
|
||||||
to 'V' then a scale of 1000 would cause the axis to display values -100 to 100
|
to 'V' then a scale of 1000 would cause the axis to display values -100 to 100
|
||||||
and the units would appear as 'mV'
|
and the units would appear as 'mV'
|
||||||
|
|
||||||
If scale is None, then it will be determined automatically based on the current
|
|
||||||
range displayed by the axis.
|
|
||||||
"""
|
"""
|
||||||
if scale is None:
|
if scale is None:
|
||||||
#if self.drawLabel: ## If there is a label, then we are free to rescale the values
|
#if self.drawLabel: ## If there is a label, then we are free to rescale the values
|
||||||
|
@ -219,8 +227,10 @@ class AxisItem(GraphicsWidget):
|
||||||
self.setLabel(unitPrefix=prefix)
|
self.setLabel(unitPrefix=prefix)
|
||||||
else:
|
else:
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
|
else:
|
||||||
|
self.setLabel(unitPrefix='')
|
||||||
|
self.autoScale = False
|
||||||
|
|
||||||
if scale != self.scale:
|
if scale != self.scale:
|
||||||
self.scale = scale
|
self.scale = scale
|
||||||
self.setLabel()
|
self.setLabel()
|
||||||
|
@ -354,6 +364,29 @@ class AxisItem(GraphicsWidget):
|
||||||
(intervals[minorIndex], 0)
|
(intervals[minorIndex], 0)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
##### This does not work -- switching between 2/5 confuses the automatic text-level-selection
|
||||||
|
### Determine major/minor tick spacings which flank the optimal spacing.
|
||||||
|
#intervals = np.array([1., 2., 5., 10., 20., 50., 100.]) * p10unit
|
||||||
|
#minorIndex = 0
|
||||||
|
#while intervals[minorIndex+1] <= optimalSpacing:
|
||||||
|
#minorIndex += 1
|
||||||
|
|
||||||
|
### make sure we never see 5 and 2 at the same time
|
||||||
|
#intIndexes = [
|
||||||
|
#[0,1,3],
|
||||||
|
#[0,2,3],
|
||||||
|
#[2,3,4],
|
||||||
|
#[3,4,6],
|
||||||
|
#[3,5,6],
|
||||||
|
#][minorIndex]
|
||||||
|
|
||||||
|
#return [
|
||||||
|
#(intervals[intIndexes[2]], 0),
|
||||||
|
#(intervals[intIndexes[1]], 0),
|
||||||
|
#(intervals[intIndexes[0]], 0)
|
||||||
|
#]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def tickValues(self, minVal, maxVal, size):
|
def tickValues(self, minVal, maxVal, size):
|
||||||
"""
|
"""
|
||||||
|
@ -370,8 +403,6 @@ class AxisItem(GraphicsWidget):
|
||||||
"""
|
"""
|
||||||
minVal, maxVal = sorted((minVal, maxVal))
|
minVal, maxVal = sorted((minVal, maxVal))
|
||||||
|
|
||||||
if self.logMode:
|
|
||||||
return self.logTickValues(minVal, maxVal, size)
|
|
||||||
|
|
||||||
ticks = []
|
ticks = []
|
||||||
tickLevels = self.tickSpacing(minVal, maxVal, size)
|
tickLevels = self.tickSpacing(minVal, maxVal, size)
|
||||||
|
@ -388,21 +419,36 @@ class AxisItem(GraphicsWidget):
|
||||||
## remove any ticks that were present in higher levels
|
## remove any ticks that were present in higher levels
|
||||||
## we assume here that if the difference between a tick value and a previously seen tick value
|
## we assume here that if the difference between a tick value and a previously seen tick value
|
||||||
## is less than spacing/100, then they are 'equal' and we can ignore the new tick.
|
## is less than spacing/100, then they are 'equal' and we can ignore the new tick.
|
||||||
values = filter(lambda x: all(np.abs(allValues-x) > spacing*0.01), values)
|
values = list(filter(lambda x: all(np.abs(allValues-x) > spacing*0.01), values) )
|
||||||
allValues = np.concatenate([allValues, values])
|
allValues = np.concatenate([allValues, values])
|
||||||
ticks.append((spacing, values))
|
ticks.append((spacing, values))
|
||||||
|
|
||||||
|
if self.logMode:
|
||||||
|
return self.logTickValues(minVal, maxVal, size, ticks)
|
||||||
|
|
||||||
return ticks
|
return ticks
|
||||||
|
|
||||||
def logTickValues(self, minVal, maxVal, size):
|
def logTickValues(self, minVal, maxVal, size, stdTicks):
|
||||||
v1 = int(np.floor(minVal))
|
|
||||||
v2 = int(np.ceil(maxVal))
|
|
||||||
major = list(range(v1+1, v2))
|
|
||||||
|
|
||||||
minor = []
|
## start with the tick spacing given by tickValues().
|
||||||
for v in range(v1, v2):
|
## Any level whose spacing is < 1 needs to be converted to log scale
|
||||||
minor.extend(v + np.log10(np.arange(1, 10)))
|
|
||||||
minor = [x for x in minor if x>minVal and x<maxVal]
|
ticks = []
|
||||||
return [(1.0, major), (None, minor)]
|
for (spacing, t) in stdTicks:
|
||||||
|
if spacing >= 1.0:
|
||||||
|
ticks.append((spacing, t))
|
||||||
|
|
||||||
|
if len(ticks) < 3:
|
||||||
|
v1 = int(np.floor(minVal))
|
||||||
|
v2 = int(np.ceil(maxVal))
|
||||||
|
#major = list(range(v1+1, v2))
|
||||||
|
|
||||||
|
minor = []
|
||||||
|
for v in range(v1, v2):
|
||||||
|
minor.extend(v + np.log10(np.arange(1, 10)))
|
||||||
|
minor = [x for x in minor if x>minVal and x<maxVal]
|
||||||
|
ticks.append((None, minor))
|
||||||
|
return ticks
|
||||||
|
|
||||||
def tickStrings(self, values, scale, spacing):
|
def tickStrings(self, values, scale, spacing):
|
||||||
"""Return the strings that should be placed next to ticks. This method is called
|
"""Return the strings that should be placed next to ticks. This method is called
|
||||||
|
@ -477,12 +523,14 @@ class AxisItem(GraphicsWidget):
|
||||||
#print tickStart, tickStop, span
|
#print tickStart, tickStop, span
|
||||||
|
|
||||||
## draw long line along axis
|
## draw long line along axis
|
||||||
p.setPen(self.pen)
|
p.setPen(self.pen())
|
||||||
p.drawLine(*span)
|
p.drawLine(*span)
|
||||||
p.translate(0.5,0) ## resolves some damn pixel ambiguity
|
p.translate(0.5,0) ## resolves some damn pixel ambiguity
|
||||||
|
|
||||||
## determine size of this item in pixels
|
## determine size of this item in pixels
|
||||||
points = list(map(self.mapToDevice, span))
|
points = list(map(self.mapToDevice, span))
|
||||||
|
if None in points:
|
||||||
|
return
|
||||||
lengthInPixels = Point(points[1] - points[0]).length()
|
lengthInPixels = Point(points[1] - points[0]).length()
|
||||||
if lengthInPixels == 0:
|
if lengthInPixels == 0:
|
||||||
return
|
return
|
||||||
|
@ -513,6 +561,10 @@ class AxisItem(GraphicsWidget):
|
||||||
else:
|
else:
|
||||||
xScale = bounds.width() / dif
|
xScale = bounds.width() / dif
|
||||||
offset = self.range[0] * xScale
|
offset = self.range[0] * xScale
|
||||||
|
|
||||||
|
xRange = [x * xScale - offset for x in self.range]
|
||||||
|
xMin = min(xRange)
|
||||||
|
xMax = max(xRange)
|
||||||
|
|
||||||
prof.mark('init')
|
prof.mark('init')
|
||||||
|
|
||||||
|
@ -521,6 +573,7 @@ class AxisItem(GraphicsWidget):
|
||||||
## draw ticks
|
## draw ticks
|
||||||
## (to improve performance, we do not interleave line and text drawing, since this causes unnecessary pipeline switching)
|
## (to improve performance, we do not interleave line and text drawing, since this causes unnecessary pipeline switching)
|
||||||
## draw three different intervals, long ticks first
|
## draw three different intervals, long ticks first
|
||||||
|
|
||||||
for i in range(len(tickLevels)):
|
for i in range(len(tickLevels)):
|
||||||
tickPositions.append([])
|
tickPositions.append([])
|
||||||
ticks = tickLevels[i][1]
|
ticks = tickLevels[i][1]
|
||||||
|
@ -530,19 +583,28 @@ class AxisItem(GraphicsWidget):
|
||||||
|
|
||||||
lineAlpha = 255 / (i+1)
|
lineAlpha = 255 / (i+1)
|
||||||
if self.grid is not False:
|
if self.grid is not False:
|
||||||
lineAlpha = self.grid
|
lineAlpha *= self.grid/255. * np.clip((0.05 * lengthInPixels / (len(ticks)+1)), 0., 1.)
|
||||||
|
|
||||||
for v in ticks:
|
for v in ticks:
|
||||||
|
## determine actual position to draw this tick
|
||||||
x = (v * xScale) - offset
|
x = (v * xScale) - offset
|
||||||
|
if x < xMin or x > xMax: ## last check to make sure no out-of-bounds ticks are drawn
|
||||||
|
tickPositions[i].append(None)
|
||||||
|
continue
|
||||||
|
tickPositions[i].append(x)
|
||||||
|
|
||||||
p1 = [x, x]
|
p1 = [x, x]
|
||||||
p2 = [x, x]
|
p2 = [x, x]
|
||||||
p1[axis] = tickStart
|
p1[axis] = tickStart
|
||||||
p2[axis] = tickStop
|
p2[axis] = tickStop
|
||||||
if self.grid is False:
|
if self.grid is False:
|
||||||
p2[axis] += tickLength*tickDir
|
p2[axis] += tickLength*tickDir
|
||||||
p.setPen(QtGui.QPen(QtGui.QColor(150, 150, 150, lineAlpha)))
|
tickPen = self.pen()
|
||||||
|
color = tickPen.color()
|
||||||
|
color.setAlpha(lineAlpha)
|
||||||
|
tickPen.setColor(color)
|
||||||
|
p.setPen(tickPen)
|
||||||
p.drawLine(Point(p1), Point(p2))
|
p.drawLine(Point(p1), Point(p2))
|
||||||
tickPositions[i].append(x)
|
|
||||||
prof.mark('draw ticks')
|
prof.mark('draw ticks')
|
||||||
|
|
||||||
## Draw text until there is no more room (or no more text)
|
## Draw text until there is no more room (or no more text)
|
||||||
|
@ -557,10 +619,15 @@ class AxisItem(GraphicsWidget):
|
||||||
|
|
||||||
if len(strings) == 0:
|
if len(strings) == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
## ignore strings belonging to ticks that were previously ignored
|
||||||
|
for j in range(len(strings)):
|
||||||
|
if tickPositions[i][j] is None:
|
||||||
|
strings[j] = None
|
||||||
|
|
||||||
|
textRects.extend([p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, s) for s in strings if s is not None])
|
||||||
if i > 0: ## always draw top level
|
if i > 0: ## always draw top level
|
||||||
## measure all text, make sure there's enough room
|
## measure all text, make sure there's enough room
|
||||||
textRects.extend([p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, s) for s in strings])
|
|
||||||
if axis == 0:
|
if axis == 0:
|
||||||
textSize = np.sum([r.height() for r in textRects])
|
textSize = np.sum([r.height() for r in textRects])
|
||||||
else:
|
else:
|
||||||
|
@ -570,11 +637,12 @@ class AxisItem(GraphicsWidget):
|
||||||
textFillRatio = float(textSize) / lengthInPixels
|
textFillRatio = float(textSize) / lengthInPixels
|
||||||
if textFillRatio > 0.7:
|
if textFillRatio > 0.7:
|
||||||
break
|
break
|
||||||
|
|
||||||
#spacing, values = tickLevels[best]
|
#spacing, values = tickLevels[best]
|
||||||
#strings = self.tickStrings(values, self.scale, spacing)
|
#strings = self.tickStrings(values, self.scale, spacing)
|
||||||
for j in range(len(strings)):
|
for j in range(len(strings)):
|
||||||
vstr = strings[j]
|
vstr = strings[j]
|
||||||
|
if vstr is None:## this tick was ignored because it is out of bounds
|
||||||
|
continue
|
||||||
x = tickPositions[i][j]
|
x = tickPositions[i][j]
|
||||||
textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr)
|
textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr)
|
||||||
height = textRect.height()
|
height = textRect.height()
|
||||||
|
@ -592,7 +660,7 @@ class AxisItem(GraphicsWidget):
|
||||||
textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop
|
textFlags = QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop
|
||||||
rect = QtCore.QRectF(x-100, tickStop+max(0,self.tickLength), 200, height)
|
rect = QtCore.QRectF(x-100, tickStop+max(0,self.tickLength), 200, height)
|
||||||
|
|
||||||
p.setPen(QtGui.QPen(QtGui.QColor(150, 150, 150)))
|
p.setPen(self.pen())
|
||||||
p.drawText(rect, textFlags, vstr)
|
p.drawText(rect, textFlags, vstr)
|
||||||
prof.mark('draw text')
|
prof.mark('draw text')
|
||||||
prof.finish()
|
prof.finish()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
from pyqtgraph.python2_3 import sortList
|
||||||
import pyqtgraph.functions as fn
|
import pyqtgraph.functions as fn
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
from .GraphicsWidget import GraphicsWidget
|
from .GraphicsWidget import GraphicsWidget
|
||||||
|
@ -175,7 +176,7 @@ class TickSliderItem(GraphicsWidget):
|
||||||
|
|
||||||
def resizeEvent(self, ev):
|
def resizeEvent(self, ev):
|
||||||
wlen = max(40, self.widgetLength())
|
wlen = max(40, self.widgetLength())
|
||||||
self.setLength(wlen-self.tickSize)
|
self.setLength(wlen-self.tickSize-2)
|
||||||
self.setOrientation(self.orientation)
|
self.setOrientation(self.orientation)
|
||||||
#bounds = self.scene().itemsBoundingRect()
|
#bounds = self.scene().itemsBoundingRect()
|
||||||
#bounds.setLeft(min(-self.tickSize*0.5, bounds.left()))
|
#bounds.setLeft(min(-self.tickSize*0.5, bounds.left()))
|
||||||
|
@ -186,7 +187,7 @@ class TickSliderItem(GraphicsWidget):
|
||||||
def setLength(self, newLen):
|
def setLength(self, newLen):
|
||||||
#private
|
#private
|
||||||
for t, x in list(self.ticks.items()):
|
for t, x in list(self.ticks.items()):
|
||||||
t.setPos(x * newLen, t.pos().y())
|
t.setPos(x * newLen + 1, t.pos().y())
|
||||||
self.length = float(newLen)
|
self.length = float(newLen)
|
||||||
|
|
||||||
#def mousePressEvent(self, ev):
|
#def mousePressEvent(self, ev):
|
||||||
|
@ -491,8 +492,8 @@ class GradientEditorItem(TickSliderItem):
|
||||||
def setLength(self, newLen):
|
def setLength(self, newLen):
|
||||||
#private (but maybe public)
|
#private (but maybe public)
|
||||||
TickSliderItem.setLength(self, newLen)
|
TickSliderItem.setLength(self, newLen)
|
||||||
self.backgroundRect.setRect(0, -self.rectSize, newLen, self.rectSize)
|
self.backgroundRect.setRect(1, -self.rectSize, newLen, self.rectSize)
|
||||||
self.gradRect.setRect(0, -self.rectSize, newLen, self.rectSize)
|
self.gradRect.setRect(1, -self.rectSize, newLen, self.rectSize)
|
||||||
self.updateGradient()
|
self.updateGradient()
|
||||||
|
|
||||||
def currentColorChanged(self, color):
|
def currentColorChanged(self, color):
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
from pyqtgraph.GraphicsScene import GraphicsScene
|
from pyqtgraph.GraphicsScene import GraphicsScene
|
||||||
from pyqtgraph.Point import Point
|
from pyqtgraph.Point import Point
|
||||||
|
import pyqtgraph.functions as fn
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
class GraphicsItem(object):
|
class GraphicsItem(object):
|
||||||
|
@ -149,16 +150,32 @@ class GraphicsItem(object):
|
||||||
"""Return vectors in local coordinates representing the width and height of a view pixel.
|
"""Return vectors in local coordinates representing the width and height of a view pixel.
|
||||||
If direction is specified, then return vectors parallel and orthogonal to it.
|
If direction is specified, then return vectors parallel and orthogonal to it.
|
||||||
|
|
||||||
Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed)."""
|
Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed)
|
||||||
|
or if pixel size is below floating-point precision limit.
|
||||||
|
"""
|
||||||
|
|
||||||
dt = self.deviceTransform()
|
dt = self.deviceTransform()
|
||||||
if dt is None:
|
if dt is None:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
if direction is None:
|
if direction is None:
|
||||||
direction = Point(1, 0)
|
direction = Point(1, 0)
|
||||||
|
if direction.manhattanLength() == 0:
|
||||||
|
raise Exception("Cannot compute pixel length for 0-length vector.")
|
||||||
|
|
||||||
|
## attempt to re-scale direction vector to fit within the precision of the coordinate system
|
||||||
|
if direction.x() == 0:
|
||||||
|
r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))
|
||||||
|
elif direction.y() == 0:
|
||||||
|
r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))
|
||||||
|
else:
|
||||||
|
r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5
|
||||||
|
direction = direction * r
|
||||||
|
|
||||||
viewDir = Point(dt.map(direction) - dt.map(Point(0,0)))
|
viewDir = Point(dt.map(direction) - dt.map(Point(0,0)))
|
||||||
|
if viewDir.manhattanLength() == 0:
|
||||||
|
return None, None ## pixel size cannot be represented on this scale
|
||||||
|
|
||||||
orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space
|
orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -168,7 +185,7 @@ class GraphicsItem(object):
|
||||||
raise Exception("Invalid direction %s" %direction)
|
raise Exception("Invalid direction %s" %direction)
|
||||||
|
|
||||||
|
|
||||||
dti = dt.inverted()[0]
|
dti = fn.invertQTransform(dt)
|
||||||
return Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0)))
|
return Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0)))
|
||||||
|
|
||||||
#vt = self.deviceTransform()
|
#vt = self.deviceTransform()
|
||||||
|
@ -194,23 +211,26 @@ class GraphicsItem(object):
|
||||||
|
|
||||||
|
|
||||||
def pixelSize(self):
|
def pixelSize(self):
|
||||||
|
## deprecated
|
||||||
v = self.pixelVectors()
|
v = self.pixelVectors()
|
||||||
if v == (None, None):
|
if v == (None, None):
|
||||||
return None, None
|
return None, None
|
||||||
return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5
|
return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5
|
||||||
|
|
||||||
def pixelWidth(self):
|
def pixelWidth(self):
|
||||||
|
## deprecated
|
||||||
vt = self.deviceTransform()
|
vt = self.deviceTransform()
|
||||||
if vt is None:
|
if vt is None:
|
||||||
return 0
|
return 0
|
||||||
vt = vt.inverted()[0]
|
vt = fn.invertQTransform(vt)
|
||||||
return Point(vt.map(QtCore.QPointF(1, 0))-vt.map(QtCore.QPointF(0, 0))).length()
|
return Point(vt.map(QtCore.QPointF(1, 0))-vt.map(QtCore.QPointF(0, 0))).length()
|
||||||
|
|
||||||
def pixelHeight(self):
|
def pixelHeight(self):
|
||||||
|
## deprecated
|
||||||
vt = self.deviceTransform()
|
vt = self.deviceTransform()
|
||||||
if vt is None:
|
if vt is None:
|
||||||
return 0
|
return 0
|
||||||
vt = vt.inverted()[0]
|
vt = fn.invertQTransform(vt)
|
||||||
return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length()
|
return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length()
|
||||||
|
|
||||||
|
|
||||||
|
@ -232,7 +252,7 @@ class GraphicsItem(object):
|
||||||
vt = self.deviceTransform()
|
vt = self.deviceTransform()
|
||||||
if vt is None:
|
if vt is None:
|
||||||
return None
|
return None
|
||||||
vt = vt.inverted()[0]
|
vt = fn.invertQTransform(vt)
|
||||||
return vt.map(obj)
|
return vt.map(obj)
|
||||||
|
|
||||||
def mapRectToDevice(self, rect):
|
def mapRectToDevice(self, rect):
|
||||||
|
@ -253,7 +273,7 @@ class GraphicsItem(object):
|
||||||
vt = self.deviceTransform()
|
vt = self.deviceTransform()
|
||||||
if vt is None:
|
if vt is None:
|
||||||
return None
|
return None
|
||||||
vt = vt.inverted()[0]
|
vt = fn.invertQTransform(vt)
|
||||||
return vt.mapRect(rect)
|
return vt.mapRect(rect)
|
||||||
|
|
||||||
def mapToView(self, obj):
|
def mapToView(self, obj):
|
||||||
|
@ -272,14 +292,14 @@ class GraphicsItem(object):
|
||||||
vt = self.viewTransform()
|
vt = self.viewTransform()
|
||||||
if vt is None:
|
if vt is None:
|
||||||
return None
|
return None
|
||||||
vt = vt.inverted()[0]
|
vt = fn.invertQTransform(vt)
|
||||||
return vt.map(obj)
|
return vt.map(obj)
|
||||||
|
|
||||||
def mapRectFromView(self, obj):
|
def mapRectFromView(self, obj):
|
||||||
vt = self.viewTransform()
|
vt = self.viewTransform()
|
||||||
if vt is None:
|
if vt is None:
|
||||||
return None
|
return None
|
||||||
vt = vt.inverted()[0]
|
vt = fn.invertQTransform(vt)
|
||||||
return vt.mapRect(obj)
|
return vt.mapRect(obj)
|
||||||
|
|
||||||
def pos(self):
|
def pos(self):
|
||||||
|
|
|
@ -21,21 +21,29 @@ class GraphicsLayout(GraphicsWidget):
|
||||||
self.border = border
|
self.border = border
|
||||||
self.layout = QtGui.QGraphicsGridLayout()
|
self.layout = QtGui.QGraphicsGridLayout()
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
self.items = {}
|
self.items = {} ## item: [(row, col), (row, col), ...] lists all cells occupied by the item
|
||||||
self.rows = {}
|
self.rows = {} ## row: {col1: item1, col2: item2, ...} maps cell location to item
|
||||||
self.currentRow = 0
|
self.currentRow = 0
|
||||||
self.currentCol = 0
|
self.currentCol = 0
|
||||||
|
self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding))
|
||||||
|
|
||||||
|
#def resizeEvent(self, ev):
|
||||||
|
#ret = GraphicsWidget.resizeEvent(self, ev)
|
||||||
|
#print self.pos(), self.mapToDevice(self.rect().topLeft())
|
||||||
|
#return ret
|
||||||
|
|
||||||
def nextRow(self):
|
def nextRow(self):
|
||||||
"""Advance to next row for automatic item placement"""
|
"""Advance to next row for automatic item placement"""
|
||||||
self.currentRow += 1
|
self.currentRow += 1
|
||||||
self.currentCol = 0
|
self.currentCol = -1
|
||||||
|
self.nextColumn()
|
||||||
|
|
||||||
def nextColumn(self, colspan=1):
|
def nextColumn(self):
|
||||||
"""Advance to next column, while returning the current column number
|
"""Advance to next available column
|
||||||
(generally only for internal use--called by addItem)"""
|
(generally only for internal use--called by addItem)"""
|
||||||
self.currentCol += colspan
|
self.currentCol += 1
|
||||||
return self.currentCol-colspan
|
while self.getItem(self.currentRow, self.currentCol) is not None:
|
||||||
|
self.currentCol += 1
|
||||||
|
|
||||||
def nextCol(self, *args, **kargs):
|
def nextCol(self, *args, **kargs):
|
||||||
"""Alias of nextColumn"""
|
"""Alias of nextColumn"""
|
||||||
|
@ -66,6 +74,8 @@ class GraphicsLayout(GraphicsWidget):
|
||||||
Create a LabelItem with *text* and place it in the next available cell (or in the cell specified)
|
Create a LabelItem with *text* and place it in the next available cell (or in the cell specified)
|
||||||
All extra keyword arguments are passed to :func:`LabelItem.__init__ <pyqtgraph.LabelItem.__init__>`
|
All extra keyword arguments are passed to :func:`LabelItem.__init__ <pyqtgraph.LabelItem.__init__>`
|
||||||
Returns the created item.
|
Returns the created item.
|
||||||
|
|
||||||
|
To create a vertical label, use *angle*=-90
|
||||||
"""
|
"""
|
||||||
text = LabelItem(text, **kargs)
|
text = LabelItem(text, **kargs)
|
||||||
self.addItem(text, row, col, rowspan, colspan)
|
self.addItem(text, row, col, rowspan, colspan)
|
||||||
|
@ -89,18 +99,24 @@ class GraphicsLayout(GraphicsWidget):
|
||||||
if row is None:
|
if row is None:
|
||||||
row = self.currentRow
|
row = self.currentRow
|
||||||
if col is None:
|
if col is None:
|
||||||
col = self.nextCol(colspan)
|
col = self.currentCol
|
||||||
|
|
||||||
if row not in self.rows:
|
self.items[item] = []
|
||||||
self.rows[row] = {}
|
for i in range(rowspan):
|
||||||
self.rows[row][col] = item
|
for j in range(colspan):
|
||||||
self.items[item] = (row, col)
|
row2 = row + i
|
||||||
|
col2 = col + j
|
||||||
|
if row2 not in self.rows:
|
||||||
|
self.rows[row2] = {}
|
||||||
|
self.rows[row2][col2] = item
|
||||||
|
self.items[item].append((row2, col2))
|
||||||
|
|
||||||
self.layout.addItem(item, row, col, rowspan, colspan)
|
self.layout.addItem(item, row, col, rowspan, colspan)
|
||||||
|
self.nextColumn()
|
||||||
|
|
||||||
def getItem(self, row, col):
|
def getItem(self, row, col):
|
||||||
"""Return the item in (*row*, *col*)"""
|
"""Return the item in (*row*, *col*). If the cell is empty, return None."""
|
||||||
return self.row[row][col]
|
return self.rows.get(row, {}).get(col, None)
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
return self.rect()
|
return self.rect()
|
||||||
|
@ -124,9 +140,10 @@ class GraphicsLayout(GraphicsWidget):
|
||||||
ind = self.itemIndex(item)
|
ind = self.itemIndex(item)
|
||||||
self.layout.removeAt(ind)
|
self.layout.removeAt(ind)
|
||||||
self.scene().removeItem(item)
|
self.scene().removeItem(item)
|
||||||
r,c = self.items[item]
|
|
||||||
|
for r,c in self.items[item]:
|
||||||
|
del self.rows[r][c]
|
||||||
del self.items[item]
|
del self.items[item]
|
||||||
del self.rows[r][c]
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
|
|
@ -2,6 +2,7 @@ from pyqtgraph.Qt import QtGui, QtCore
|
||||||
from .UIGraphicsItem import *
|
from .UIGraphicsItem import *
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from pyqtgraph.Point import Point
|
from pyqtgraph.Point import Point
|
||||||
|
import pyqtgraph.functions as fn
|
||||||
|
|
||||||
__all__ = ['GridItem']
|
__all__ = ['GridItem']
|
||||||
class GridItem(UIGraphicsItem):
|
class GridItem(UIGraphicsItem):
|
||||||
|
@ -47,7 +48,7 @@ class GridItem(UIGraphicsItem):
|
||||||
p = QtGui.QPainter()
|
p = QtGui.QPainter()
|
||||||
p.begin(self.picture)
|
p.begin(self.picture)
|
||||||
|
|
||||||
dt = self.viewTransform().inverted()[0]
|
dt = fn.invertQTransform(self.viewTransform())
|
||||||
vr = self.getViewWidget().rect()
|
vr = self.getViewWidget().rect()
|
||||||
unit = self.pixelWidth(), self.pixelHeight()
|
unit = self.pixelWidth(), self.pixelHeight()
|
||||||
dim = [vr.width(), vr.height()]
|
dim = [vr.width(), vr.height()]
|
||||||
|
@ -112,7 +113,7 @@ class GridItem(UIGraphicsItem):
|
||||||
texts.append((QtCore.QPointF(x, y), "%g"%p1[ax]))
|
texts.append((QtCore.QPointF(x, y), "%g"%p1[ax]))
|
||||||
tr = self.deviceTransform()
|
tr = self.deviceTransform()
|
||||||
#tr.scale(1.5, 1.5)
|
#tr.scale(1.5, 1.5)
|
||||||
p.setWorldTransform(tr.inverted()[0])
|
p.setWorldTransform(fn.invertQTransform(tr))
|
||||||
for t in texts:
|
for t in texts:
|
||||||
x = tr.map(t[0]) + Point(0.5, 0.5)
|
x = tr.map(t[0]) + Point(0.5, 0.5)
|
||||||
p.drawText(x, t[1])
|
p.drawText(x, t[1])
|
||||||
|
|
|
@ -50,7 +50,7 @@ class HistogramLUTItem(GraphicsWidget):
|
||||||
self.layout.setSpacing(0)
|
self.layout.setSpacing(0)
|
||||||
self.vb = ViewBox()
|
self.vb = ViewBox()
|
||||||
self.vb.setMaximumWidth(152)
|
self.vb.setMaximumWidth(152)
|
||||||
self.vb.setMinimumWidth(52)
|
self.vb.setMinimumWidth(45)
|
||||||
self.vb.setMouseEnabled(x=False, y=True)
|
self.vb.setMouseEnabled(x=False, y=True)
|
||||||
self.gradient = GradientEditorItem()
|
self.gradient = GradientEditorItem()
|
||||||
self.gradient.setOrientation('right')
|
self.gradient.setOrientation('right')
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
import pyqtgraph.functions as fn
|
import pyqtgraph.functions as fn
|
||||||
|
import pyqtgraph as pg
|
||||||
from .GraphicsWidget import GraphicsWidget
|
from .GraphicsWidget import GraphicsWidget
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,14 +19,13 @@ class LabelItem(GraphicsWidget):
|
||||||
GraphicsWidget.__init__(self, parent)
|
GraphicsWidget.__init__(self, parent)
|
||||||
self.item = QtGui.QGraphicsTextItem(self)
|
self.item = QtGui.QGraphicsTextItem(self)
|
||||||
self.opts = {
|
self.opts = {
|
||||||
'color': 'CCC',
|
'color': None,
|
||||||
'justify': 'center'
|
'justify': 'center'
|
||||||
}
|
}
|
||||||
self.opts.update(args)
|
self.opts.update(args)
|
||||||
self.sizeHint = {}
|
self._sizeHint = {}
|
||||||
self.setText(text)
|
self.setText(text)
|
||||||
self.setAngle(angle)
|
self.setAngle(angle)
|
||||||
|
|
||||||
|
|
||||||
def setAttr(self, attr, value):
|
def setAttr(self, attr, value):
|
||||||
"""Set default text properties. See setText() for accepted parameters."""
|
"""Set default text properties. See setText() for accepted parameters."""
|
||||||
|
@ -44,15 +44,17 @@ class LabelItem(GraphicsWidget):
|
||||||
==================== ==============================
|
==================== ==============================
|
||||||
"""
|
"""
|
||||||
self.text = text
|
self.text = text
|
||||||
opts = self.opts.copy()
|
opts = self.opts
|
||||||
for k in args:
|
for k in args:
|
||||||
opts[k] = args[k]
|
opts[k] = args[k]
|
||||||
|
|
||||||
optlist = []
|
optlist = []
|
||||||
if 'color' in opts:
|
|
||||||
if isinstance(opts['color'], QtGui.QColor):
|
color = self.opts['color']
|
||||||
opts['color'] = fn.colorStr(opts['color'])[:6]
|
if color is None:
|
||||||
optlist.append('color: #' + opts['color'])
|
color = pg.getConfigOption('foreground')
|
||||||
|
color = fn.mkColor(color)
|
||||||
|
optlist.append('color: #' + fn.colorStr(color)[:6])
|
||||||
if 'size' in opts:
|
if 'size' in opts:
|
||||||
optlist.append('font-size: ' + opts['size'])
|
optlist.append('font-size: ' + opts['size'])
|
||||||
if 'bold' in opts and opts['bold'] in [True, False]:
|
if 'bold' in opts and opts['bold'] in [True, False]:
|
||||||
|
@ -64,7 +66,7 @@ class LabelItem(GraphicsWidget):
|
||||||
self.item.setHtml(full)
|
self.item.setHtml(full)
|
||||||
self.updateMin()
|
self.updateMin()
|
||||||
self.resizeEvent(None)
|
self.resizeEvent(None)
|
||||||
self.update()
|
self.updateGeometry()
|
||||||
|
|
||||||
def resizeEvent(self, ev):
|
def resizeEvent(self, ev):
|
||||||
#c1 = self.boundingRect().center()
|
#c1 = self.boundingRect().center()
|
||||||
|
@ -72,16 +74,35 @@ class LabelItem(GraphicsWidget):
|
||||||
#dif = c1 - c2
|
#dif = c1 - c2
|
||||||
#self.item.moveBy(dif.x(), dif.y())
|
#self.item.moveBy(dif.x(), dif.y())
|
||||||
#print c1, c2, dif, self.item.pos()
|
#print c1, c2, dif, self.item.pos()
|
||||||
|
self.item.setPos(0,0)
|
||||||
|
bounds = self.itemRect()
|
||||||
|
left = self.mapFromItem(self.item, QtCore.QPointF(0,0)) - self.mapFromItem(self.item, QtCore.QPointF(1,0))
|
||||||
|
rect = self.rect()
|
||||||
|
|
||||||
if self.opts['justify'] == 'left':
|
if self.opts['justify'] == 'left':
|
||||||
self.item.setPos(0,0)
|
if left.x() != 0:
|
||||||
|
bounds.moveLeft(rect.left())
|
||||||
|
if left.y() < 0:
|
||||||
|
bounds.moveTop(rect.top())
|
||||||
|
elif left.y() > 0:
|
||||||
|
bounds.moveBottom(rect.bottom())
|
||||||
|
|
||||||
elif self.opts['justify'] == 'center':
|
elif self.opts['justify'] == 'center':
|
||||||
bounds = self.item.mapRectToParent(self.item.boundingRect())
|
bounds.moveCenter(rect.center())
|
||||||
self.item.setPos(self.width()/2. - bounds.width()/2., 0)
|
#bounds = self.itemRect()
|
||||||
|
#self.item.setPos(self.width()/2. - bounds.width()/2., 0)
|
||||||
elif self.opts['justify'] == 'right':
|
elif self.opts['justify'] == 'right':
|
||||||
bounds = self.item.mapRectToParent(self.item.boundingRect())
|
if left.x() != 0:
|
||||||
self.item.setPos(self.width() - bounds.width(), 0)
|
bounds.moveRight(rect.right())
|
||||||
#if self.width() > 0:
|
if left.y() < 0:
|
||||||
#self.item.setTextWidth(self.width())
|
bounds.moveBottom(rect.bottom())
|
||||||
|
elif left.y() > 0:
|
||||||
|
bounds.moveTop(rect.top())
|
||||||
|
#bounds = self.itemRect()
|
||||||
|
#self.item.setPos(self.width() - bounds.width(), 0)
|
||||||
|
|
||||||
|
self.item.setPos(bounds.topLeft() - self.itemRect().topLeft())
|
||||||
|
self.updateMin()
|
||||||
|
|
||||||
def setAngle(self, angle):
|
def setAngle(self, angle):
|
||||||
self.angle = angle
|
self.angle = angle
|
||||||
|
@ -89,27 +110,31 @@ class LabelItem(GraphicsWidget):
|
||||||
self.item.rotate(angle)
|
self.item.rotate(angle)
|
||||||
self.updateMin()
|
self.updateMin()
|
||||||
|
|
||||||
|
|
||||||
def updateMin(self):
|
def updateMin(self):
|
||||||
bounds = self.item.mapRectToParent(self.item.boundingRect())
|
bounds = self.itemRect()
|
||||||
self.setMinimumWidth(bounds.width())
|
self.setMinimumWidth(bounds.width())
|
||||||
self.setMinimumHeight(bounds.height())
|
self.setMinimumHeight(bounds.height())
|
||||||
|
|
||||||
self.sizeHint = {
|
self._sizeHint = {
|
||||||
QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()),
|
QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()),
|
||||||
QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()),
|
QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()),
|
||||||
QtCore.Qt.MaximumSize: (-1, -1), #bounds.width()*2, bounds.height()*2),
|
QtCore.Qt.MaximumSize: (-1, -1), #bounds.width()*2, bounds.height()*2),
|
||||||
QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this?
|
QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this?
|
||||||
}
|
}
|
||||||
|
self.updateGeometry()
|
||||||
self.update()
|
|
||||||
|
|
||||||
def sizeHint(self, hint, constraint):
|
def sizeHint(self, hint, constraint):
|
||||||
if hint not in self.sizeHint:
|
if hint not in self._sizeHint:
|
||||||
return QtCore.QSizeF(0, 0)
|
return QtCore.QSizeF(0, 0)
|
||||||
return QtCore.QSizeF(*self.sizeHint[hint])
|
return QtCore.QSizeF(*self._sizeHint[hint])
|
||||||
|
|
||||||
|
def itemRect(self):
|
||||||
|
return self.item.mapRectToParent(self.item.boundingRect())
|
||||||
|
|
||||||
#def paint(self, p, *args):
|
#def paint(self, p, *args):
|
||||||
#p.setPen(fn.mkPen('r'))
|
#p.setPen(fn.mkPen('r'))
|
||||||
#p.drawRect(self.rect())
|
#p.drawRect(self.rect())
|
||||||
#p.drawRect(self.item.boundingRect())
|
#p.setPen(fn.mkPen('g'))
|
||||||
|
#p.drawRect(self.itemRect())
|
||||||
|
|
||||||
|
|
|
@ -33,17 +33,12 @@ from .. AxisItem import AxisItem
|
||||||
from .. LabelItem import LabelItem
|
from .. LabelItem import LabelItem
|
||||||
from .. GraphicsWidget import GraphicsWidget
|
from .. GraphicsWidget import GraphicsWidget
|
||||||
from .. ButtonItem import ButtonItem
|
from .. ButtonItem import ButtonItem
|
||||||
|
#from .. GraphicsLayout import GraphicsLayout
|
||||||
from pyqtgraph.WidgetGroup import WidgetGroup
|
from pyqtgraph.WidgetGroup import WidgetGroup
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
__all__ = ['PlotItem']
|
__all__ = ['PlotItem']
|
||||||
|
|
||||||
#try:
|
|
||||||
#from WidgetGroup import *
|
|
||||||
#HAVE_WIDGETGROUP = True
|
|
||||||
#except:
|
|
||||||
#HAVE_WIDGETGROUP = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from metaarray import *
|
from metaarray import *
|
||||||
HAVE_METAARRAY = True
|
HAVE_METAARRAY = True
|
||||||
|
@ -78,6 +73,7 @@ class PlotItem(GraphicsWidget):
|
||||||
:func:`enableAutoRange <pyqtgraph.ViewBox.enableAutoRange>`,
|
:func:`enableAutoRange <pyqtgraph.ViewBox.enableAutoRange>`,
|
||||||
:func:`disableAutoRange <pyqtgraph.ViewBox.disableAutoRange>`,
|
:func:`disableAutoRange <pyqtgraph.ViewBox.disableAutoRange>`,
|
||||||
:func:`setAspectLocked <pyqtgraph.ViewBox.setAspectLocked>`,
|
:func:`setAspectLocked <pyqtgraph.ViewBox.setAspectLocked>`,
|
||||||
|
:func:`invertY <pyqtgraph.ViewBox.invertY>`,
|
||||||
:func:`register <pyqtgraph.ViewBox.register>`,
|
:func:`register <pyqtgraph.ViewBox.register>`,
|
||||||
:func:`unregister <pyqtgraph.ViewBox.unregister>`
|
:func:`unregister <pyqtgraph.ViewBox.unregister>`
|
||||||
|
|
||||||
|
@ -99,26 +95,28 @@ class PlotItem(GraphicsWidget):
|
||||||
lastFileDir = None
|
lastFileDir = None
|
||||||
managers = {}
|
managers = {}
|
||||||
|
|
||||||
def __init__(self, parent=None, name=None, labels=None, title=None, **kargs):
|
def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs):
|
||||||
"""
|
"""
|
||||||
Create a new PlotItem. All arguments are optional.
|
Create a new PlotItem. All arguments are optional.
|
||||||
Any extra keyword arguments are passed to PlotItem.plot().
|
Any extra keyword arguments are passed to PlotItem.plot().
|
||||||
|
|
||||||
============= ==========================================================================================
|
============== ==========================================================================================
|
||||||
**Arguments**
|
**Arguments**
|
||||||
*title* Title to display at the top of the item. Html is allowed.
|
*title* Title to display at the top of the item. Html is allowed.
|
||||||
*labels* A dictionary specifying the axis labels to display::
|
*labels* A dictionary specifying the axis labels to display::
|
||||||
|
|
||||||
{'left': (args), 'bottom': (args), ...}
|
{'left': (args), 'bottom': (args), ...}
|
||||||
|
|
||||||
The name of each axis and the corresponding arguments are passed to
|
The name of each axis and the corresponding arguments are passed to
|
||||||
:func:`PlotItem.setLabel() <pyqtgraph.PlotItem.setLabel>`
|
:func:`PlotItem.setLabel() <pyqtgraph.PlotItem.setLabel>`
|
||||||
Optionally, PlotItem my also be initialized with the keyword arguments left,
|
Optionally, PlotItem my also be initialized with the keyword arguments left,
|
||||||
right, top, or bottom to achieve the same effect.
|
right, top, or bottom to achieve the same effect.
|
||||||
*name* Registers a name for this view so that others may link to it
|
*name* Registers a name for this view so that others may link to it
|
||||||
============= ==========================================================================================
|
*viewBox* If specified, the PlotItem will be constructed with this as its ViewBox.
|
||||||
|
*axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items
|
||||||
|
for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top')
|
||||||
|
and the values must be instances of AxisItem (or at least compatible with AxisItem).
|
||||||
|
============== ==========================================================================================
|
||||||
"""
|
"""
|
||||||
|
|
||||||
GraphicsWidget.__init__(self, parent)
|
GraphicsWidget.__init__(self, parent)
|
||||||
|
@ -127,8 +125,6 @@ class PlotItem(GraphicsWidget):
|
||||||
|
|
||||||
## Set up control buttons
|
## Set up control buttons
|
||||||
path = os.path.dirname(__file__)
|
path = os.path.dirname(__file__)
|
||||||
#self.ctrlBtn = ButtonItem(os.path.join(path, 'ctrl.png'), 14, self)
|
|
||||||
#self.ctrlBtn.clicked.connect(self.ctrlBtnClicked)
|
|
||||||
self.autoImageFile = os.path.join(path, 'auto.png')
|
self.autoImageFile = os.path.join(path, 'auto.png')
|
||||||
self.lockImageFile = os.path.join(path, 'lock.png')
|
self.lockImageFile = os.path.join(path, 'lock.png')
|
||||||
self.autoBtn = ButtonItem(self.autoImageFile, 14, self)
|
self.autoBtn = ButtonItem(self.autoImageFile, 14, self)
|
||||||
|
@ -141,32 +137,33 @@ class PlotItem(GraphicsWidget):
|
||||||
self.layout.setHorizontalSpacing(0)
|
self.layout.setHorizontalSpacing(0)
|
||||||
self.layout.setVerticalSpacing(0)
|
self.layout.setVerticalSpacing(0)
|
||||||
|
|
||||||
self.vb = ViewBox(name=name)
|
if viewBox is None:
|
||||||
|
viewBox = ViewBox()
|
||||||
|
self.vb = viewBox
|
||||||
|
self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
self.vb.register(name)
|
||||||
self.vb.sigRangeChanged.connect(self.sigRangeChanged)
|
self.vb.sigRangeChanged.connect(self.sigRangeChanged)
|
||||||
self.vb.sigXRangeChanged.connect(self.sigXRangeChanged)
|
self.vb.sigXRangeChanged.connect(self.sigXRangeChanged)
|
||||||
self.vb.sigYRangeChanged.connect(self.sigYRangeChanged)
|
self.vb.sigYRangeChanged.connect(self.sigYRangeChanged)
|
||||||
#self.vb.sigRangeChangedManually.connect(self.enableManualScale)
|
|
||||||
#self.vb.sigRangeChanged.connect(self.viewRangeChanged)
|
|
||||||
|
|
||||||
self.layout.addItem(self.vb, 2, 1)
|
self.layout.addItem(self.vb, 2, 1)
|
||||||
self.alpha = 1.0
|
self.alpha = 1.0
|
||||||
self.autoAlpha = True
|
self.autoAlpha = True
|
||||||
self.spectrumMode = False
|
self.spectrumMode = False
|
||||||
|
|
||||||
#self.autoScale = [True, True]
|
## Create and place axis items
|
||||||
|
if axisItems is None:
|
||||||
## Create and place scale items
|
axisItems = {}
|
||||||
self.scales = {
|
self.axes = {}
|
||||||
'top': {'item': AxisItem(orientation='top', linkView=self.vb), 'pos': (1, 1)},
|
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
|
||||||
'bottom': {'item': AxisItem(orientation='bottom', linkView=self.vb), 'pos': (3, 1)},
|
axis = axisItems.get(k, AxisItem(orientation=k))
|
||||||
'left': {'item': AxisItem(orientation='left', linkView=self.vb), 'pos': (2, 0)},
|
axis.linkToView(self.vb)
|
||||||
'right': {'item': AxisItem(orientation='right', linkView=self.vb), 'pos': (2, 2)}
|
self.axes[k] = {'item': axis, 'pos': pos}
|
||||||
}
|
self.layout.addItem(axis, *pos)
|
||||||
for k in self.scales:
|
axis.setZValue(-1000)
|
||||||
item = self.scales[k]['item']
|
axis.setFlag(axis.ItemNegativeZStacksBehindParent)
|
||||||
self.layout.addItem(item, *self.scales[k]['pos'])
|
|
||||||
item.setZValue(-1000)
|
|
||||||
item.setFlag(item.ItemNegativeZStacksBehindParent)
|
|
||||||
|
|
||||||
self.titleLabel = LabelItem('', size='11pt')
|
self.titleLabel = LabelItem('', size='11pt')
|
||||||
self.layout.addItem(self.titleLabel, 0, 1)
|
self.layout.addItem(self.titleLabel, 0, 1)
|
||||||
|
@ -192,8 +189,7 @@ class PlotItem(GraphicsWidget):
|
||||||
for m in [
|
for m in [
|
||||||
'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible',
|
'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible',
|
||||||
'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled',
|
'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled',
|
||||||
'enableAutoRange', 'disableAutoRange', 'setAspectLocked',
|
'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'invertY',
|
||||||
'setMenuEnabled', 'menuEnabled',
|
|
||||||
'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well.
|
'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well.
|
||||||
setattr(self, m, getattr(self.vb, m))
|
setattr(self, m, getattr(self.vb, m))
|
||||||
|
|
||||||
|
@ -233,45 +229,12 @@ class PlotItem(GraphicsWidget):
|
||||||
self.subMenus.append(sm)
|
self.subMenus.append(sm)
|
||||||
self.ctrlMenu.addMenu(sm)
|
self.ctrlMenu.addMenu(sm)
|
||||||
|
|
||||||
## exporting is handled by GraphicsScene now
|
|
||||||
#exportOpts = collections.OrderedDict([
|
|
||||||
#('SVG - Full Plot', self.saveSvgClicked),
|
|
||||||
#('SVG - Curves Only', self.saveSvgCurvesClicked),
|
|
||||||
#('Image', self.saveImgClicked),
|
|
||||||
#('CSV', self.saveCsvClicked),
|
|
||||||
#])
|
|
||||||
|
|
||||||
#self.vb.menu.setExportMethods(exportOpts)
|
|
||||||
|
|
||||||
|
|
||||||
#if HAVE_WIDGETGROUP:
|
|
||||||
self.stateGroup = WidgetGroup()
|
self.stateGroup = WidgetGroup()
|
||||||
for name, w in menuItems:
|
for name, w in menuItems:
|
||||||
self.stateGroup.autoAdd(w)
|
self.stateGroup.autoAdd(w)
|
||||||
|
|
||||||
self.fileDialog = None
|
self.fileDialog = None
|
||||||
|
|
||||||
#self.xLinkPlot = None
|
|
||||||
#self.yLinkPlot = None
|
|
||||||
#self.linksBlocked = False
|
|
||||||
|
|
||||||
#self.setAcceptHoverEvents(True)
|
|
||||||
|
|
||||||
## Connect control widgets
|
|
||||||
#c.xMinText.editingFinished.connect(self.setManualXScale)
|
|
||||||
#c.xMaxText.editingFinished.connect(self.setManualXScale)
|
|
||||||
#c.yMinText.editingFinished.connect(self.setManualYScale)
|
|
||||||
#c.yMaxText.editingFinished.connect(self.setManualYScale)
|
|
||||||
|
|
||||||
#c.xManualRadio.clicked.connect(lambda: self.updateXScale())
|
|
||||||
#c.yManualRadio.clicked.connect(lambda: self.updateYScale())
|
|
||||||
|
|
||||||
#c.xAutoRadio.clicked.connect(self.updateXScale)
|
|
||||||
#c.yAutoRadio.clicked.connect(self.updateYScale)
|
|
||||||
|
|
||||||
#c.xAutoPercentSpin.valueChanged.connect(self.replot)
|
|
||||||
#c.yAutoPercentSpin.valueChanged.connect(self.replot)
|
|
||||||
|
|
||||||
c.alphaGroup.toggled.connect(self.updateAlpha)
|
c.alphaGroup.toggled.connect(self.updateAlpha)
|
||||||
c.alphaSlider.valueChanged.connect(self.updateAlpha)
|
c.alphaSlider.valueChanged.connect(self.updateAlpha)
|
||||||
c.autoAlphaCheck.toggled.connect(self.updateAlpha)
|
c.autoAlphaCheck.toggled.connect(self.updateAlpha)
|
||||||
|
@ -283,13 +246,6 @@ class PlotItem(GraphicsWidget):
|
||||||
c.fftCheck.toggled.connect(self.updateSpectrumMode)
|
c.fftCheck.toggled.connect(self.updateSpectrumMode)
|
||||||
c.logXCheck.toggled.connect(self.updateLogMode)
|
c.logXCheck.toggled.connect(self.updateLogMode)
|
||||||
c.logYCheck.toggled.connect(self.updateLogMode)
|
c.logYCheck.toggled.connect(self.updateLogMode)
|
||||||
#c.saveSvgBtn.clicked.connect(self.saveSvgClicked)
|
|
||||||
#c.saveSvgCurvesBtn.clicked.connect(self.saveSvgCurvesClicked)
|
|
||||||
#c.saveImgBtn.clicked.connect(self.saveImgClicked)
|
|
||||||
#c.saveCsvBtn.clicked.connect(self.saveCsvClicked)
|
|
||||||
|
|
||||||
#self.ctrl.xLinkCombo.currentIndexChanged.connect(self.xLinkComboChanged)
|
|
||||||
#self.ctrl.yLinkCombo.currentIndexChanged.connect(self.yLinkComboChanged)
|
|
||||||
|
|
||||||
c.downsampleSpin.valueChanged.connect(self.updateDownsampling)
|
c.downsampleSpin.valueChanged.connect(self.updateDownsampling)
|
||||||
|
|
||||||
|
@ -298,24 +254,15 @@ class PlotItem(GraphicsWidget):
|
||||||
|
|
||||||
self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation)
|
self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation)
|
||||||
self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation)
|
self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation)
|
||||||
#c.xMouseCheck.toggled.connect(self.mouseCheckChanged)
|
|
||||||
#c.yMouseCheck.toggled.connect(self.mouseCheckChanged)
|
|
||||||
|
|
||||||
#self.xLinkPlot = None
|
|
||||||
#self.yLinkPlot = None
|
|
||||||
#self.linksBlocked = False
|
|
||||||
self.manager = None
|
|
||||||
|
|
||||||
self.hideAxis('right')
|
self.hideAxis('right')
|
||||||
self.hideAxis('top')
|
self.hideAxis('top')
|
||||||
self.showAxis('left')
|
self.showAxis('left')
|
||||||
self.showAxis('bottom')
|
self.showAxis('bottom')
|
||||||
|
|
||||||
#if name is not None:
|
|
||||||
#self.registerPlot(name)
|
|
||||||
if labels is None:
|
if labels is None:
|
||||||
labels = {}
|
labels = {}
|
||||||
for label in list(self.scales.keys()):
|
for label in list(self.axes.keys()):
|
||||||
if label in kargs:
|
if label in kargs:
|
||||||
labels[label] = kargs[label]
|
labels[label] = kargs[label]
|
||||||
del kargs[label]
|
del kargs[label]
|
||||||
|
@ -330,15 +277,16 @@ class PlotItem(GraphicsWidget):
|
||||||
if len(kargs) > 0:
|
if len(kargs) > 0:
|
||||||
self.plot(**kargs)
|
self.plot(**kargs)
|
||||||
|
|
||||||
#self.enableAutoRange()
|
|
||||||
|
|
||||||
def implements(self, interface=None):
|
def implements(self, interface=None):
|
||||||
return interface in ['ViewBoxWrapper']
|
return interface in ['ViewBoxWrapper']
|
||||||
|
|
||||||
def getViewBox(self):
|
def getViewBox(self):
|
||||||
"""Return the ViewBox within."""
|
"""Return the :class:`ViewBox <pyqtgraph.ViewBox>` contained within."""
|
||||||
return self.vb
|
return self.vb
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def setLogMode(self, x, y):
|
def setLogMode(self, x, y):
|
||||||
"""
|
"""
|
||||||
Set log scaling for x and y axes.
|
Set log scaling for x and y axes.
|
||||||
|
@ -399,11 +347,11 @@ class PlotItem(GraphicsWidget):
|
||||||
#self.autoBtn.setParent(None)
|
#self.autoBtn.setParent(None)
|
||||||
#self.autoBtn = None
|
#self.autoBtn = None
|
||||||
|
|
||||||
for k in self.scales:
|
for k in self.axes:
|
||||||
i = self.scales[k]['item']
|
i = self.axes[k]['item']
|
||||||
i.close()
|
i.close()
|
||||||
|
|
||||||
self.scales = None
|
self.axes = None
|
||||||
self.scene().removeItem(self.vb)
|
self.scene().removeItem(self.vb)
|
||||||
self.vb = None
|
self.vb = None
|
||||||
|
|
||||||
|
@ -431,47 +379,6 @@ class PlotItem(GraphicsWidget):
|
||||||
|
|
||||||
def registerPlot(self, name): ## for backward compatibility
|
def registerPlot(self, name): ## for backward compatibility
|
||||||
self.vb.register(name)
|
self.vb.register(name)
|
||||||
#self.name = name
|
|
||||||
#win = str(self.window())
|
|
||||||
##print "register", name, win
|
|
||||||
#if win not in PlotItem.managers:
|
|
||||||
#PlotItem.managers[win] = PlotWidgetManager()
|
|
||||||
#self.manager = PlotItem.managers[win]
|
|
||||||
#self.manager.addWidget(self, name)
|
|
||||||
##QtCore.QObject.connect(self.manager, QtCore.SIGNAL('widgetListChanged'), self.updatePlotList)
|
|
||||||
#self.manager.sigWidgetListChanged.connect(self.updatePlotList)
|
|
||||||
#self.updatePlotList()
|
|
||||||
|
|
||||||
#def updatePlotList(self):
|
|
||||||
#"""Update the list of all plotWidgets in the "link" combos"""
|
|
||||||
##print "update plot list", self
|
|
||||||
#try:
|
|
||||||
#for sc in [self.ctrl.xLinkCombo, self.ctrl.yLinkCombo]:
|
|
||||||
#current = unicode(sc.currentText())
|
|
||||||
#sc.blockSignals(True)
|
|
||||||
#try:
|
|
||||||
#sc.clear()
|
|
||||||
#sc.addItem("")
|
|
||||||
#if self.manager is not None:
|
|
||||||
#for w in self.manager.listWidgets():
|
|
||||||
##print w
|
|
||||||
#if w == self.name:
|
|
||||||
#continue
|
|
||||||
#sc.addItem(w)
|
|
||||||
#if w == current:
|
|
||||||
#sc.setCurrentIndex(sc.count()-1)
|
|
||||||
#finally:
|
|
||||||
#sc.blockSignals(False)
|
|
||||||
#if unicode(sc.currentText()) != current:
|
|
||||||
#sc.currentItemChanged.emit()
|
|
||||||
#except:
|
|
||||||
#import gc
|
|
||||||
#refs= gc.get_referrers(self)
|
|
||||||
#print " error during update of", self
|
|
||||||
#print " Referrers are:", refs
|
|
||||||
#raise
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def updateGrid(self, *args):
|
def updateGrid(self, *args):
|
||||||
alpha = self.ctrl.gridAlphaSlider.value()
|
alpha = self.ctrl.gridAlphaSlider.value()
|
||||||
|
@ -492,91 +399,6 @@ class PlotItem(GraphicsWidget):
|
||||||
return wr
|
return wr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#def viewRangeChanged(self, vb, range):
|
|
||||||
##self.emit(QtCore.SIGNAL('viewChanged'), *args)
|
|
||||||
#self.sigRangeChanged.emit(self, range)
|
|
||||||
|
|
||||||
#def blockLink(self, b):
|
|
||||||
#self.linksBlocked = b
|
|
||||||
|
|
||||||
#def xLinkComboChanged(self):
|
|
||||||
#self.setXLink(str(self.ctrl.xLinkCombo.currentText()))
|
|
||||||
|
|
||||||
#def yLinkComboChanged(self):
|
|
||||||
#self.setYLink(str(self.ctrl.yLinkCombo.currentText()))
|
|
||||||
|
|
||||||
#def setXLink(self, plot=None):
|
|
||||||
#"""Link this plot's X axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)"""
|
|
||||||
#if isinstance(plot, basestring):
|
|
||||||
#if self.manager is None:
|
|
||||||
#return
|
|
||||||
#if self.xLinkPlot is not None:
|
|
||||||
#self.manager.unlinkX(self, self.xLinkPlot)
|
|
||||||
#plot = self.manager.getWidget(plot)
|
|
||||||
#if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'):
|
|
||||||
#plot = plot.getPlotItem()
|
|
||||||
#self.xLinkPlot = plot
|
|
||||||
#if plot is not None:
|
|
||||||
#self.setManualXScale()
|
|
||||||
#self.manager.linkX(self, plot)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#def setYLink(self, plot=None):
|
|
||||||
#"""Link this plot's Y axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)"""
|
|
||||||
#if isinstance(plot, basestring):
|
|
||||||
#if self.manager is None:
|
|
||||||
#return
|
|
||||||
#if self.yLinkPlot is not None:
|
|
||||||
#self.manager.unlinkY(self, self.yLinkPlot)
|
|
||||||
#plot = self.manager.getWidget(plot)
|
|
||||||
#if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'):
|
|
||||||
#plot = plot.getPlotItem()
|
|
||||||
#self.yLinkPlot = plot
|
|
||||||
#if plot is not None:
|
|
||||||
#self.setManualYScale()
|
|
||||||
#self.manager.linkY(self, plot)
|
|
||||||
|
|
||||||
#def linkXChanged(self, plot):
|
|
||||||
#"""Called when a linked plot has changed its X scale"""
|
|
||||||
##print "update from", plot
|
|
||||||
#if self.linksBlocked:
|
|
||||||
#return
|
|
||||||
#pr = plot.vb.viewRect()
|
|
||||||
#pg = plot.viewGeometry()
|
|
||||||
#if pg is None:
|
|
||||||
##print " return early"
|
|
||||||
#return
|
|
||||||
#sg = self.viewGeometry()
|
|
||||||
#upp = float(pr.width()) / pg.width()
|
|
||||||
#x1 = pr.left() + (sg.x()-pg.x()) * upp
|
|
||||||
#x2 = x1 + sg.width() * upp
|
|
||||||
#plot.blockLink(True)
|
|
||||||
#self.setManualXScale()
|
|
||||||
#self.setXRange(x1, x2, padding=0)
|
|
||||||
#plot.blockLink(False)
|
|
||||||
#self.replot()
|
|
||||||
|
|
||||||
#def linkYChanged(self, plot):
|
|
||||||
#"""Called when a linked plot has changed its Y scale"""
|
|
||||||
#if self.linksBlocked:
|
|
||||||
#return
|
|
||||||
#pr = plot.vb.viewRect()
|
|
||||||
#pg = plot.vb.boundingRect()
|
|
||||||
#sg = self.vb.boundingRect()
|
|
||||||
#upp = float(pr.height()) / pg.height()
|
|
||||||
#y1 = pr.bottom() + (sg.y()-pg.y()) * upp
|
|
||||||
#y2 = y1 + sg.height() * upp
|
|
||||||
#plot.blockLink(True)
|
|
||||||
#self.setManualYScale()
|
|
||||||
#self.setYRange(y1, y2, padding=0)
|
|
||||||
#plot.blockLink(False)
|
|
||||||
#self.replot()
|
|
||||||
|
|
||||||
|
|
||||||
def avgToggled(self, b):
|
def avgToggled(self, b):
|
||||||
if b:
|
if b:
|
||||||
self.recomputeAverages()
|
self.recomputeAverages()
|
||||||
|
@ -650,50 +472,6 @@ class PlotItem(GraphicsWidget):
|
||||||
else:
|
else:
|
||||||
plot.setData(x, y)
|
plot.setData(x, y)
|
||||||
|
|
||||||
|
|
||||||
#def mouseCheckChanged(self):
|
|
||||||
#state = [self.ctrl.xMouseCheck.isChecked(), self.ctrl.yMouseCheck.isChecked()]
|
|
||||||
#self.vb.setMouseEnabled(*state)
|
|
||||||
|
|
||||||
#def xRangeChanged(self, _, range):
|
|
||||||
#if any(np.isnan(range)) or any(np.isinf(range)):
|
|
||||||
#raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender())))
|
|
||||||
#self.ctrl.xMinText.setText('%0.5g' % range[0])
|
|
||||||
#self.ctrl.xMaxText.setText('%0.5g' % range[1])
|
|
||||||
|
|
||||||
### automatically change unit scale
|
|
||||||
#maxVal = max(abs(range[0]), abs(range[1]))
|
|
||||||
#(scale, prefix) = fn.siScale(maxVal)
|
|
||||||
##for l in ['top', 'bottom']:
|
|
||||||
##if self.getLabel(l).isVisible():
|
|
||||||
##self.setLabel(l, unitPrefix=prefix)
|
|
||||||
##self.getScale(l).setScale(scale)
|
|
||||||
##else:
|
|
||||||
##self.setLabel(l, unitPrefix='')
|
|
||||||
##self.getScale(l).setScale(1.0)
|
|
||||||
|
|
||||||
##self.emit(QtCore.SIGNAL('xRangeChanged'), self, range)
|
|
||||||
#self.sigXRangeChanged.emit(self, range)
|
|
||||||
|
|
||||||
#def yRangeChanged(self, _, range):
|
|
||||||
#if any(np.isnan(range)) or any(np.isinf(range)):
|
|
||||||
#raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender())))
|
|
||||||
#self.ctrl.yMinText.setText('%0.5g' % range[0])
|
|
||||||
#self.ctrl.yMaxText.setText('%0.5g' % range[1])
|
|
||||||
|
|
||||||
### automatically change unit scale
|
|
||||||
#maxVal = max(abs(range[0]), abs(range[1]))
|
|
||||||
#(scale, prefix) = fn.siScale(maxVal)
|
|
||||||
##for l in ['left', 'right']:
|
|
||||||
##if self.getLabel(l).isVisible():
|
|
||||||
##self.setLabel(l, unitPrefix=prefix)
|
|
||||||
##self.getScale(l).setScale(scale)
|
|
||||||
##else:
|
|
||||||
##self.setLabel(l, unitPrefix='')
|
|
||||||
##self.getScale(l).setScale(1.0)
|
|
||||||
##self.emit(QtCore.SIGNAL('yRangeChanged'), self, range)
|
|
||||||
#self.sigYRangeChanged.emit(self, range)
|
|
||||||
|
|
||||||
def autoBtnClicked(self):
|
def autoBtnClicked(self):
|
||||||
if self.autoBtn.mode == 'auto':
|
if self.autoBtn.mode == 'auto':
|
||||||
self.enableAutoRange()
|
self.enableAutoRange()
|
||||||
|
@ -706,72 +484,6 @@ class PlotItem(GraphicsWidget):
|
||||||
"""
|
"""
|
||||||
print("Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead.")
|
print("Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead.")
|
||||||
self.vb.enableAutoRange(self.vb.XYAxes)
|
self.vb.enableAutoRange(self.vb.XYAxes)
|
||||||
#self.ctrl.xAutoRadio.setChecked(True)
|
|
||||||
#self.ctrl.yAutoRadio.setChecked(True)
|
|
||||||
|
|
||||||
#self.autoBtn.setImageFile(self.lockImageFile)
|
|
||||||
#self.autoBtn.mode = 'lock'
|
|
||||||
#self.updateXScale()
|
|
||||||
#self.updateYScale()
|
|
||||||
#self.replot()
|
|
||||||
|
|
||||||
#def updateXScale(self):
|
|
||||||
#"""Set plot to autoscale or not depending on state of radio buttons"""
|
|
||||||
#if self.ctrl.xManualRadio.isChecked():
|
|
||||||
#self.setManualXScale()
|
|
||||||
#else:
|
|
||||||
#self.setAutoXScale()
|
|
||||||
#self.replot()
|
|
||||||
|
|
||||||
#def updateYScale(self, b=False):
|
|
||||||
#"""Set plot to autoscale or not depending on state of radio buttons"""
|
|
||||||
#if self.ctrl.yManualRadio.isChecked():
|
|
||||||
#self.setManualYScale()
|
|
||||||
#else:
|
|
||||||
#self.setAutoYScale()
|
|
||||||
#self.replot()
|
|
||||||
|
|
||||||
#def enableManualScale(self, v=[True, True]):
|
|
||||||
#if v[0]:
|
|
||||||
#self.autoScale[0] = False
|
|
||||||
#self.ctrl.xManualRadio.setChecked(True)
|
|
||||||
##self.setManualXScale()
|
|
||||||
#if v[1]:
|
|
||||||
#self.autoScale[1] = False
|
|
||||||
#self.ctrl.yManualRadio.setChecked(True)
|
|
||||||
##self.setManualYScale()
|
|
||||||
##self.autoBtn.enable()
|
|
||||||
#self.autoBtn.setImageFile(self.autoImageFile)
|
|
||||||
#self.autoBtn.mode = 'auto'
|
|
||||||
##self.replot()
|
|
||||||
|
|
||||||
#def setManualXScale(self):
|
|
||||||
#self.autoScale[0] = False
|
|
||||||
#x1 = float(self.ctrl.xMinText.text())
|
|
||||||
#x2 = float(self.ctrl.xMaxText.text())
|
|
||||||
#self.ctrl.xManualRadio.setChecked(True)
|
|
||||||
#self.setXRange(x1, x2, padding=0)
|
|
||||||
#self.autoBtn.show()
|
|
||||||
##self.replot()
|
|
||||||
|
|
||||||
#def setManualYScale(self):
|
|
||||||
#self.autoScale[1] = False
|
|
||||||
#y1 = float(self.ctrl.yMinText.text())
|
|
||||||
#y2 = float(self.ctrl.yMaxText.text())
|
|
||||||
#self.ctrl.yManualRadio.setChecked(True)
|
|
||||||
#self.setYRange(y1, y2, padding=0)
|
|
||||||
#self.autoBtn.show()
|
|
||||||
##self.replot()
|
|
||||||
|
|
||||||
#def setAutoXScale(self):
|
|
||||||
#self.autoScale[0] = True
|
|
||||||
#self.ctrl.xAutoRadio.setChecked(True)
|
|
||||||
##self.replot()
|
|
||||||
|
|
||||||
#def setAutoYScale(self):
|
|
||||||
#self.autoScale[1] = True
|
|
||||||
#self.ctrl.yAutoRadio.setChecked(True)
|
|
||||||
##self.replot()
|
|
||||||
|
|
||||||
def addItem(self, item, *args, **kargs):
|
def addItem(self, item, *args, **kargs):
|
||||||
"""
|
"""
|
||||||
|
@ -867,17 +579,6 @@ class PlotItem(GraphicsWidget):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if y is not None:
|
|
||||||
#data = y
|
|
||||||
#if data2 is not None:
|
|
||||||
#x = data
|
|
||||||
#data = data2
|
|
||||||
#if decimate is not None and decimate > 1:
|
|
||||||
#data = data[::decimate]
|
|
||||||
#if x is not None:
|
|
||||||
#x = x[::decimate]
|
|
||||||
## print 'plot with decimate = %d' % (decimate)
|
|
||||||
clear = kargs.get('clear', False)
|
clear = kargs.get('clear', False)
|
||||||
params = kargs.get('params', None)
|
params = kargs.get('params', None)
|
||||||
|
|
||||||
|
@ -888,23 +589,7 @@ class PlotItem(GraphicsWidget):
|
||||||
|
|
||||||
if params is None:
|
if params is None:
|
||||||
params = {}
|
params = {}
|
||||||
#if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')):
|
|
||||||
#curve = self._plotMetaArray(data, x=x, **kargs)
|
|
||||||
#elif isinstance(data, np.ndarray):
|
|
||||||
#curve = self._plotArray(data, x=x, **kargs)
|
|
||||||
#elif isinstance(data, list):
|
|
||||||
#if x is not None:
|
|
||||||
#x = np.array(x)
|
|
||||||
#curve = self._plotArray(np.array(data), x=x, **kargs)
|
|
||||||
#elif data is None:
|
|
||||||
#curve = PlotCurveItem(**kargs)
|
|
||||||
#else:
|
|
||||||
#raise Exception('Not sure how to plot object of type %s' % type(data))
|
|
||||||
|
|
||||||
#print data, curve
|
|
||||||
self.addItem(item, params=params)
|
self.addItem(item, params=params)
|
||||||
#if pen is not None:
|
|
||||||
#curve.setPen(fn.mkPen(pen))
|
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@ -922,80 +607,34 @@ class PlotItem(GraphicsWidget):
|
||||||
del kargs['size']
|
del kargs['size']
|
||||||
|
|
||||||
return self.plot(*args, **kargs)
|
return self.plot(*args, **kargs)
|
||||||
#sp = ScatterPlotItem(*args, **kargs)
|
|
||||||
#self.addItem(sp)
|
|
||||||
#return sp
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#def plotChanged(self, curve=None):
|
|
||||||
## Recompute auto range if needed
|
|
||||||
#args = {}
|
|
||||||
#for ax in [0, 1]:
|
|
||||||
#print "range", ax
|
|
||||||
#if self.autoScale[ax]:
|
|
||||||
#percentScale = [self.ctrl.xAutoPercentSpin.value(), self.ctrl.yAutoPercentSpin.value()][ax] * 0.01
|
|
||||||
#mn = None
|
|
||||||
#mx = None
|
|
||||||
#for c in self.curves + [c[1] for c in self.avgCurves.values()] + self.dataItems:
|
|
||||||
#if not c.isVisible():
|
|
||||||
#continue
|
|
||||||
#cmn, cmx = c.getRange(ax, percentScale)
|
|
||||||
##print " ", c, cmn, cmx
|
|
||||||
#if mn is None or cmn < mn:
|
|
||||||
#mn = cmn
|
|
||||||
#if mx is None or cmx > mx:
|
|
||||||
#mx = cmx
|
|
||||||
#if mn is None or mx is None or any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])):
|
|
||||||
#continue
|
|
||||||
#if mn == mx:
|
|
||||||
#mn -= 1
|
|
||||||
#mx += 1
|
|
||||||
#if ax == 0:
|
|
||||||
#args['xRange'] = [mn, mx]
|
|
||||||
#else:
|
|
||||||
#args['yRange'] = [mn, mx]
|
|
||||||
|
|
||||||
#if len(args) > 0:
|
|
||||||
##print args
|
|
||||||
#self.setRange(**args)
|
|
||||||
|
|
||||||
def replot(self):
|
def replot(self):
|
||||||
#self.plotChanged()
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def updateParamList(self):
|
def updateParamList(self):
|
||||||
self.ctrl.avgParamList.clear()
|
self.ctrl.avgParamList.clear()
|
||||||
## Check to see that each parameter for each curve is present in the list
|
## Check to see that each parameter for each curve is present in the list
|
||||||
#print "\nUpdate param list", self
|
|
||||||
#print "paramList:", self.paramList
|
|
||||||
for c in self.curves:
|
for c in self.curves:
|
||||||
#print " curve:", c
|
|
||||||
for p in list(self.itemMeta.get(c, {}).keys()):
|
for p in list(self.itemMeta.get(c, {}).keys()):
|
||||||
#print " param:", p
|
|
||||||
if type(p) is tuple:
|
if type(p) is tuple:
|
||||||
p = '.'.join(p)
|
p = '.'.join(p)
|
||||||
|
|
||||||
## If the parameter is not in the list, add it.
|
## If the parameter is not in the list, add it.
|
||||||
matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly)
|
matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly)
|
||||||
#print " matches:", matches
|
|
||||||
if len(matches) == 0:
|
if len(matches) == 0:
|
||||||
i = QtGui.QListWidgetItem(p)
|
i = QtGui.QListWidgetItem(p)
|
||||||
if p in self.paramList and self.paramList[p] is True:
|
if p in self.paramList and self.paramList[p] is True:
|
||||||
#print " set checked"
|
|
||||||
i.setCheckState(QtCore.Qt.Checked)
|
i.setCheckState(QtCore.Qt.Checked)
|
||||||
else:
|
else:
|
||||||
#print " set unchecked"
|
|
||||||
i.setCheckState(QtCore.Qt.Unchecked)
|
i.setCheckState(QtCore.Qt.Unchecked)
|
||||||
self.ctrl.avgParamList.addItem(i)
|
self.ctrl.avgParamList.addItem(i)
|
||||||
else:
|
else:
|
||||||
i = matches[0]
|
i = matches[0]
|
||||||
|
|
||||||
self.paramList[p] = (i.checkState() == QtCore.Qt.Checked)
|
self.paramList[p] = (i.checkState() == QtCore.Qt.Checked)
|
||||||
#print "paramList:", self.paramList
|
|
||||||
|
|
||||||
|
|
||||||
## This is bullshit.
|
## Qt's SVG-writing capabilities are pretty terrible.
|
||||||
def writeSvgCurves(self, fileName=None):
|
def writeSvgCurves(self, fileName=None):
|
||||||
if fileName is None:
|
if fileName is None:
|
||||||
self.fileDialog = FileDialog()
|
self.fileDialog = FileDialog()
|
||||||
|
@ -1190,18 +829,12 @@ class PlotItem(GraphicsWidget):
|
||||||
|
|
||||||
|
|
||||||
def saveState(self):
|
def saveState(self):
|
||||||
#if not HAVE_WIDGETGROUP:
|
|
||||||
#raise Exception("State save/restore requires WidgetGroup class.")
|
|
||||||
state = self.stateGroup.state()
|
state = self.stateGroup.state()
|
||||||
state['paramList'] = self.paramList.copy()
|
state['paramList'] = self.paramList.copy()
|
||||||
state['view'] = self.vb.getState()
|
state['view'] = self.vb.getState()
|
||||||
#print "\nSAVE %s:\n" % str(self.name), state
|
|
||||||
#print "Saving state. averageGroup.isChecked(): %s state: %s" % (str(self.ctrl.averageGroup.isChecked()), str(state['averageGroup']))
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def restoreState(self, state):
|
def restoreState(self, state):
|
||||||
#if not HAVE_WIDGETGROUP:
|
|
||||||
#raise Exception("State save/restore requires WidgetGroup class.")
|
|
||||||
if 'paramList' in state:
|
if 'paramList' in state:
|
||||||
self.paramList = state['paramList'].copy()
|
self.paramList = state['paramList'].copy()
|
||||||
|
|
||||||
|
@ -1218,8 +851,6 @@ class PlotItem(GraphicsWidget):
|
||||||
state['yGridCheck'] = state['gridGroup']
|
state['yGridCheck'] = state['gridGroup']
|
||||||
|
|
||||||
self.stateGroup.setState(state)
|
self.stateGroup.setState(state)
|
||||||
#self.updateXScale()
|
|
||||||
#self.updateYScale()
|
|
||||||
self.updateParamList()
|
self.updateParamList()
|
||||||
|
|
||||||
if 'view' not in state:
|
if 'view' not in state:
|
||||||
|
@ -1232,13 +863,6 @@ class PlotItem(GraphicsWidget):
|
||||||
}
|
}
|
||||||
self.vb.setState(state['view'])
|
self.vb.setState(state['view'])
|
||||||
|
|
||||||
|
|
||||||
#print "\nRESTORE %s:\n" % str(self.name), state
|
|
||||||
#print "Restoring state. averageGroup.isChecked(): %s state: %s" % (str(self.ctrl.averageGroup.isChecked()), str(state['averageGroup']))
|
|
||||||
#avg = self.ctrl.averageGroup.isChecked()
|
|
||||||
#if avg != state['averageGroup']:
|
|
||||||
#print " WARNING: avgGroup is %s, should be %s" % (str(avg), str(state['averageGroup']))
|
|
||||||
|
|
||||||
|
|
||||||
def widgetGroupInterface(self):
|
def widgetGroupInterface(self):
|
||||||
return (None, PlotItem.saveState, PlotItem.restoreState)
|
return (None, PlotItem.saveState, PlotItem.restoreState)
|
||||||
|
@ -1269,8 +893,6 @@ class PlotItem(GraphicsWidget):
|
||||||
for c in self.curves:
|
for c in self.curves:
|
||||||
c.setDownsampling(ds)
|
c.setDownsampling(ds)
|
||||||
self.recomputeAverages()
|
self.recomputeAverages()
|
||||||
#for c in self.avgCurves.values():
|
|
||||||
#c[1].setDownsampling(ds)
|
|
||||||
|
|
||||||
|
|
||||||
def downsampleMode(self):
|
def downsampleMode(self):
|
||||||
|
@ -1306,8 +928,6 @@ class PlotItem(GraphicsWidget):
|
||||||
(alpha, auto) = self.alphaState()
|
(alpha, auto) = self.alphaState()
|
||||||
for c in self.curves:
|
for c in self.curves:
|
||||||
c.setAlpha(alpha**2, auto)
|
c.setAlpha(alpha**2, auto)
|
||||||
|
|
||||||
#self.replot(autoRange=False)
|
|
||||||
|
|
||||||
def alphaState(self):
|
def alphaState(self):
|
||||||
enabled = self.ctrl.alphaGroup.isChecked()
|
enabled = self.ctrl.alphaGroup.isChecked()
|
||||||
|
@ -1330,9 +950,6 @@ class PlotItem(GraphicsWidget):
|
||||||
mode = False
|
mode = False
|
||||||
return mode
|
return mode
|
||||||
|
|
||||||
#def wheelEvent(self, ev):
|
|
||||||
## disables default panning the whole scene by mousewheel
|
|
||||||
#ev.accept()
|
|
||||||
|
|
||||||
def resizeEvent(self, ev):
|
def resizeEvent(self, ev):
|
||||||
if self.autoBtn is None: ## already closed down
|
if self.autoBtn is None: ## already closed down
|
||||||
|
@ -1340,29 +957,42 @@ class PlotItem(GraphicsWidget):
|
||||||
btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect())
|
btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect())
|
||||||
y = self.size().height() - btnRect.height()
|
y = self.size().height() - btnRect.height()
|
||||||
self.autoBtn.setPos(0, y)
|
self.autoBtn.setPos(0, y)
|
||||||
|
|
||||||
#def hoverMoveEvent(self, ev):
|
|
||||||
#self.mousePos = ev.pos()
|
|
||||||
#self.mouseScreenPos = ev.screenPos()
|
|
||||||
|
|
||||||
|
|
||||||
#def ctrlBtnClicked(self):
|
|
||||||
#self.ctrlMenu.popup(self.mouseScreenPos)
|
|
||||||
|
|
||||||
def getMenu(self):
|
def getMenu(self):
|
||||||
return self.ctrlMenu
|
return self.ctrlMenu
|
||||||
|
|
||||||
def getContextMenus(self, event):
|
def getContextMenus(self, event):
|
||||||
## called when another item is displaying its context menu; we get to add extras to the end of the menu.
|
## called when another item is displaying its context menu; we get to add extras to the end of the menu.
|
||||||
return self.ctrlMenu
|
if self.menuEnabled():
|
||||||
|
return self.ctrlMenu
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def setMenuEnabled(self, enableMenu=True, enableViewBoxMenu='same'):
|
||||||
|
"""
|
||||||
|
Enable or disable the context menu for this PlotItem.
|
||||||
|
By default, the ViewBox's context menu will also be affected.
|
||||||
|
(use enableViewBoxMenu=None to leave the ViewBox unchanged)
|
||||||
|
"""
|
||||||
|
self._menuEnabled = enableMenu
|
||||||
|
if enableViewBoxMenu is None:
|
||||||
|
return
|
||||||
|
if enableViewBoxMenu is 'same':
|
||||||
|
enableViewBoxMenu = enableMenu
|
||||||
|
self.vb.setMenuEnabled(enableViewBoxMenu)
|
||||||
|
|
||||||
|
def menuEnabled(self):
|
||||||
|
return self._menuEnabled
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getLabel(self, key):
|
def getLabel(self, key):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _checkScaleKey(self, key):
|
def _checkScaleKey(self, key):
|
||||||
if key not in self.scales:
|
if key not in self.axes:
|
||||||
raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.scales.keys()))))
|
raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.axes.keys()))))
|
||||||
|
|
||||||
def getScale(self, key):
|
def getScale(self, key):
|
||||||
return self.getAxis(key)
|
return self.getAxis(key)
|
||||||
|
@ -1371,7 +1001,7 @@ class PlotItem(GraphicsWidget):
|
||||||
"""Return the specified AxisItem.
|
"""Return the specified AxisItem.
|
||||||
*name* should be 'left', 'bottom', 'top', or 'right'."""
|
*name* should be 'left', 'bottom', 'top', or 'right'."""
|
||||||
self._checkScaleKey(name)
|
self._checkScaleKey(name)
|
||||||
return self.scales[name]['item']
|
return self.axes[name]['item']
|
||||||
|
|
||||||
def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args):
|
def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args):
|
||||||
"""
|
"""
|
||||||
|
@ -1417,13 +1047,14 @@ class PlotItem(GraphicsWidget):
|
||||||
axis must be one of 'left', 'bottom', 'right', or 'top'
|
axis must be one of 'left', 'bottom', 'right', or 'top'
|
||||||
"""
|
"""
|
||||||
s = self.getScale(axis)
|
s = self.getScale(axis)
|
||||||
p = self.scales[axis]['pos']
|
p = self.axes[axis]['pos']
|
||||||
if show:
|
if show:
|
||||||
s.show()
|
s.show()
|
||||||
else:
|
else:
|
||||||
s.hide()
|
s.hide()
|
||||||
|
|
||||||
def hideAxis(self, axis):
|
def hideAxis(self, axis):
|
||||||
|
"""Hide one of the PlotItem's axes. ('left', 'bottom', 'right', or 'top')"""
|
||||||
self.showAxis(axis, False)
|
self.showAxis(axis, False)
|
||||||
|
|
||||||
def showScale(self, *args, **kargs):
|
def showScale(self, *args, **kargs):
|
||||||
|
@ -1431,6 +1062,7 @@ class PlotItem(GraphicsWidget):
|
||||||
return self.showAxis(*args, **kargs)
|
return self.showAxis(*args, **kargs)
|
||||||
|
|
||||||
def hideButtons(self):
|
def hideButtons(self):
|
||||||
|
"""Causes auto-scale button ('A' in lower-left corner) to be hidden for this PlotItem"""
|
||||||
#self.ctrlBtn.hide()
|
#self.ctrlBtn.hide()
|
||||||
self.autoBtn.hide()
|
self.autoBtn.hide()
|
||||||
|
|
||||||
|
@ -1454,7 +1086,6 @@ class PlotItem(GraphicsWidget):
|
||||||
## create curve
|
## create curve
|
||||||
try:
|
try:
|
||||||
xv = arr.xvals(0)
|
xv = arr.xvals(0)
|
||||||
#print 'xvals:', xv
|
|
||||||
except:
|
except:
|
||||||
if x is None:
|
if x is None:
|
||||||
xv = np.arange(arr.shape[0])
|
xv = np.arange(arr.shape[0])
|
||||||
|
@ -1474,17 +1105,6 @@ class PlotItem(GraphicsWidget):
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
|
||||||
#def saveSvgClicked(self):
|
|
||||||
#self.writeSvg()
|
|
||||||
|
|
||||||
#def saveSvgCurvesClicked(self):
|
|
||||||
#self.writeSvgCurves()
|
|
||||||
|
|
||||||
#def saveImgClicked(self):
|
|
||||||
#self.writeImage()
|
|
||||||
|
|
||||||
#def saveCsvClicked(self):
|
|
||||||
#self.writeCsv()
|
|
||||||
|
|
||||||
def setExportMode(self, export, opts):
|
def setExportMode(self, export, opts):
|
||||||
if export:
|
if export:
|
||||||
|
@ -1492,63 +1112,3 @@ class PlotItem(GraphicsWidget):
|
||||||
else:
|
else:
|
||||||
self.autoBtn.show()
|
self.autoBtn.show()
|
||||||
|
|
||||||
|
|
||||||
#class PlotWidgetManager(QtCore.QObject):
|
|
||||||
|
|
||||||
#sigWidgetListChanged = QtCore.Signal(object)
|
|
||||||
|
|
||||||
#"""Used for managing communication between PlotWidgets"""
|
|
||||||
#def __init__(self):
|
|
||||||
#QtCore.QObject.__init__(self)
|
|
||||||
#self.widgets = weakref.WeakValueDictionary() # Don't keep PlotWidgets around just because they are listed here
|
|
||||||
|
|
||||||
#def addWidget(self, w, name):
|
|
||||||
#self.widgets[name] = w
|
|
||||||
##self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys())
|
|
||||||
#self.sigWidgetListChanged.emit(self.widgets.keys())
|
|
||||||
|
|
||||||
#def removeWidget(self, name):
|
|
||||||
#if name in self.widgets:
|
|
||||||
#del self.widgets[name]
|
|
||||||
##self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys())
|
|
||||||
#self.sigWidgetListChanged.emit(self.widgets.keys())
|
|
||||||
#else:
|
|
||||||
#print "plot %s not managed" % name
|
|
||||||
|
|
||||||
|
|
||||||
#def listWidgets(self):
|
|
||||||
#return self.widgets.keys()
|
|
||||||
|
|
||||||
#def getWidget(self, name):
|
|
||||||
#if name not in self.widgets:
|
|
||||||
#return None
|
|
||||||
#else:
|
|
||||||
#return self.widgets[name]
|
|
||||||
|
|
||||||
#def linkX(self, p1, p2):
|
|
||||||
##QtCore.QObject.connect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
|
|
||||||
#p1.sigXRangeChanged.connect(p2.linkXChanged)
|
|
||||||
##QtCore.QObject.connect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged)
|
|
||||||
#p2.sigXRangeChanged.connect(p1.linkXChanged)
|
|
||||||
#p1.linkXChanged(p2)
|
|
||||||
##p2.setManualXScale()
|
|
||||||
|
|
||||||
#def unlinkX(self, p1, p2):
|
|
||||||
##QtCore.QObject.disconnect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
|
|
||||||
#p1.sigXRangeChanged.disconnect(p2.linkXChanged)
|
|
||||||
##QtCore.QObject.disconnect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged)
|
|
||||||
#p2.sigXRangeChanged.disconnect(p1.linkXChanged)
|
|
||||||
|
|
||||||
#def linkY(self, p1, p2):
|
|
||||||
##QtCore.QObject.connect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
|
|
||||||
#p1.sigYRangeChanged.connect(p2.linkYChanged)
|
|
||||||
##QtCore.QObject.connect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged)
|
|
||||||
#p2.sigYRangeChanged.connect(p1.linkYChanged)
|
|
||||||
#p1.linkYChanged(p2)
|
|
||||||
##p2.setManualYScale()
|
|
||||||
|
|
||||||
#def unlinkY(self, p1, p2):
|
|
||||||
##QtCore.QObject.disconnect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
|
|
||||||
#p1.sigYRangeChanged.disconnect(p2.linkYChanged)
|
|
||||||
##QtCore.QObject.disconnect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged)
|
|
||||||
#p2.sigYRangeChanged.disconnect(p1.linkYChanged)
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ class Ui_Form(object):
|
||||||
self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2)
|
self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2)
|
||||||
self.gridAlphaSlider = QtGui.QSlider(self.gridGroup)
|
self.gridAlphaSlider = QtGui.QSlider(self.gridGroup)
|
||||||
self.gridAlphaSlider.setMaximum(255)
|
self.gridAlphaSlider.setMaximum(255)
|
||||||
self.gridAlphaSlider.setProperty("value", 70)
|
self.gridAlphaSlider.setProperty("value", 128)
|
||||||
self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal)
|
self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal)
|
||||||
self.gridAlphaSlider.setObjectName(_fromUtf8("gridAlphaSlider"))
|
self.gridAlphaSlider.setObjectName(_fromUtf8("gridAlphaSlider"))
|
||||||
self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1)
|
self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1)
|
||||||
|
|
|
@ -221,7 +221,7 @@
|
||||||
<number>255</number>
|
<number>255</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<number>70</number>
|
<number>128</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
|
|
|
@ -800,7 +800,7 @@ class ROI(GraphicsObject):
|
||||||
#print " dshape", dShape
|
#print " dshape", dShape
|
||||||
|
|
||||||
## Determine transform that maps ROI bounding box to image coordinates
|
## Determine transform that maps ROI bounding box to image coordinates
|
||||||
tr = self.sceneTransform() * img.sceneTransform().inverted()[0]
|
tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform())
|
||||||
|
|
||||||
## Modify transform to scale from image coords to data coords
|
## Modify transform to scale from image coords to data coords
|
||||||
#m = QtGui.QTransform()
|
#m = QtGui.QTransform()
|
||||||
|
@ -832,35 +832,34 @@ class ROI(GraphicsObject):
|
||||||
else:
|
else:
|
||||||
return bounds, tr
|
return bounds, tr
|
||||||
|
|
||||||
|
def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds):
|
||||||
def getArrayRegion(self, data, img, axes=(0,1)):
|
"""Use the position and orientation of this ROI relative to an imageItem to pull a slice from an array.
|
||||||
"""Use the position of this ROI relative to an imageItem to pull a slice from an array."""
|
|
||||||
|
|
||||||
|
This method uses :func:`affineSlice <pyqtgraph.affineSlice>` to generate
|
||||||
|
the slice from *data* and uses :func:`getAffineSliceParams <pyqtgraph.ROI.getAffineSliceParams>` to determine the parameters to
|
||||||
|
pass to :func:`affineSlice <pyqtgraph.affineSlice>`.
|
||||||
|
|
||||||
shape = self.state['size']
|
If *returnMappedCoords* is True, then the method returns a tuple (result, coords)
|
||||||
|
such that coords is the set of coordinates used to interpolate values from the original
|
||||||
|
data, mapped into the parent coordinate system of the image. This is useful, when slicing
|
||||||
|
data from images that have been transformed, for determining the location of each value
|
||||||
|
in the sliced data.
|
||||||
|
|
||||||
origin = self.mapToItem(img, QtCore.QPointF(0, 0))
|
All extra keyword arguments are passed to :func:`affineSlice <pyqtgraph.affineSlice>`.
|
||||||
|
"""
|
||||||
## vx and vy point in the directions of the slice axes, but must be scaled properly
|
|
||||||
vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin
|
|
||||||
vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin
|
|
||||||
|
|
||||||
lvx = np.sqrt(vx.x()**2 + vx.y()**2)
|
|
||||||
lvy = np.sqrt(vy.x()**2 + vy.y()**2)
|
|
||||||
pxLen = img.width() / float(data.shape[axes[0]])
|
|
||||||
sx = pxLen / lvx
|
|
||||||
sy = pxLen / lvy
|
|
||||||
|
|
||||||
vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy))
|
|
||||||
shape = self.state['size']
|
|
||||||
shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
|
|
||||||
|
|
||||||
origin = (origin.x(), origin.y())
|
|
||||||
|
|
||||||
#print "shape", shape, "vectors", vectors, "origin", origin
|
|
||||||
|
|
||||||
return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, order=1)
|
|
||||||
|
|
||||||
|
shape, vectors, origin = self.getAffineSliceParams(data, img, axes)
|
||||||
|
if not returnMappedCoords:
|
||||||
|
return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
|
||||||
|
else:
|
||||||
|
kwds['returnCoords'] = True
|
||||||
|
result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds)
|
||||||
|
tr = fn.transformToArray(img.transform())[:,:2].reshape((3, 2) + (1,)*(coords.ndim-1))
|
||||||
|
coords = coords[np.newaxis, ...]
|
||||||
|
mapped = (tr*coords).sum(axis=0)
|
||||||
|
return result, mapped
|
||||||
|
|
||||||
|
|
||||||
### transpose data so x and y are the first 2 axes
|
### transpose data so x and y are the first 2 axes
|
||||||
#trAx = range(0, data.ndim)
|
#trAx = range(0, data.ndim)
|
||||||
#trAx.remove(axes[0])
|
#trAx.remove(axes[0])
|
||||||
|
@ -959,6 +958,37 @@ class ROI(GraphicsObject):
|
||||||
### Untranspose array before returning
|
### Untranspose array before returning
|
||||||
#return arr5.transpose(tr2)
|
#return arr5.transpose(tr2)
|
||||||
|
|
||||||
|
def getAffineSliceParams(self, data, img, axes=(0.1)):
|
||||||
|
"""
|
||||||
|
Returns the parameters needed to use :func:`affineSlice <pyqtgraph.affineSlice>` to
|
||||||
|
extract a subset of *data* using this ROI and *img* to specify the subset.
|
||||||
|
|
||||||
|
See :func:`getArrayRegion <pyqtgraph.ROI.getArrayRegion>` for more information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
shape = self.state['size']
|
||||||
|
|
||||||
|
origin = self.mapToItem(img, QtCore.QPointF(0, 0))
|
||||||
|
|
||||||
|
## vx and vy point in the directions of the slice axes, but must be scaled properly
|
||||||
|
vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin
|
||||||
|
vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin
|
||||||
|
|
||||||
|
lvx = np.sqrt(vx.x()**2 + vx.y()**2)
|
||||||
|
lvy = np.sqrt(vy.x()**2 + vy.y()**2)
|
||||||
|
pxLen = img.width() / float(data.shape[axes[0]])
|
||||||
|
#img.width is number of pixels or width of item?
|
||||||
|
#need pxWidth and pxHeight instead of pxLen ?
|
||||||
|
sx = pxLen / lvx
|
||||||
|
sy = pxLen / lvy
|
||||||
|
|
||||||
|
vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy))
|
||||||
|
shape = self.state['size']
|
||||||
|
shape = [abs(shape[0]/sx), abs(shape[1]/sy)]
|
||||||
|
|
||||||
|
origin = (origin.x(), origin.y())
|
||||||
|
return shape, vectors, origin
|
||||||
|
|
||||||
def getGlobalTransform(self, relativeTo=None):
|
def getGlobalTransform(self, relativeTo=None):
|
||||||
"""Return global transformation (rotation angle+translation) required to move
|
"""Return global transformation (rotation angle+translation) required to move
|
||||||
from relative state to current state. If relative state isn't specified,
|
from relative state to current state. If relative state isn't specified,
|
||||||
|
@ -1251,7 +1281,7 @@ class Handle(UIGraphicsItem):
|
||||||
v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0))
|
v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0))
|
||||||
va = np.arctan2(v.y(), v.x())
|
va = np.arctan2(v.y(), v.x())
|
||||||
|
|
||||||
dti = dt.inverted()[0]
|
dti = fn.invertQTransform(dt)
|
||||||
devPos = dt.map(QtCore.QPointF(0,0))
|
devPos = dt.map(QtCore.QPointF(0,0))
|
||||||
tr = QtGui.QTransform()
|
tr = QtGui.QTransform()
|
||||||
tr.translate(devPos.x(), devPos.y())
|
tr.translate(devPos.x(), devPos.y())
|
||||||
|
|
|
@ -79,7 +79,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||||
prof = debug.Profiler('ScatterPlotItem.__init__', disabled=True)
|
prof = debug.Profiler('ScatterPlotItem.__init__', disabled=True)
|
||||||
GraphicsObject.__init__(self)
|
GraphicsObject.__init__(self)
|
||||||
self.setFlag(self.ItemHasNoContents, True)
|
self.setFlag(self.ItemHasNoContents, True)
|
||||||
self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', 'S1'), ('pen', object), ('brush', object), ('item', object), ('data', object)])
|
self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('item', object), ('data', object)])
|
||||||
self.bounds = [None, None] ## caches data bounds
|
self.bounds = [None, None] ## caches data bounds
|
||||||
self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots
|
self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots
|
||||||
self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots
|
self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots
|
||||||
|
@ -226,6 +226,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||||
self.setPointData(kargs['data'], dataSet=newData)
|
self.setPointData(kargs['data'], dataSet=newData)
|
||||||
|
|
||||||
#self.updateSpots()
|
#self.updateSpots()
|
||||||
|
self.prepareGeometryChange()
|
||||||
self.bounds = [None, None]
|
self.bounds = [None, None]
|
||||||
self.generateSpotItems()
|
self.generateSpotItems()
|
||||||
self.sigPlotChanged.emit(self)
|
self.sigPlotChanged.emit(self)
|
||||||
|
@ -396,7 +397,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||||
if frac >= 1.0 and self.bounds[ax] is not None:
|
if frac >= 1.0 and self.bounds[ax] is not None:
|
||||||
return self.bounds[ax]
|
return self.bounds[ax]
|
||||||
|
|
||||||
self.prepareGeometryChange()
|
#self.prepareGeometryChange()
|
||||||
if self.data is None or len(self.data) == 0:
|
if self.data is None or len(self.data) == 0:
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
||||||
|
@ -464,6 +465,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||||
return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
|
return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
|
||||||
|
|
||||||
def viewRangeChanged(self):
|
def viewRangeChanged(self):
|
||||||
|
self.prepareGeometryChange()
|
||||||
GraphicsObject.viewRangeChanged(self)
|
GraphicsObject.viewRangeChanged(self)
|
||||||
self.bounds = [None, None]
|
self.bounds = [None, None]
|
||||||
|
|
||||||
|
@ -557,7 +559,7 @@ class SpotItem(GraphicsItem):
|
||||||
If the spot has no explicit symbol set, then return the ScatterPlotItem's default symbol instead.
|
If the spot has no explicit symbol set, then return the ScatterPlotItem's default symbol instead.
|
||||||
"""
|
"""
|
||||||
symbol = self._data['symbol']
|
symbol = self._data['symbol']
|
||||||
if symbol == '':
|
if symbol is None:
|
||||||
symbol = self._plot.opts['symbol']
|
symbol = self._plot.opts['symbol']
|
||||||
try:
|
try:
|
||||||
n = int(symbol)
|
n = int(symbol)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from .UIGraphicsItem import *
|
from .UIGraphicsItem import *
|
||||||
|
import pyqtgraph.functions as fn
|
||||||
|
|
||||||
class TextItem(UIGraphicsItem):
|
class TextItem(UIGraphicsItem):
|
||||||
"""
|
"""
|
||||||
|
@ -87,7 +88,7 @@ class TextItem(UIGraphicsItem):
|
||||||
if br is None:
|
if br is None:
|
||||||
return
|
return
|
||||||
self.prepareGeometryChange()
|
self.prepareGeometryChange()
|
||||||
self._bounds = self.deviceTransform().inverted()[0].mapRect(br)
|
self._bounds = fn.invertQTransform(self.deviceTransform()).mapRect(br)
|
||||||
#print self._bounds
|
#print self._bounds
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
from pyqtgraph.python2_3 import sortList
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from pyqtgraph.Point import Point
|
from pyqtgraph.Point import Point
|
||||||
import pyqtgraph.functions as fn
|
import pyqtgraph.functions as fn
|
||||||
|
@ -62,7 +63,7 @@ class ViewBox(GraphicsWidget):
|
||||||
NamedViews = weakref.WeakValueDictionary() # name: ViewBox
|
NamedViews = weakref.WeakValueDictionary() # name: ViewBox
|
||||||
AllViews = weakref.WeakKeyDictionary() # ViewBox: None
|
AllViews = weakref.WeakKeyDictionary() # ViewBox: None
|
||||||
|
|
||||||
def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu = True, name=None):
|
def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True, name=None):
|
||||||
"""
|
"""
|
||||||
============= =============================================================
|
============= =============================================================
|
||||||
**Arguments**
|
**Arguments**
|
||||||
|
@ -105,6 +106,8 @@ class ViewBox(GraphicsWidget):
|
||||||
'mouseMode': ViewBox.PanMode if pyqtgraph.getConfigOption('leftButtonPan') else ViewBox.RectMode,
|
'mouseMode': ViewBox.PanMode if pyqtgraph.getConfigOption('leftButtonPan') else ViewBox.RectMode,
|
||||||
'enableMenu': enableMenu,
|
'enableMenu': enableMenu,
|
||||||
'wheelScaleFactor': -1.0 / 8.0,
|
'wheelScaleFactor': -1.0 / 8.0,
|
||||||
|
|
||||||
|
'background': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,17 +121,23 @@ class ViewBox(GraphicsWidget):
|
||||||
self.setFlag(self.ItemIsFocusable, True) ## so we can receive key presses
|
self.setFlag(self.ItemIsFocusable, True) ## so we can receive key presses
|
||||||
|
|
||||||
## childGroup is required so that ViewBox has local coordinates similar to device coordinates.
|
## childGroup is required so that ViewBox has local coordinates similar to device coordinates.
|
||||||
## this is a workaround for a Qt + OpenGL but that causes improper clipping
|
## this is a workaround for a Qt + OpenGL bug that causes improper clipping
|
||||||
## https://bugreports.qt.nokia.com/browse/QTBUG-23723
|
## https://bugreports.qt.nokia.com/browse/QTBUG-23723
|
||||||
self.childGroup = ChildGroup(self)
|
self.childGroup = ChildGroup(self)
|
||||||
self.childGroup.sigItemsChanged.connect(self.itemsChanged)
|
self.childGroup.sigItemsChanged.connect(self.itemsChanged)
|
||||||
|
|
||||||
|
self.background = QtGui.QGraphicsRectItem(self.rect())
|
||||||
|
self.background.setParentItem(self)
|
||||||
|
self.background.setZValue(-1e6)
|
||||||
|
self.background.setPen(fn.mkPen(None))
|
||||||
|
self.updateBackground()
|
||||||
|
|
||||||
#self.useLeftButtonPan = pyqtgraph.getConfigOption('leftButtonPan') # normally use left button to pan
|
#self.useLeftButtonPan = pyqtgraph.getConfigOption('leftButtonPan') # normally use left button to pan
|
||||||
# this also enables capture of keyPressEvents.
|
# this also enables capture of keyPressEvents.
|
||||||
|
|
||||||
## Make scale box that is shown when dragging on the view
|
## Make scale box that is shown when dragging on the view
|
||||||
self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1)
|
self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1)
|
||||||
self.rbScaleBox.setPen(fn.mkPen((255,0,0), width=1))
|
self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1))
|
||||||
self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100))
|
self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100))
|
||||||
self.rbScaleBox.hide()
|
self.rbScaleBox.hide()
|
||||||
self.addItem(self.rbScaleBox)
|
self.addItem(self.rbScaleBox)
|
||||||
|
@ -286,6 +295,7 @@ class ViewBox(GraphicsWidget):
|
||||||
#self.updateAutoRange()
|
#self.updateAutoRange()
|
||||||
self.updateMatrix()
|
self.updateMatrix()
|
||||||
self.sigStateChanged.emit(self)
|
self.sigStateChanged.emit(self)
|
||||||
|
self.background.setRect(self.rect())
|
||||||
#self.linkedXChanged()
|
#self.linkedXChanged()
|
||||||
#self.linkedYChanged()
|
#self.linkedYChanged()
|
||||||
|
|
||||||
|
@ -349,7 +359,7 @@ class ViewBox(GraphicsWidget):
|
||||||
changes[1] = yRange
|
changes[1] = yRange
|
||||||
|
|
||||||
if len(changes) == 0:
|
if len(changes) == 0:
|
||||||
print rect
|
print(rect)
|
||||||
raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect)))
|
raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect)))
|
||||||
|
|
||||||
changed = [False, False]
|
changed = [False, False]
|
||||||
|
@ -442,10 +452,8 @@ class ViewBox(GraphicsWidget):
|
||||||
center = Point(vr.center())
|
center = Point(vr.center())
|
||||||
else:
|
else:
|
||||||
center = Point(center)
|
center = Point(center)
|
||||||
|
|
||||||
tl = center + (vr.topLeft()-center) * scale
|
tl = center + (vr.topLeft()-center) * scale
|
||||||
br = center + (vr.bottomRight()-center) * scale
|
br = center + (vr.bottomRight()-center) * scale
|
||||||
|
|
||||||
self.setRange(QtCore.QRectF(tl, br), padding=0)
|
self.setRange(QtCore.QRectF(tl, br), padding=0)
|
||||||
|
|
||||||
def translateBy(self, t):
|
def translateBy(self, t):
|
||||||
|
@ -755,7 +763,7 @@ class ViewBox(GraphicsWidget):
|
||||||
|
|
||||||
def mapToView(self, obj):
|
def mapToView(self, obj):
|
||||||
"""Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox"""
|
"""Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox"""
|
||||||
m = self.childTransform().inverted()[0]
|
m = fn.invertQTransform(self.childTransform())
|
||||||
return m.map(obj)
|
return m.map(obj)
|
||||||
|
|
||||||
def mapFromView(self, obj):
|
def mapFromView(self, obj):
|
||||||
|
@ -821,7 +829,7 @@ class ViewBox(GraphicsWidget):
|
||||||
mask[axis] = mv
|
mask[axis] = mv
|
||||||
s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor
|
s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor
|
||||||
|
|
||||||
center = Point(self.childGroup.transform().inverted()[0].map(ev.pos()))
|
center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos()))
|
||||||
#center = ev.pos()
|
#center = ev.pos()
|
||||||
|
|
||||||
self.scaleBy(s, center)
|
self.scaleBy(s, center)
|
||||||
|
@ -854,7 +862,10 @@ class ViewBox(GraphicsWidget):
|
||||||
return self._menuCopy
|
return self._menuCopy
|
||||||
|
|
||||||
def getContextMenus(self, event):
|
def getContextMenus(self, event):
|
||||||
return self.menu.subMenus()
|
if self.menuEnabled():
|
||||||
|
return self.menu.subMenus()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
#return [self.getMenu(event)]
|
#return [self.getMenu(event)]
|
||||||
|
|
||||||
|
|
||||||
|
@ -901,8 +912,11 @@ class ViewBox(GraphicsWidget):
|
||||||
dif = np.array([dif.x(), dif.y()])
|
dif = np.array([dif.x(), dif.y()])
|
||||||
dif[0] *= -1
|
dif[0] *= -1
|
||||||
s = ((mask * 0.02) + 1) ** dif
|
s = ((mask * 0.02) + 1) ** dif
|
||||||
center = Point(self.childGroup.transform().inverted()[0].map(ev.buttonDownPos(QtCore.Qt.RightButton)))
|
|
||||||
#center = Point(ev.buttonDownPos(QtCore.Qt.RightButton))
|
tr = self.childGroup.transform()
|
||||||
|
tr = fn.invertQTransform(tr)
|
||||||
|
|
||||||
|
center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton)))
|
||||||
self.scaleBy(s, center)
|
self.scaleBy(s, center)
|
||||||
self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
|
self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
|
||||||
|
|
||||||
|
@ -1155,6 +1169,15 @@ class ViewBox(GraphicsWidget):
|
||||||
#self.scene().render(p)
|
#self.scene().render(p)
|
||||||
#p.end()
|
#p.end()
|
||||||
|
|
||||||
|
def updateBackground(self):
|
||||||
|
bg = self.state['background']
|
||||||
|
if bg is None:
|
||||||
|
self.background.hide()
|
||||||
|
else:
|
||||||
|
self.background.show()
|
||||||
|
self.background.setBrush(fn.mkBrush(bg))
|
||||||
|
|
||||||
|
|
||||||
def updateViewLists(self):
|
def updateViewLists(self):
|
||||||
def cmpViews(a, b):
|
def cmpViews(a, b):
|
||||||
wins = 100 * cmp(a.window() is self.window(), b.window() is self.window())
|
wins = 100 * cmp(a.window() is self.window(), b.window() is self.window())
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
from pyqtgraph.python2_3 import asUnicode
|
||||||
from pyqtgraph.WidgetGroup import WidgetGroup
|
from pyqtgraph.WidgetGroup import WidgetGroup
|
||||||
from .axisCtrlTemplate import Ui_Form as AxisCtrlTemplate
|
from .axisCtrlTemplate import Ui_Form as AxisCtrlTemplate
|
||||||
import weakref
|
import weakref
|
||||||
|
|
|
@ -38,7 +38,7 @@ from pyqtgraph.SignalProxy import SignalProxy
|
||||||
|
|
||||||
class PlotROI(ROI):
|
class PlotROI(ROI):
|
||||||
def __init__(self, size):
|
def __init__(self, size):
|
||||||
ROI.__init__(self, pos=[0,0], size=size, scaleSnap=True, translateSnap=True)
|
ROI.__init__(self, pos=[0,0], size=size) #, scaleSnap=True, translateSnap=True)
|
||||||
self.addScaleHandle([1, 1], [0, 0])
|
self.addScaleHandle([1, 1], [0, 0])
|
||||||
self.addRotateHandle([0, 0], [0.5, 0.5])
|
self.addRotateHandle([0, 0], [0.5, 0.5])
|
||||||
|
|
||||||
|
@ -67,7 +67,12 @@ class ImageView(QtGui.QWidget):
|
||||||
sigTimeChanged = QtCore.Signal(object, object)
|
sigTimeChanged = QtCore.Signal(object, object)
|
||||||
sigProcessingChanged = QtCore.Signal(object)
|
sigProcessingChanged = QtCore.Signal(object)
|
||||||
|
|
||||||
def __init__(self, parent=None, name="ImageView", *args):
|
def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, *args):
|
||||||
|
"""
|
||||||
|
By default, this class creates an :class:`ImageItem <pyqtgraph.ImageItem>` to display image data
|
||||||
|
and a :class:`ViewBox <pyqtgraph.ViewBox>` to contain the ImageItem. Custom items may be given instead
|
||||||
|
by specifying the *view* and/or *imageItem* arguments.
|
||||||
|
"""
|
||||||
QtGui.QWidget.__init__(self, parent, *args)
|
QtGui.QWidget.__init__(self, parent, *args)
|
||||||
self.levelMax = 4096
|
self.levelMax = 4096
|
||||||
self.levelMin = 0
|
self.levelMin = 0
|
||||||
|
@ -89,7 +94,10 @@ class ImageView(QtGui.QWidget):
|
||||||
#self.ui.graphicsView.setAspectLocked(True)
|
#self.ui.graphicsView.setAspectLocked(True)
|
||||||
#self.ui.graphicsView.invertY()
|
#self.ui.graphicsView.invertY()
|
||||||
#self.ui.graphicsView.enableMouse()
|
#self.ui.graphicsView.enableMouse()
|
||||||
self.view = ViewBox()
|
if view is None:
|
||||||
|
self.view = ViewBox()
|
||||||
|
else:
|
||||||
|
self.view = view
|
||||||
self.ui.graphicsView.setCentralItem(self.view)
|
self.ui.graphicsView.setCentralItem(self.view)
|
||||||
self.view.setAspectLocked(True)
|
self.view.setAspectLocked(True)
|
||||||
self.view.invertY()
|
self.view.invertY()
|
||||||
|
@ -101,7 +109,10 @@ class ImageView(QtGui.QWidget):
|
||||||
#self.ui.gradientWidget.setTickColor(self.ticks[1], QtGui.QColor(255,255,255))
|
#self.ui.gradientWidget.setTickColor(self.ticks[1], QtGui.QColor(255,255,255))
|
||||||
#self.ui.gradientWidget.setOrientation('right')
|
#self.ui.gradientWidget.setOrientation('right')
|
||||||
|
|
||||||
self.imageItem = ImageItem()
|
if imageItem is None:
|
||||||
|
self.imageItem = ImageItem()
|
||||||
|
else:
|
||||||
|
self.imageItem = imageItem
|
||||||
self.view.addItem(self.imageItem)
|
self.view.addItem(self.imageItem)
|
||||||
self.currentIndex = 0
|
self.currentIndex = 0
|
||||||
|
|
||||||
|
@ -531,14 +542,18 @@ class ImageView(QtGui.QWidget):
|
||||||
axes = (1, 2)
|
axes = (1, 2)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
data = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes)
|
data, coords = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes, returnMappedCoords=True)
|
||||||
if data is not None:
|
if data is not None:
|
||||||
while data.ndim > 1:
|
while data.ndim > 1:
|
||||||
data = data.mean(axis=1)
|
data = data.mean(axis=1)
|
||||||
if image.ndim == 3:
|
if image.ndim == 3:
|
||||||
self.roiCurve.setData(y=data, x=self.tVals)
|
self.roiCurve.setData(y=data, x=self.tVals)
|
||||||
else:
|
else:
|
||||||
self.roiCurve.setData(y=data, x=list(range(len(data))))
|
while coords.ndim > 2:
|
||||||
|
coords = coords[:,:,0]
|
||||||
|
coords = coords - coords[:,0,np.newaxis]
|
||||||
|
xvals = (coords**2).sum(axis=0) ** 0.5
|
||||||
|
self.roiCurve.setData(y=data, x=xvals)
|
||||||
|
|
||||||
#self.ui.roiPlot.replot()
|
#self.ui.roiPlot.replot()
|
||||||
|
|
||||||
|
@ -664,4 +679,18 @@ class ImageView(QtGui.QWidget):
|
||||||
#return self.levelMin + (self.levelMax-self.levelMin) * self.ui.gradientWidget.tickValue(self.ticks[0])
|
#return self.levelMin + (self.levelMax-self.levelMin) * self.ui.gradientWidget.tickValue(self.ticks[0])
|
||||||
##return self.levelMin + ((self.levelMax-self.levelMin) / self.ui.blackSlider.maximum()) * self.ui.blackSlider.value()
|
##return self.levelMin + ((self.levelMax-self.levelMin) / self.ui.blackSlider.maximum()) * self.ui.blackSlider.value()
|
||||||
|
|
||||||
|
def getView(self):
|
||||||
|
"""Return the ViewBox (or other compatible object) which displays the ImageItem"""
|
||||||
|
return self.view
|
||||||
|
|
||||||
|
def getImageItem(self):
|
||||||
|
"""Return the ImageItem for this ImageView."""
|
||||||
|
return self.imageItem
|
||||||
|
|
||||||
|
def getRoiPlot(self):
|
||||||
|
"""Return the ROI PlotWidget for this ImageView"""
|
||||||
|
return self.ui.roiPlot
|
||||||
|
|
||||||
|
def getHistogramWidget(self):
|
||||||
|
"""Return the HistogramLUTWidget for this ImageView"""
|
||||||
|
return self.ui.histogram
|
||||||
|
|
|
@ -19,4 +19,6 @@ TODO:
|
||||||
(RemoteGraphicsView class)
|
(RemoteGraphicsView class)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from processes import *
|
from .processes import *
|
||||||
|
from .parallelizer import Parallelize, CanceledError
|
||||||
|
from .remoteproxy import proxy
|
15
multiprocess/bootstrap.py
Normal file
15
multiprocess/bootstrap.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
"""For starting up remote processes"""
|
||||||
|
import sys, pickle
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
name, port, authkey, targetStr, path = pickle.load(sys.stdin)
|
||||||
|
if path is not None:
|
||||||
|
## rewrite sys.path without assigning a new object--no idea who already has a reference to the existing list.
|
||||||
|
while len(sys.path) > 0:
|
||||||
|
sys.path.pop()
|
||||||
|
sys.path.extend(path)
|
||||||
|
#import pyqtgraph
|
||||||
|
#import pyqtgraph.multiprocess.processes
|
||||||
|
target = pickle.loads(targetStr) ## unpickling the target should import everything we need
|
||||||
|
target(name, port, authkey)
|
||||||
|
sys.exit(0)
|
|
@ -2,6 +2,10 @@ import os, sys, time, multiprocessing
|
||||||
from processes import ForkedProcess
|
from processes import ForkedProcess
|
||||||
from remoteproxy import ExitError
|
from remoteproxy import ExitError
|
||||||
|
|
||||||
|
class CanceledError(Exception):
|
||||||
|
"""Raised when the progress dialog is canceled during a processing operation."""
|
||||||
|
pass
|
||||||
|
|
||||||
class Parallelize:
|
class Parallelize:
|
||||||
"""
|
"""
|
||||||
Class for ultra-simple inline parallelization on multi-core CPUs
|
Class for ultra-simple inline parallelization on multi-core CPUs
|
||||||
|
@ -29,35 +33,82 @@ class Parallelize:
|
||||||
print results
|
print results
|
||||||
|
|
||||||
|
|
||||||
The only major caveat is that *result* in the example above must be picklable.
|
The only major caveat is that *result* in the example above must be picklable,
|
||||||
|
since it is automatically sent via pipe back to the parent process.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, tasks, workers=None, block=True, **kwds):
|
def __init__(self, tasks, workers=None, block=True, progressDialog=None, randomReseed=True, **kwds):
|
||||||
"""
|
"""
|
||||||
Args:
|
=============== ===================================================================
|
||||||
tasks - list of objects to be processed (Parallelize will determine how to distribute the tasks)
|
Arguments:
|
||||||
workers - number of worker processes or None to use number of CPUs in the system
|
tasks list of objects to be processed (Parallelize will determine how to
|
||||||
kwds - objects to be shared by proxy with child processes
|
distribute the tasks)
|
||||||
|
workers number of worker processes or None to use number of CPUs in the
|
||||||
|
system
|
||||||
|
progressDialog optional dict of arguments for ProgressDialog
|
||||||
|
to update while tasks are processed
|
||||||
|
randomReseed If True, each forked process will reseed its random number generator
|
||||||
|
to ensure independent results. Works with the built-in random
|
||||||
|
and numpy.random.
|
||||||
|
kwds objects to be shared by proxy with child processes (they will
|
||||||
|
appear as attributes of the tasker)
|
||||||
|
=============== ===================================================================
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.block = block
|
## Generate progress dialog.
|
||||||
|
## Note that we want to avoid letting forked child processes play with progress dialogs..
|
||||||
|
self.showProgress = False
|
||||||
|
if progressDialog is not None:
|
||||||
|
self.showProgress = True
|
||||||
|
if isinstance(progressDialog, basestring):
|
||||||
|
progressDialog = {'labelText': progressDialog}
|
||||||
|
import pyqtgraph as pg
|
||||||
|
self.progressDlg = pg.ProgressDialog(**progressDialog)
|
||||||
|
|
||||||
if workers is None:
|
if workers is None:
|
||||||
workers = multiprocessing.cpu_count()
|
workers = self.suggestedWorkerCount()
|
||||||
if not hasattr(os, 'fork'):
|
if not hasattr(os, 'fork'):
|
||||||
workers = 1
|
workers = 1
|
||||||
self.workers = workers
|
self.workers = workers
|
||||||
self.tasks = list(tasks)
|
self.tasks = list(tasks)
|
||||||
self.kwds = kwds
|
self.reseed = randomReseed
|
||||||
|
self.kwds = kwds.copy()
|
||||||
|
self.kwds['_taskStarted'] = self._taskStarted
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.proc = None
|
self.proc = None
|
||||||
workers = self.workers
|
if self.workers == 1:
|
||||||
if workers == 1:
|
return self.runSerial()
|
||||||
return Tasker(None, self.tasks, self.kwds)
|
else:
|
||||||
|
return self.runParallel()
|
||||||
|
|
||||||
|
def __exit__(self, *exc_info):
|
||||||
|
|
||||||
|
if self.proc is not None: ## worker
|
||||||
|
try:
|
||||||
|
if exc_info[0] is not None:
|
||||||
|
sys.excepthook(*exc_info)
|
||||||
|
finally:
|
||||||
|
#print os.getpid(), 'exit'
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
|
else: ## parent
|
||||||
|
if self.showProgress:
|
||||||
|
self.progressDlg.__exit__(None, None, None)
|
||||||
|
|
||||||
|
def runSerial(self):
|
||||||
|
if self.showProgress:
|
||||||
|
self.progressDlg.__enter__()
|
||||||
|
self.progressDlg.setMaximum(len(self.tasks))
|
||||||
|
self.progress = {os.getpid(): []}
|
||||||
|
return Tasker(None, self.tasks, self.kwds)
|
||||||
|
|
||||||
|
|
||||||
|
def runParallel(self):
|
||||||
self.childs = []
|
self.childs = []
|
||||||
|
|
||||||
## break up tasks into one set per worker
|
## break up tasks into one set per worker
|
||||||
|
workers = self.workers
|
||||||
chunks = [[] for i in xrange(workers)]
|
chunks = [[] for i in xrange(workers)]
|
||||||
i = 0
|
i = 0
|
||||||
for i in range(len(self.tasks)):
|
for i in range(len(self.tasks)):
|
||||||
|
@ -65,37 +116,91 @@ class Parallelize:
|
||||||
|
|
||||||
## fork and assign tasks to each worker
|
## fork and assign tasks to each worker
|
||||||
for i in range(workers):
|
for i in range(workers):
|
||||||
proc = ForkedProcess(target=None, preProxy=self.kwds)
|
proc = ForkedProcess(target=None, preProxy=self.kwds, randomReseed=self.reseed)
|
||||||
if not proc.isParent:
|
if not proc.isParent:
|
||||||
self.proc = proc
|
self.proc = proc
|
||||||
return Tasker(proc, chunks[i], proc.forkedProxies)
|
return Tasker(proc, chunks[i], proc.forkedProxies)
|
||||||
else:
|
else:
|
||||||
self.childs.append(proc)
|
self.childs.append(proc)
|
||||||
|
|
||||||
## process events from workers until all have exited.
|
## Keep track of the progress of each worker independently.
|
||||||
activeChilds = self.childs[:]
|
self.progress = {ch.childPid: [] for ch in self.childs}
|
||||||
while len(activeChilds) > 0:
|
## for each child process, self.progress[pid] is a list
|
||||||
for ch in activeChilds:
|
## of task indexes. The last index is the task currently being
|
||||||
|
## processed; all others are finished.
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.showProgress:
|
||||||
|
self.progressDlg.__enter__()
|
||||||
|
self.progressDlg.setMaximum(len(self.tasks))
|
||||||
|
## process events from workers until all have exited.
|
||||||
|
|
||||||
|
activeChilds = self.childs[:]
|
||||||
|
pollInterval = 0.01
|
||||||
|
while len(activeChilds) > 0:
|
||||||
|
waitingChildren = 0
|
||||||
rem = []
|
rem = []
|
||||||
try:
|
for ch in activeChilds:
|
||||||
ch.processRequests()
|
try:
|
||||||
except ExitError:
|
n = ch.processRequests()
|
||||||
rem.append(ch)
|
if n > 0:
|
||||||
for ch in rem:
|
waitingChildren += 1
|
||||||
activeChilds.remove(ch)
|
except ExitError:
|
||||||
time.sleep(0.1)
|
#print ch.childPid, 'process finished'
|
||||||
|
rem.append(ch)
|
||||||
|
if self.showProgress:
|
||||||
|
self.progressDlg += 1
|
||||||
|
#print "remove:", [ch.childPid for ch in rem]
|
||||||
|
for ch in rem:
|
||||||
|
activeChilds.remove(ch)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
os.waitpid(ch.childPid, 0)
|
||||||
|
break
|
||||||
|
except OSError as ex:
|
||||||
|
if ex.errno == 4: ## If we get this error, just try again
|
||||||
|
continue
|
||||||
|
#print "Ignored system call interruption"
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
#print [ch.childPid for ch in activeChilds]
|
||||||
|
|
||||||
|
if self.showProgress and self.progressDlg.wasCanceled():
|
||||||
|
for ch in activeChilds:
|
||||||
|
ch.kill()
|
||||||
|
raise CanceledError()
|
||||||
|
|
||||||
|
## adjust polling interval--prefer to get exactly 1 event per poll cycle.
|
||||||
|
if waitingChildren > 1:
|
||||||
|
pollInterval *= 0.7
|
||||||
|
elif waitingChildren == 0:
|
||||||
|
pollInterval /= 0.7
|
||||||
|
pollInterval = max(min(pollInterval, 0.5), 0.0005) ## but keep it within reasonable limits
|
||||||
|
|
||||||
|
time.sleep(pollInterval)
|
||||||
|
finally:
|
||||||
|
if self.showProgress:
|
||||||
|
self.progressDlg.__exit__(None, None, None)
|
||||||
return [] ## no tasks for parent process.
|
return [] ## no tasks for parent process.
|
||||||
|
|
||||||
def __exit__(self, *exc_info):
|
|
||||||
if exc_info[0] is not None:
|
|
||||||
sys.excepthook(*exc_info)
|
|
||||||
if self.proc is not None:
|
|
||||||
os._exit(0)
|
|
||||||
|
|
||||||
def wait(self):
|
|
||||||
## wait for all child processes to finish
|
@staticmethod
|
||||||
pass
|
def suggestedWorkerCount():
|
||||||
|
return multiprocessing.cpu_count() ## is this really the best option?
|
||||||
|
|
||||||
|
def _taskStarted(self, pid, i, **kwds):
|
||||||
|
## called remotely by tasker to indicate it has started working on task i
|
||||||
|
#print pid, 'reported starting task', i
|
||||||
|
if self.showProgress:
|
||||||
|
if len(self.progress[pid]) > 0:
|
||||||
|
self.progressDlg += 1
|
||||||
|
if pid == os.getpid(): ## single-worker process
|
||||||
|
if self.progressDlg.wasCanceled():
|
||||||
|
raise CanceledError()
|
||||||
|
self.progress[pid].append(i)
|
||||||
|
|
||||||
|
|
||||||
class Tasker:
|
class Tasker:
|
||||||
def __init__(self, proc, tasks, kwds):
|
def __init__(self, proc, tasks, kwds):
|
||||||
|
@ -106,9 +211,13 @@ class Tasker:
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
## we could fix this up such that tasks are retrieved from the parent process one at a time..
|
## we could fix this up such that tasks are retrieved from the parent process one at a time..
|
||||||
for task in self.tasks:
|
for i, task in enumerate(self.tasks):
|
||||||
|
self.index = i
|
||||||
|
#print os.getpid(), 'starting task', i
|
||||||
|
self._taskStarted(os.getpid(), i, _callSync='off')
|
||||||
yield task
|
yield task
|
||||||
if self.proc is not None:
|
if self.proc is not None:
|
||||||
|
#print os.getpid(), 'no more tasks'
|
||||||
self.proc.close()
|
self.proc.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,51 @@
|
||||||
from remoteproxy import RemoteEventHandler, ExitError, NoResultError, LocalObjectProxy, ObjectProxy
|
from remoteproxy import RemoteEventHandler, ExitError, NoResultError, LocalObjectProxy, ObjectProxy
|
||||||
import subprocess, atexit, os, sys, time, random, socket
|
import subprocess, atexit, os, sys, time, random, socket, signal
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
import multiprocessing.connection
|
import multiprocessing.connection
|
||||||
|
|
||||||
|
__all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ExitError', 'NoResultError']
|
||||||
|
|
||||||
class Process(RemoteEventHandler):
|
class Process(RemoteEventHandler):
|
||||||
def __init__(self, name=None, target=None):
|
"""
|
||||||
|
Bases: RemoteEventHandler
|
||||||
|
|
||||||
|
This class is used to spawn and control a new python interpreter.
|
||||||
|
It uses subprocess.Popen to start the new process and communicates with it
|
||||||
|
using multiprocessing.Connection objects over a network socket.
|
||||||
|
|
||||||
|
By default, the remote process will immediately enter an event-processing
|
||||||
|
loop that carries out requests send from the parent process.
|
||||||
|
|
||||||
|
Remote control works mainly through proxy objects::
|
||||||
|
|
||||||
|
proc = Process() ## starts process, returns handle
|
||||||
|
rsys = proc._import('sys') ## asks remote process to import 'sys', returns
|
||||||
|
## a proxy which references the imported module
|
||||||
|
rsys.stdout.write('hello\n') ## This message will be printed from the remote
|
||||||
|
## process. Proxy objects can usually be used
|
||||||
|
## exactly as regular objects are.
|
||||||
|
proc.close() ## Request the remote process shut down
|
||||||
|
|
||||||
|
Requests made via proxy objects may be synchronous or asynchronous and may
|
||||||
|
return objects either by proxy or by value (if they are picklable). See
|
||||||
|
ProxyObject for more information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name=None, target=None, copySysPath=True):
|
||||||
|
"""
|
||||||
|
============ =============================================================
|
||||||
|
Arguments:
|
||||||
|
name Optional name for this process used when printing messages
|
||||||
|
from the remote process.
|
||||||
|
target Optional function to call after starting remote process.
|
||||||
|
By default, this is startEventLoop(), which causes the remote
|
||||||
|
process to process requests from the parent process until it
|
||||||
|
is asked to quit. If you wish to specify a different target,
|
||||||
|
it must be picklable (bound methods are not).
|
||||||
|
copySysPath If true, copy the contents of sys.path to the remote process
|
||||||
|
============ =============================================================
|
||||||
|
|
||||||
|
"""
|
||||||
if target is None:
|
if target is None:
|
||||||
target = startEventLoop
|
target = startEventLoop
|
||||||
if name is None:
|
if name is None:
|
||||||
|
@ -25,8 +66,12 @@ class Process(RemoteEventHandler):
|
||||||
port += 1
|
port += 1
|
||||||
|
|
||||||
## start remote process, instruct it to run target function
|
## start remote process, instruct it to run target function
|
||||||
self.proc = subprocess.Popen((sys.executable, __file__, 'remote'), stdin=subprocess.PIPE)
|
sysPath = sys.path if copySysPath else None
|
||||||
pickle.dump((name+'_child', port, authkey, target), self.proc.stdin)
|
bootstrap = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bootstrap.py'))
|
||||||
|
self.proc = subprocess.Popen((sys.executable, bootstrap), stdin=subprocess.PIPE)
|
||||||
|
targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to
|
||||||
|
## set its sys.path properly before unpickling the target
|
||||||
|
pickle.dump((name+'_child', port, authkey, targetStr, sysPath), self.proc.stdin)
|
||||||
self.proc.stdin.close()
|
self.proc.stdin.close()
|
||||||
|
|
||||||
## open connection for remote process
|
## open connection for remote process
|
||||||
|
@ -60,19 +105,32 @@ def startEventLoop(name, port, authkey):
|
||||||
class ForkedProcess(RemoteEventHandler):
|
class ForkedProcess(RemoteEventHandler):
|
||||||
"""
|
"""
|
||||||
ForkedProcess is a substitute for Process that uses os.fork() to generate a new process.
|
ForkedProcess is a substitute for Process that uses os.fork() to generate a new process.
|
||||||
This is much faster than starting a completely new interpreter, but carries some caveats
|
This is much faster than starting a completely new interpreter and child processes
|
||||||
and limitations:
|
automatically have a copy of the entire program state from before the fork. This
|
||||||
- open file handles are shared with the parent process, which is potentially dangerous
|
makes it an appealing approach when parallelizing expensive computations. (see
|
||||||
- it is not possible to have a QApplication in both parent and child process
|
also Parallelizer)
|
||||||
(unless both QApplications are created _after_ the call to fork())
|
|
||||||
- generally not thread-safe. Also, threads are not copied by fork(); the new process
|
However, fork() comes with some caveats and limitations:
|
||||||
will have only one thread that starts wherever fork() was called in the parent process.
|
|
||||||
- forked processes are unceremoniously terminated when join() is called; they are not
|
- fork() is not available on Windows.
|
||||||
given any opportunity to clean up. (This prevents them calling any cleanup code that
|
- It is not possible to have a QApplication in both parent and child process
|
||||||
was only intended to be used by the parent process)
|
(unless both QApplications are created _after_ the call to fork())
|
||||||
|
Attempts by the forked process to access Qt GUI elements created by the parent
|
||||||
|
will most likely cause the child to crash.
|
||||||
|
- Likewise, database connections are unlikely to function correctly in a forked child.
|
||||||
|
- Threads are not copied by fork(); the new process
|
||||||
|
will have only one thread that starts wherever fork() was called in the parent process.
|
||||||
|
- Forked processes are unceremoniously terminated when join() is called; they are not
|
||||||
|
given any opportunity to clean up. (This prevents them calling any cleanup code that
|
||||||
|
was only intended to be used by the parent process)
|
||||||
|
- Normally when fork()ing, open file handles are shared with the parent process,
|
||||||
|
which is potentially dangerous. ForkedProcess is careful to close all file handles
|
||||||
|
that are not explicitly needed--stdout, stderr, and a single pipe to the parent
|
||||||
|
process.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name=None, target=0, preProxy=None):
|
def __init__(self, name=None, target=0, preProxy=None, randomReseed=True):
|
||||||
"""
|
"""
|
||||||
When initializing, an optional target may be given.
|
When initializing, an optional target may be given.
|
||||||
If no target is specified, self.eventLoop will be used.
|
If no target is specified, self.eventLoop will be used.
|
||||||
|
@ -83,6 +141,9 @@ class ForkedProcess(RemoteEventHandler):
|
||||||
in the remote process (but do not need to be sent explicitly since
|
in the remote process (but do not need to be sent explicitly since
|
||||||
they are available immediately before the call to fork().
|
they are available immediately before the call to fork().
|
||||||
Proxies will be availabe as self.proxies[name].
|
Proxies will be availabe as self.proxies[name].
|
||||||
|
|
||||||
|
If randomReseed is True, the built-in random and numpy.random generators
|
||||||
|
will be reseeded in the child process.
|
||||||
"""
|
"""
|
||||||
self.hasJoined = False
|
self.hasJoined = False
|
||||||
if target == 0:
|
if target == 0:
|
||||||
|
@ -101,16 +162,51 @@ class ForkedProcess(RemoteEventHandler):
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
if pid == 0:
|
if pid == 0:
|
||||||
self.isParent = False
|
self.isParent = False
|
||||||
|
## We are now in the forked process; need to be extra careful what we touch while here.
|
||||||
|
## - no reading/writing file handles/sockets owned by parent process (stdout is ok)
|
||||||
|
## - don't touch QtGui or QApplication at all; these are landmines.
|
||||||
|
## - don't let the process call exit handlers
|
||||||
|
## -
|
||||||
|
|
||||||
|
## close all file handles we do not want shared with parent
|
||||||
conn.close()
|
conn.close()
|
||||||
sys.stdin.close() ## otherwise we screw with interactive prompts.
|
sys.stdin.close() ## otherwise we screw with interactive prompts.
|
||||||
|
fid = remoteConn.fileno()
|
||||||
|
os.closerange(3, fid)
|
||||||
|
os.closerange(fid+1, 4096) ## just guessing on the maximum descriptor count..
|
||||||
|
|
||||||
|
## Override any custom exception hooks
|
||||||
|
def excepthook(*args):
|
||||||
|
import traceback
|
||||||
|
traceback.print_exception(*args)
|
||||||
|
sys.excepthook = excepthook
|
||||||
|
|
||||||
|
## Make it harder to access QApplication instance
|
||||||
|
if 'PyQt4.QtGui' in sys.modules:
|
||||||
|
sys.modules['PyQt4.QtGui'].QApplication = None
|
||||||
|
sys.modules.pop('PyQt4.QtGui', None)
|
||||||
|
sys.modules.pop('PyQt4.QtCore', None)
|
||||||
|
|
||||||
|
## sabotage atexit callbacks
|
||||||
|
atexit._exithandlers = []
|
||||||
|
atexit.register(lambda: os._exit(0))
|
||||||
|
|
||||||
|
if randomReseed:
|
||||||
|
if 'numpy.random' in sys.modules:
|
||||||
|
sys.modules['numpy.random'].seed(os.getpid() ^ int(time.time()*10000%10000))
|
||||||
|
if 'random' in sys.modules:
|
||||||
|
sys.modules['random'].seed(os.getpid() ^ int(time.time()*10000%10000))
|
||||||
|
|
||||||
RemoteEventHandler.__init__(self, remoteConn, name+'_child', pid=os.getppid())
|
RemoteEventHandler.__init__(self, remoteConn, name+'_child', pid=os.getppid())
|
||||||
if target is not None:
|
|
||||||
target()
|
|
||||||
|
|
||||||
ppid = os.getppid()
|
ppid = os.getppid()
|
||||||
self.forkedProxies = {}
|
self.forkedProxies = {}
|
||||||
for name, proxyId in proxyIDs.iteritems():
|
for name, proxyId in proxyIDs.iteritems():
|
||||||
self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name]))
|
self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name]))
|
||||||
|
|
||||||
|
if target is not None:
|
||||||
|
target()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.isParent = True
|
self.isParent = True
|
||||||
self.childPid = pid
|
self.childPid = pid
|
||||||
|
@ -127,10 +223,11 @@ class ForkedProcess(RemoteEventHandler):
|
||||||
self.processRequests() # exception raised when the loop should exit
|
self.processRequests() # exception raised when the loop should exit
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
except ExitError:
|
except ExitError:
|
||||||
sys.exit(0)
|
break
|
||||||
except:
|
except:
|
||||||
print "Error occurred in forked event loop:"
|
print "Error occurred in forked event loop:"
|
||||||
sys.excepthook(*sys.exc_info())
|
sys.excepthook(*sys.exc_info())
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
def join(self, timeout=10):
|
def join(self, timeout=10):
|
||||||
if self.hasJoined:
|
if self.hasJoined:
|
||||||
|
@ -138,10 +235,19 @@ class ForkedProcess(RemoteEventHandler):
|
||||||
#os.kill(pid, 9)
|
#os.kill(pid, 9)
|
||||||
try:
|
try:
|
||||||
self.close(callSync='sync', timeout=timeout, noCleanup=True) ## ask the child process to exit and require that it return a confirmation.
|
self.close(callSync='sync', timeout=timeout, noCleanup=True) ## ask the child process to exit and require that it return a confirmation.
|
||||||
|
os.waitpid(self.childPid, 0)
|
||||||
except IOError: ## probably remote process has already quit
|
except IOError: ## probably remote process has already quit
|
||||||
pass
|
pass
|
||||||
self.hasJoined = True
|
self.hasJoined = True
|
||||||
|
|
||||||
|
def kill(self):
|
||||||
|
"""Immediately kill the forked remote process.
|
||||||
|
This is generally safe because forked processes are already
|
||||||
|
expected to _avoid_ any cleanup at exit."""
|
||||||
|
os.kill(self.childPid, signal.SIGKILL)
|
||||||
|
self.hasJoined = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##Special set of subclasses that implement a Qt event loop instead.
|
##Special set of subclasses that implement a Qt event loop instead.
|
||||||
|
|
||||||
|
@ -165,8 +271,33 @@ class RemoteQtEventHandler(RemoteEventHandler):
|
||||||
#raise
|
#raise
|
||||||
|
|
||||||
class QtProcess(Process):
|
class QtProcess(Process):
|
||||||
def __init__(self, name=None):
|
"""
|
||||||
Process.__init__(self, name, target=startQtEventLoop)
|
QtProcess is essentially the same as Process, with two major differences:
|
||||||
|
|
||||||
|
- The remote process starts by running startQtEventLoop() which creates a
|
||||||
|
QApplication in the remote process and uses a QTimer to trigger
|
||||||
|
remote event processing. This allows the remote process to have its own
|
||||||
|
GUI.
|
||||||
|
- A QTimer is also started on the parent process which polls for requests
|
||||||
|
from the child process. This allows Qt signals emitted within the child
|
||||||
|
process to invoke slots on the parent process and vice-versa.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
proc = QtProcess()
|
||||||
|
rQtGui = proc._import('PyQt4.QtGui')
|
||||||
|
btn = rQtGui.QPushButton('button on child process')
|
||||||
|
btn.show()
|
||||||
|
|
||||||
|
def slot():
|
||||||
|
print 'slot invoked on parent process'
|
||||||
|
btn.clicked.connect(proxy(slot)) # be sure to send a proxy of the slot
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **kwds):
|
||||||
|
if 'target' not in kwds:
|
||||||
|
kwds['target'] = startQtEventLoop
|
||||||
|
Process.__init__(self, **kwds)
|
||||||
self.startEventTimer()
|
self.startEventTimer()
|
||||||
|
|
||||||
def startEventTimer(self):
|
def startEventTimer(self):
|
||||||
|
@ -201,8 +332,3 @@ def startQtEventLoop(name, port, authkey):
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
if len(sys.argv) == 2 and sys.argv[1] == 'remote': ## module has been invoked as script in new python interpreter.
|
|
||||||
name, port, authkey, target = pickle.load(sys.stdin)
|
|
||||||
target(name, port, authkey)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
|
@ -9,7 +9,26 @@ class NoResultError(Exception):
|
||||||
|
|
||||||
|
|
||||||
class RemoteEventHandler(object):
|
class RemoteEventHandler(object):
|
||||||
|
"""
|
||||||
|
This class handles communication between two processes. One instance is present on
|
||||||
|
each process and listens for communication from the other process. This enables
|
||||||
|
(amongst other things) ObjectProxy instances to look up their attributes and call
|
||||||
|
their methods.
|
||||||
|
|
||||||
|
This class is responsible for carrying out actions on behalf of the remote process.
|
||||||
|
Each instance holds one end of a Connection which allows python
|
||||||
|
objects to be passed between processes.
|
||||||
|
|
||||||
|
For the most common operations, see _import(), close(), and transfer()
|
||||||
|
|
||||||
|
To handle and respond to incoming requests, RemoteEventHandler requires that its
|
||||||
|
processRequests method is called repeatedly (this is usually handled by the Process
|
||||||
|
classes defined in multiprocess.processes).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
handlers = {} ## maps {process ID : handler}. This allows unpickler to determine which process
|
handlers = {} ## maps {process ID : handler}. This allows unpickler to determine which process
|
||||||
## an object proxy belongs to
|
## an object proxy belongs to
|
||||||
|
|
||||||
|
@ -55,19 +74,25 @@ class RemoteEventHandler(object):
|
||||||
|
|
||||||
def processRequests(self):
|
def processRequests(self):
|
||||||
"""Process all pending requests from the pipe, return
|
"""Process all pending requests from the pipe, return
|
||||||
after no more events are immediately available. (non-blocking)"""
|
after no more events are immediately available. (non-blocking)
|
||||||
|
Returns the number of events processed.
|
||||||
|
"""
|
||||||
if self.exited:
|
if self.exited:
|
||||||
raise ExitError()
|
raise ExitError()
|
||||||
|
|
||||||
|
numProcessed = 0
|
||||||
while self.conn.poll():
|
while self.conn.poll():
|
||||||
try:
|
try:
|
||||||
self.handleRequest()
|
self.handleRequest()
|
||||||
|
numProcessed += 1
|
||||||
except ExitError:
|
except ExitError:
|
||||||
self.exited = True
|
self.exited = True
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
print "Error in process %s" % self.name
|
print "Error in process %s" % self.name
|
||||||
sys.excepthook(*sys.exc_info())
|
sys.excepthook(*sys.exc_info())
|
||||||
|
|
||||||
|
return numProcessed
|
||||||
|
|
||||||
def handleRequest(self):
|
def handleRequest(self):
|
||||||
"""Handle a single request from the remote process.
|
"""Handle a single request from the remote process.
|
||||||
|
@ -175,6 +200,7 @@ class RemoteEventHandler(object):
|
||||||
self.send(request='result', reqId=reqId, callSync='off', opts=dict(result=result))
|
self.send(request='result', reqId=reqId, callSync='off', opts=dict(result=result))
|
||||||
|
|
||||||
def replyError(self, reqId, *exc):
|
def replyError(self, reqId, *exc):
|
||||||
|
print "error:", self.name, reqId, exc[1]
|
||||||
excStr = traceback.format_exception(*exc)
|
excStr = traceback.format_exception(*exc)
|
||||||
try:
|
try:
|
||||||
self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=exc[1], excString=excStr))
|
self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=exc[1], excString=excStr))
|
||||||
|
@ -282,7 +308,9 @@ class RemoteEventHandler(object):
|
||||||
try:
|
try:
|
||||||
optStr = pickle.dumps(opts)
|
optStr = pickle.dumps(opts)
|
||||||
except:
|
except:
|
||||||
print "Error pickling:", opts
|
print "==== Error pickling this object: ===="
|
||||||
|
print opts
|
||||||
|
print "======================================="
|
||||||
raise
|
raise
|
||||||
|
|
||||||
request = (request, reqId, optStr)
|
request = (request, reqId, optStr)
|
||||||
|
@ -381,8 +409,8 @@ class RemoteEventHandler(object):
|
||||||
|
|
||||||
def transfer(self, obj, **kwds):
|
def transfer(self, obj, **kwds):
|
||||||
"""
|
"""
|
||||||
Transfer an object to the remote host (the object must be picklable) and return
|
Transfer an object by value to the remote host (the object must be picklable)
|
||||||
a proxy for the new remote object.
|
and return a proxy for the new remote object.
|
||||||
"""
|
"""
|
||||||
return self.send(request='transfer', opts=dict(obj=obj), **kwds)
|
return self.send(request='transfer', opts=dict(obj=obj), **kwds)
|
||||||
|
|
||||||
|
@ -395,7 +423,12 @@ class RemoteEventHandler(object):
|
||||||
|
|
||||||
|
|
||||||
class Request:
|
class Request:
|
||||||
## used internally for tracking asynchronous requests and returning results
|
"""
|
||||||
|
Request objects are returned when calling an ObjectProxy in asynchronous mode
|
||||||
|
or if a synchronous call has timed out. Use hasResult() to ask whether
|
||||||
|
the result of the call has been returned yet. Use result() to get
|
||||||
|
the returned value.
|
||||||
|
"""
|
||||||
def __init__(self, process, reqId, description=None, timeout=10):
|
def __init__(self, process, reqId, description=None, timeout=10):
|
||||||
self.proc = process
|
self.proc = process
|
||||||
self.description = description
|
self.description = description
|
||||||
|
@ -405,10 +438,13 @@ class Request:
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
def result(self, block=True, timeout=None):
|
def result(self, block=True, timeout=None):
|
||||||
"""Return the result for this request.
|
"""
|
||||||
|
Return the result for this request.
|
||||||
|
|
||||||
If block is True, wait until the result has arrived or *timeout* seconds passes.
|
If block is True, wait until the result has arrived or *timeout* seconds passes.
|
||||||
If the timeout is reached, raise an exception. (use timeout=None to disable)
|
If the timeout is reached, raise NoResultError. (use timeout=None to disable)
|
||||||
If block is False, raises an exception if the result has not arrived yet."""
|
If block is False, raise NoResultError immediately if the result has not arrived yet.
|
||||||
|
"""
|
||||||
|
|
||||||
if self.gotResult:
|
if self.gotResult:
|
||||||
return self._result
|
return self._result
|
||||||
|
@ -434,16 +470,24 @@ class Request:
|
||||||
def hasResult(self):
|
def hasResult(self):
|
||||||
"""Returns True if the result for this request has arrived."""
|
"""Returns True if the result for this request has arrived."""
|
||||||
try:
|
try:
|
||||||
#print "check result", self.description
|
|
||||||
self.result(block=False)
|
self.result(block=False)
|
||||||
except NoResultError:
|
except NoResultError:
|
||||||
#print " -> not yet"
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.gotResult
|
return self.gotResult
|
||||||
|
|
||||||
class LocalObjectProxy(object):
|
class LocalObjectProxy(object):
|
||||||
"""Used for wrapping local objects to ensure that they are send by proxy to a remote host."""
|
"""
|
||||||
|
Used for wrapping local objects to ensure that they are send by proxy to a remote host.
|
||||||
|
Note that 'proxy' is just a shorter alias for LocalObjectProxy.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
data = [1,2,3,4,5]
|
||||||
|
remotePlot.plot(data) ## by default, lists are pickled and sent by value
|
||||||
|
remotePlot.plot(proxy(data)) ## force the object to be sent by proxy
|
||||||
|
|
||||||
|
"""
|
||||||
nextProxyId = 0
|
nextProxyId = 0
|
||||||
proxiedObjects = {} ## maps {proxyId: object}
|
proxiedObjects = {} ## maps {proxyId: object}
|
||||||
|
|
||||||
|
@ -467,24 +511,31 @@ class LocalObjectProxy(object):
|
||||||
del cls.proxiedObjects[pid]
|
del cls.proxiedObjects[pid]
|
||||||
#print "release:", cls.proxiedObjects
|
#print "release:", cls.proxiedObjects
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj, **opts):
|
||||||
|
"""
|
||||||
|
Create a 'local' proxy object that, when sent to a remote host,
|
||||||
|
will appear as a normal ObjectProxy to *obj*.
|
||||||
|
Any extra keyword arguments are passed to proxy._setProxyOptions()
|
||||||
|
on the remote side.
|
||||||
|
"""
|
||||||
self.processId = os.getpid()
|
self.processId = os.getpid()
|
||||||
#self.objectId = id(obj)
|
#self.objectId = id(obj)
|
||||||
self.typeStr = repr(obj)
|
self.typeStr = repr(obj)
|
||||||
#self.handler = handler
|
#self.handler = handler
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
|
self.opts = opts
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
## a proxy is being pickled and sent to a remote process.
|
## a proxy is being pickled and sent to a remote process.
|
||||||
## every time this happens, a new proxy will be generated in the remote process,
|
## every time this happens, a new proxy will be generated in the remote process,
|
||||||
## so we keep a new ID so we can track when each is released.
|
## so we keep a new ID so we can track when each is released.
|
||||||
pid = LocalObjectProxy.registerObject(self.obj)
|
pid = LocalObjectProxy.registerObject(self.obj)
|
||||||
return (unpickleObjectProxy, (self.processId, pid, self.typeStr))
|
return (unpickleObjectProxy, (self.processId, pid, self.typeStr, None, self.opts))
|
||||||
|
|
||||||
## alias
|
## alias
|
||||||
proxy = LocalObjectProxy
|
proxy = LocalObjectProxy
|
||||||
|
|
||||||
def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None):
|
def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None, opts=None):
|
||||||
if processId == os.getpid():
|
if processId == os.getpid():
|
||||||
obj = LocalObjectProxy.lookupProxyId(proxyId)
|
obj = LocalObjectProxy.lookupProxyId(proxyId)
|
||||||
if attributes is not None:
|
if attributes is not None:
|
||||||
|
@ -492,7 +543,10 @@ def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None):
|
||||||
obj = getattr(obj, attr)
|
obj = getattr(obj, attr)
|
||||||
return obj
|
return obj
|
||||||
else:
|
else:
|
||||||
return ObjectProxy(processId, proxyId=proxyId, typeStr=typeStr)
|
proxy = ObjectProxy(processId, proxyId=proxyId, typeStr=typeStr)
|
||||||
|
if opts is not None:
|
||||||
|
proxy._setProxyOptions(**opts)
|
||||||
|
return proxy
|
||||||
|
|
||||||
class ObjectProxy(object):
|
class ObjectProxy(object):
|
||||||
"""
|
"""
|
||||||
|
@ -501,7 +555,44 @@ class ObjectProxy(object):
|
||||||
attributes on existing proxy objects.
|
attributes on existing proxy objects.
|
||||||
|
|
||||||
For the most part, this object can be used exactly as if it
|
For the most part, this object can be used exactly as if it
|
||||||
were a local object.
|
were a local object::
|
||||||
|
|
||||||
|
rsys = proc._import('sys') # returns proxy to sys module on remote process
|
||||||
|
rsys.stdout # proxy to remote sys.stdout
|
||||||
|
rsys.stdout.write # proxy to remote sys.stdout.write
|
||||||
|
rsys.stdout.write('hello') # calls sys.stdout.write('hello') on remote machine
|
||||||
|
# and returns the result (None)
|
||||||
|
|
||||||
|
When calling a proxy to a remote function, the call can be made synchronous
|
||||||
|
(result of call is returned immediately), asynchronous (result is returned later),
|
||||||
|
or return can be disabled entirely::
|
||||||
|
|
||||||
|
ros = proc._import('os')
|
||||||
|
|
||||||
|
## synchronous call; result is returned immediately
|
||||||
|
pid = ros.getpid()
|
||||||
|
|
||||||
|
## asynchronous call
|
||||||
|
request = ros.getpid(_callSync='async')
|
||||||
|
while not request.hasResult():
|
||||||
|
time.sleep(0.01)
|
||||||
|
pid = request.result()
|
||||||
|
|
||||||
|
## disable return when we know it isn't needed
|
||||||
|
rsys.stdout.write('hello', _callSync='off')
|
||||||
|
|
||||||
|
Additionally, values returned from a remote function call are automatically
|
||||||
|
returned either by value (must be picklable) or by proxy.
|
||||||
|
This behavior can be forced::
|
||||||
|
|
||||||
|
rnp = proc._import('numpy')
|
||||||
|
arrProxy = rnp.array([1,2,3,4], _returnType='proxy')
|
||||||
|
arrValue = rnp.array([1,2,3,4], _returnType='value')
|
||||||
|
|
||||||
|
The default callSync and returnType behaviors (as well as others) can be set
|
||||||
|
for each proxy individually using ObjectProxy._setProxyOptions() or globally using
|
||||||
|
proc.setProxyOptions().
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, processId, proxyId, typeStr='', parent=None):
|
def __init__(self, processId, proxyId, typeStr='', parent=None):
|
||||||
object.__init__(self)
|
object.__init__(self)
|
||||||
|
@ -574,6 +665,13 @@ class ObjectProxy(object):
|
||||||
"""
|
"""
|
||||||
self._proxyOptions.update(kwds)
|
self._proxyOptions.update(kwds)
|
||||||
|
|
||||||
|
def _getValue(self):
|
||||||
|
"""
|
||||||
|
Return the value of the proxied object
|
||||||
|
(the remote object must be picklable)
|
||||||
|
"""
|
||||||
|
return self._handler.getObjValue(self)
|
||||||
|
|
||||||
def _getProxyOption(self, opt):
|
def _getProxyOption(self, opt):
|
||||||
val = self._proxyOptions[opt]
|
val = self._proxyOptions[opt]
|
||||||
if val is None:
|
if val is None:
|
||||||
|
@ -591,20 +689,31 @@ class ObjectProxy(object):
|
||||||
return "<ObjectProxy for process %d, object 0x%x: %s >" % (self._processId, self._proxyId, self._typeStr)
|
return "<ObjectProxy for process %d, object 0x%x: %s >" % (self._processId, self._proxyId, self._typeStr)
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr, **kwds):
|
||||||
#if '_processId' not in self.__dict__:
|
"""
|
||||||
#raise Exception("ObjectProxy has no processId")
|
Calls __getattr__ on the remote object and returns the attribute
|
||||||
#proc = Process._processes[self._processId]
|
by value or by proxy depending on the options set (see
|
||||||
deferred = self._getProxyOption('deferGetattr')
|
ObjectProxy._setProxyOptions and RemoteEventHandler.setProxyOptions)
|
||||||
if deferred is True:
|
|
||||||
|
If the option 'deferGetattr' is True for this proxy, then a new proxy object
|
||||||
|
is returned _without_ asking the remote object whether the named attribute exists.
|
||||||
|
This can save time when making multiple chained attribute requests,
|
||||||
|
but may also defer a possible AttributeError until later, making
|
||||||
|
them more difficult to debug.
|
||||||
|
"""
|
||||||
|
opts = self._getProxyOptions()
|
||||||
|
for k in opts:
|
||||||
|
if '_'+k in kwds:
|
||||||
|
opts[k] = kwds.pop('_'+k)
|
||||||
|
if opts['deferGetattr'] is True:
|
||||||
return self._deferredAttr(attr)
|
return self._deferredAttr(attr)
|
||||||
else:
|
else:
|
||||||
opts = self._getProxyOptions()
|
#opts = self._getProxyOptions()
|
||||||
return self._handler.getObjAttr(self, attr, **opts)
|
return self._handler.getObjAttr(self, attr, **opts)
|
||||||
|
|
||||||
def _deferredAttr(self, attr):
|
def _deferredAttr(self, attr):
|
||||||
return DeferredObjectProxy(self, attr)
|
return DeferredObjectProxy(self, attr)
|
||||||
|
|
||||||
def __call__(self, *args, **kwds):
|
def __call__(self, *args, **kwds):
|
||||||
"""
|
"""
|
||||||
Attempts to call the proxied object from the remote process.
|
Attempts to call the proxied object from the remote process.
|
||||||
|
@ -613,44 +722,34 @@ class ObjectProxy(object):
|
||||||
_callSync 'off', 'sync', or 'async'
|
_callSync 'off', 'sync', or 'async'
|
||||||
_returnType 'value', 'proxy', or 'auto'
|
_returnType 'value', 'proxy', or 'auto'
|
||||||
|
|
||||||
|
If the remote call raises an exception on the remote process,
|
||||||
|
it will be re-raised on the local process.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#opts = {}
|
|
||||||
#callSync = kwds.pop('_callSync', self.)
|
|
||||||
#if callSync is not None:
|
|
||||||
#opts['callSync'] = callSync
|
|
||||||
#returnType = kwds.pop('_returnType', self._defaultReturnValue)
|
|
||||||
#if returnType is not None:
|
|
||||||
#opts['returnType'] = returnType
|
|
||||||
opts = self._getProxyOptions()
|
opts = self._getProxyOptions()
|
||||||
for k in opts:
|
for k in opts:
|
||||||
if '_'+k in kwds:
|
if '_'+k in kwds:
|
||||||
opts[k] = kwds.pop('_'+k)
|
opts[k] = kwds.pop('_'+k)
|
||||||
#print "call", opts
|
|
||||||
return self._handler.callObj(obj=self, args=args, kwds=kwds, **opts)
|
return self._handler.callObj(obj=self, args=args, kwds=kwds, **opts)
|
||||||
|
|
||||||
def _getValue(self):
|
|
||||||
## this just gives us an easy way to change the behavior of the special methods
|
|
||||||
#proc = Process._processes[self._processId]
|
|
||||||
return self._handler.getObjValue(self)
|
|
||||||
|
|
||||||
|
|
||||||
## Explicitly proxy special methods. Is there a better way to do this??
|
## Explicitly proxy special methods. Is there a better way to do this??
|
||||||
|
|
||||||
def _getSpecialAttr(self, attr):
|
def _getSpecialAttr(self, attr):
|
||||||
#return self.__getattr__(attr)
|
## this just gives us an easy way to change the behavior of the special methods
|
||||||
return self._deferredAttr(attr)
|
return self._deferredAttr(attr)
|
||||||
|
|
||||||
def __getitem__(self, *args):
|
def __getitem__(self, *args):
|
||||||
return self._getSpecialAttr('__getitem__')(*args)
|
return self._getSpecialAttr('__getitem__')(*args)
|
||||||
|
|
||||||
def __setitem__(self, *args):
|
def __setitem__(self, *args):
|
||||||
return self._getSpecialAttr('__setitem__')(*args)
|
return self._getSpecialAttr('__setitem__')(*args, _callSync='off')
|
||||||
|
|
||||||
def __setattr__(self, *args):
|
def __setattr__(self, *args):
|
||||||
return self._getSpecialAttr('__setattr__')(*args)
|
return self._getSpecialAttr('__setattr__')(*args, _callSync='off')
|
||||||
|
|
||||||
def __str__(self, *args):
|
def __str__(self, *args):
|
||||||
return self._getSpecialAttr('__str__')(*args, _returnType=True)
|
return self._getSpecialAttr('__str__')(*args, _returnType='value')
|
||||||
|
|
||||||
def __len__(self, *args):
|
def __len__(self, *args):
|
||||||
return self._getSpecialAttr('__len__')(*args)
|
return self._getSpecialAttr('__len__')(*args)
|
||||||
|
@ -670,6 +769,21 @@ class ObjectProxy(object):
|
||||||
def __pow__(self, *args):
|
def __pow__(self, *args):
|
||||||
return self._getSpecialAttr('__pow__')(*args)
|
return self._getSpecialAttr('__pow__')(*args)
|
||||||
|
|
||||||
|
def __iadd__(self, *args):
|
||||||
|
return self._getSpecialAttr('__iadd__')(*args, _callSync='off')
|
||||||
|
|
||||||
|
def __isub__(self, *args):
|
||||||
|
return self._getSpecialAttr('__isub__')(*args, _callSync='off')
|
||||||
|
|
||||||
|
def __idiv__(self, *args):
|
||||||
|
return self._getSpecialAttr('__idiv__')(*args, _callSync='off')
|
||||||
|
|
||||||
|
def __imul__(self, *args):
|
||||||
|
return self._getSpecialAttr('__imul__')(*args, _callSync='off')
|
||||||
|
|
||||||
|
def __ipow__(self, *args):
|
||||||
|
return self._getSpecialAttr('__ipow__')(*args, _callSync='off')
|
||||||
|
|
||||||
def __rshift__(self, *args):
|
def __rshift__(self, *args):
|
||||||
return self._getSpecialAttr('__rshift__')(*args)
|
return self._getSpecialAttr('__rshift__')(*args)
|
||||||
|
|
||||||
|
@ -679,6 +793,15 @@ class ObjectProxy(object):
|
||||||
def __floordiv__(self, *args):
|
def __floordiv__(self, *args):
|
||||||
return self._getSpecialAttr('__pow__')(*args)
|
return self._getSpecialAttr('__pow__')(*args)
|
||||||
|
|
||||||
|
def __irshift__(self, *args):
|
||||||
|
return self._getSpecialAttr('__rshift__')(*args, _callSync='off')
|
||||||
|
|
||||||
|
def __ilshift__(self, *args):
|
||||||
|
return self._getSpecialAttr('__lshift__')(*args, _callSync='off')
|
||||||
|
|
||||||
|
def __ifloordiv__(self, *args):
|
||||||
|
return self._getSpecialAttr('__pow__')(*args, _callSync='off')
|
||||||
|
|
||||||
def __eq__(self, *args):
|
def __eq__(self, *args):
|
||||||
return self._getSpecialAttr('__eq__')(*args)
|
return self._getSpecialAttr('__eq__')(*args)
|
||||||
|
|
||||||
|
@ -704,7 +827,16 @@ class ObjectProxy(object):
|
||||||
return self._getSpecialAttr('__or__')(*args)
|
return self._getSpecialAttr('__or__')(*args)
|
||||||
|
|
||||||
def __xor__(self, *args):
|
def __xor__(self, *args):
|
||||||
return self._getSpecialAttr('__or__')(*args)
|
return self._getSpecialAttr('__xor__')(*args)
|
||||||
|
|
||||||
|
def __iand__(self, *args):
|
||||||
|
return self._getSpecialAttr('__iand__')(*args, _callSync='off')
|
||||||
|
|
||||||
|
def __ior__(self, *args):
|
||||||
|
return self._getSpecialAttr('__ior__')(*args, _callSync='off')
|
||||||
|
|
||||||
|
def __ixor__(self, *args):
|
||||||
|
return self._getSpecialAttr('__ixor__')(*args, _callSync='off')
|
||||||
|
|
||||||
def __mod__(self, *args):
|
def __mod__(self, *args):
|
||||||
return self._getSpecialAttr('__mod__')(*args)
|
return self._getSpecialAttr('__mod__')(*args)
|
||||||
|
@ -746,6 +878,37 @@ class ObjectProxy(object):
|
||||||
return self._getSpecialAttr('__rmod__')(*args)
|
return self._getSpecialAttr('__rmod__')(*args)
|
||||||
|
|
||||||
class DeferredObjectProxy(ObjectProxy):
|
class DeferredObjectProxy(ObjectProxy):
|
||||||
|
"""
|
||||||
|
This class represents an attribute (or sub-attribute) of a proxied object.
|
||||||
|
It is used to speed up attribute requests. Take the following scenario::
|
||||||
|
|
||||||
|
rsys = proc._import('sys')
|
||||||
|
rsys.stdout.write('hello')
|
||||||
|
|
||||||
|
For this simple example, a total of 4 synchronous requests are made to
|
||||||
|
the remote process:
|
||||||
|
|
||||||
|
1) import sys
|
||||||
|
2) getattr(sys, 'stdout')
|
||||||
|
3) getattr(stdout, 'write')
|
||||||
|
4) write('hello')
|
||||||
|
|
||||||
|
This takes a lot longer than running the equivalent code locally. To
|
||||||
|
speed things up, we can 'defer' the two attribute lookups so they are
|
||||||
|
only carried out when neccessary::
|
||||||
|
|
||||||
|
rsys = proc._import('sys')
|
||||||
|
rsys._setProxyOptions(deferGetattr=True)
|
||||||
|
rsys.stdout.write('hello')
|
||||||
|
|
||||||
|
This example only makes two requests to the remote process; the two
|
||||||
|
attribute lookups immediately return DeferredObjectProxy instances
|
||||||
|
immediately without contacting the remote process. When the call
|
||||||
|
to write() is made, all attribute requests are processed at the same time.
|
||||||
|
|
||||||
|
Note that if the attributes requested do not exist on the remote object,
|
||||||
|
making the call to write() will raise an AttributeError.
|
||||||
|
"""
|
||||||
def __init__(self, parentProxy, attribute):
|
def __init__(self, parentProxy, attribute):
|
||||||
## can't set attributes directly because setattr is overridden.
|
## can't set attributes directly because setattr is overridden.
|
||||||
for k in ['_processId', '_typeStr', '_proxyId', '_handler']:
|
for k in ['_processId', '_typeStr', '_proxyId', '_handler']:
|
||||||
|
@ -756,4 +919,10 @@ class DeferredObjectProxy(ObjectProxy):
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ObjectProxy.__repr__(self) + '.' + '.'.join(self._attributes)
|
return ObjectProxy.__repr__(self) + '.' + '.'.join(self._attributes)
|
||||||
|
|
||||||
|
def _undefer(self):
|
||||||
|
"""
|
||||||
|
Return a non-deferred ObjectProxy referencing the same object
|
||||||
|
"""
|
||||||
|
return self._parent.__getattr__(self._attributes[-1], _deferGetattr=False)
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,18 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
||||||
self.keysPressed = {}
|
self.keysPressed = {}
|
||||||
self.keyTimer = QtCore.QTimer()
|
self.keyTimer = QtCore.QTimer()
|
||||||
self.keyTimer.timeout.connect(self.evalKeyState)
|
self.keyTimer.timeout.connect(self.evalKeyState)
|
||||||
|
|
||||||
|
self.makeCurrent()
|
||||||
|
|
||||||
def addItem(self, item):
|
def addItem(self, item):
|
||||||
self.items.append(item)
|
self.items.append(item)
|
||||||
if hasattr(item, 'initializeGL'):
|
if hasattr(item, 'initializeGL'):
|
||||||
self.makeCurrent()
|
self.makeCurrent()
|
||||||
item.initializeGL()
|
try:
|
||||||
|
item.initializeGL()
|
||||||
|
except:
|
||||||
|
self.checkOpenGLVersion('Error while adding item %s to GLViewWidget.' % str(item))
|
||||||
|
|
||||||
item._setView(self)
|
item._setView(self)
|
||||||
#print "set view", item, self, item.view()
|
#print "set view", item, self, item.view()
|
||||||
self.update()
|
self.update()
|
||||||
|
@ -100,20 +106,28 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
||||||
glPushAttrib(GL_ALL_ATTRIB_BITS)
|
glPushAttrib(GL_ALL_ATTRIB_BITS)
|
||||||
i.paint()
|
i.paint()
|
||||||
except:
|
except:
|
||||||
import sys
|
import pyqtgraph.debug
|
||||||
sys.excepthook(*sys.exc_info())
|
pyqtgraph.debug.printExc()
|
||||||
print("Error while drawing item", i)
|
msg = "Error while drawing item %s." % str(item)
|
||||||
|
ver = glGetString(GL_VERSION).split()[0]
|
||||||
|
if int(ver.split('.')[0]) < 2:
|
||||||
|
print(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver)
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
glPopAttrib(GL_ALL_ATTRIB_BITS)
|
glPopAttrib()
|
||||||
else:
|
else:
|
||||||
glMatrixMode(GL_MODELVIEW)
|
glMatrixMode(GL_MODELVIEW)
|
||||||
glPushMatrix()
|
glPushMatrix()
|
||||||
tr = i.transform()
|
try:
|
||||||
a = np.array(tr.copyDataTo()).reshape((4,4))
|
tr = i.transform()
|
||||||
glMultMatrixf(a.transpose())
|
a = np.array(tr.copyDataTo()).reshape((4,4))
|
||||||
self.drawItemTree(i)
|
glMultMatrixf(a.transpose())
|
||||||
glMatrixMode(GL_MODELVIEW)
|
self.drawItemTree(i)
|
||||||
glPopMatrix()
|
finally:
|
||||||
|
glMatrixMode(GL_MODELVIEW)
|
||||||
|
glPopMatrix()
|
||||||
|
|
||||||
|
|
||||||
def cameraPosition(self):
|
def cameraPosition(self):
|
||||||
|
@ -237,4 +251,15 @@ class GLViewWidget(QtOpenGL.QGLWidget):
|
||||||
else:
|
else:
|
||||||
self.keyTimer.stop()
|
self.keyTimer.stop()
|
||||||
|
|
||||||
|
def checkOpenGLVersion(self, msg):
|
||||||
|
## Only to be called from within exception handler.
|
||||||
|
ver = glGetString(GL_VERSION).split()[0]
|
||||||
|
if int(ver.split('.')[0]) < 2:
|
||||||
|
import pyqtgraph.debug
|
||||||
|
pyqtgraph.debug.printExc()
|
||||||
|
raise Exception(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|
16
opengl/glInfo.py
Normal file
16
opengl/glInfo.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL
|
||||||
|
from OpenGL.GL import *
|
||||||
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
|
class GLTest(QtOpenGL.QGLWidget):
|
||||||
|
def __init__(self):
|
||||||
|
QtOpenGL.QGLWidget.__init__(self)
|
||||||
|
self.makeCurrent()
|
||||||
|
print "GL version:", glGetString(GL_VERSION)
|
||||||
|
print "MAX_TEXTURE_SIZE:", glGetIntegerv(GL_MAX_TEXTURE_SIZE)
|
||||||
|
print "MAX_3D_TEXTURE_SIZE:", glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE)
|
||||||
|
print "Extensions:", glGetString(GL_EXTENSIONS)
|
||||||
|
|
||||||
|
GLTest()
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,11 @@ class GLVolumeItem(GLGraphicsItem):
|
||||||
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER)
|
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER)
|
||||||
shape = self.data.shape
|
shape = self.data.shape
|
||||||
|
|
||||||
|
## Test texture dimensions first
|
||||||
|
glTexImage3D(GL_PROXY_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
|
||||||
|
if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_WIDTH) == 0:
|
||||||
|
raise Exception("OpenGL failed to create 3D texture (%dx%dx%d); too large for this hardware." % shape[:3])
|
||||||
|
|
||||||
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((2,1,0,3)))
|
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((2,1,0,3)))
|
||||||
glDisable(GL_TEXTURE_3D)
|
glDisable(GL_TEXTURE_3D)
|
||||||
|
|
||||||
|
|
|
@ -131,11 +131,16 @@ class Parameter(QtCore.QObject):
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def childPath(self, child):
|
def childPath(self, child):
|
||||||
"""Return the path of parameter names from self to child."""
|
"""
|
||||||
|
Return the path of parameter names from self to child.
|
||||||
|
If child is not a (grand)child of self, return None.
|
||||||
|
"""
|
||||||
path = []
|
path = []
|
||||||
while child is not self:
|
while child is not self:
|
||||||
path.insert(0, child.name())
|
path.insert(0, child.name())
|
||||||
child = child.parent()
|
child = child.parent()
|
||||||
|
if child is None:
|
||||||
|
return None
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def setValue(self, value, blockSignal=None):
|
def setValue(self, value, blockSignal=None):
|
||||||
|
|
|
@ -113,4 +113,6 @@ class ParameterTree(TreeWidget):
|
||||||
sel[0].selected(True)
|
sel[0].selected(True)
|
||||||
return TreeWidget.selectionChanged(self, *args)
|
return TreeWidget.selectionChanged(self, *args)
|
||||||
|
|
||||||
|
def wheelEvent(self, ev):
|
||||||
|
self.clearSelection()
|
||||||
|
return TreeWidget.wheelEvent(self, ev)
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
## tests for ParameterTree
|
|
||||||
|
|
||||||
## make sure pyqtgraph is in path
|
|
||||||
import sys,os
|
|
||||||
md = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
sys.path.append(os.path.join(md, '..', '..'))
|
|
||||||
|
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
|
||||||
import collections, user
|
|
||||||
app = QtGui.QApplication([])
|
|
||||||
import pyqtgraph.parametertree.parameterTypes as pTypes
|
|
||||||
from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType
|
|
||||||
|
|
||||||
|
|
||||||
## test subclassing parameters
|
|
||||||
## This parameter automatically generates two child parameters which are always reciprocals of each other
|
|
||||||
class ComplexParameter(Parameter):
|
|
||||||
def __init__(self, **opts):
|
|
||||||
opts['type'] = 'bool'
|
|
||||||
opts['value'] = True
|
|
||||||
Parameter.__init__(self, **opts)
|
|
||||||
|
|
||||||
self.addChild({'name': 'A = 1/B', 'type': 'float', 'value': 7, 'suffix': 'Hz', 'siPrefix': True})
|
|
||||||
self.addChild({'name': 'B = 1/A', 'type': 'float', 'value': 1/7., 'suffix': 's', 'siPrefix': True})
|
|
||||||
self.a = self.param('A = 1/B')
|
|
||||||
self.b = self.param('B = 1/A')
|
|
||||||
self.a.sigValueChanged.connect(self.aChanged)
|
|
||||||
self.b.sigValueChanged.connect(self.bChanged)
|
|
||||||
|
|
||||||
def aChanged(self):
|
|
||||||
self.b.setValue(1.0 / self.a.value(), blockSignal=self.bChanged)
|
|
||||||
|
|
||||||
def bChanged(self):
|
|
||||||
self.a.setValue(1.0 / self.b.value(), blockSignal=self.aChanged)
|
|
||||||
|
|
||||||
|
|
||||||
## test add/remove
|
|
||||||
## this group includes a menu allowing the user to add new parameters into its child list
|
|
||||||
class ScalableGroup(pTypes.GroupParameter):
|
|
||||||
def __init__(self, **opts):
|
|
||||||
opts['type'] = 'group'
|
|
||||||
opts['addText'] = "Add"
|
|
||||||
opts['addList'] = ['str', 'float', 'int']
|
|
||||||
pTypes.GroupParameter.__init__(self, **opts)
|
|
||||||
|
|
||||||
def addNew(self, typ):
|
|
||||||
val = {
|
|
||||||
'str': '',
|
|
||||||
'float': 0.0,
|
|
||||||
'int': 0
|
|
||||||
}[typ]
|
|
||||||
self.addChild(dict(name="ScalableParam %d" % (len(self.childs)+1), type=typ, value=val, removable=True, renamable=True))
|
|
||||||
|
|
||||||
|
|
||||||
## test column spanning (widget sub-item that spans all columns)
|
|
||||||
class TextParameterItem(pTypes.WidgetParameterItem):
|
|
||||||
def __init__(self, param, depth):
|
|
||||||
pTypes.WidgetParameterItem.__init__(self, param, depth)
|
|
||||||
self.subItem = QtGui.QTreeWidgetItem()
|
|
||||||
self.addChild(self.subItem)
|
|
||||||
|
|
||||||
def treeWidgetChanged(self):
|
|
||||||
self.treeWidget().setFirstItemColumnSpanned(self.subItem, True)
|
|
||||||
self.treeWidget().setItemWidget(self.subItem, 0, self.textBox)
|
|
||||||
self.setExpanded(True)
|
|
||||||
|
|
||||||
def makeWidget(self):
|
|
||||||
self.textBox = QtGui.QTextEdit()
|
|
||||||
self.textBox.setMaximumHeight(100)
|
|
||||||
self.textBox.value = lambda: str(self.textBox.toPlainText())
|
|
||||||
self.textBox.setValue = self.textBox.setPlainText
|
|
||||||
self.textBox.sigChanged = self.textBox.textChanged
|
|
||||||
return self.textBox
|
|
||||||
|
|
||||||
class TextParameter(Parameter):
|
|
||||||
type = 'text'
|
|
||||||
itemClass = TextParameterItem
|
|
||||||
|
|
||||||
registerParameterType('text', TextParameter)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
params = [
|
|
||||||
{'name': 'Group 0', 'type': 'group', 'children': [
|
|
||||||
{'name': 'Param 1', 'type': 'int', 'value': 10},
|
|
||||||
{'name': 'Param 2', 'type': 'float', 'value': 10},
|
|
||||||
]},
|
|
||||||
{'name': 'Group 1', 'type': 'group', 'children': [
|
|
||||||
{'name': 'Param 1.1', 'type': 'float', 'value': 1.2e-6, 'dec': True, 'siPrefix': True, 'suffix': 'V'},
|
|
||||||
{'name': 'Param 1.2', 'type': 'float', 'value': 1.2e6, 'dec': True, 'siPrefix': True, 'suffix': 'Hz'},
|
|
||||||
{'name': 'Group 1.3', 'type': 'group', 'children': [
|
|
||||||
{'name': 'Param 1.3.1', 'type': 'int', 'value': 11, 'limits': (-7, 15), 'default': -6},
|
|
||||||
{'name': 'Param 1.3.2', 'type': 'float', 'value': 1.2e6, 'dec': True, 'siPrefix': True, 'suffix': 'Hz', 'readonly': True},
|
|
||||||
]},
|
|
||||||
{'name': 'Param 1.4', 'type': 'str', 'value': "hi"},
|
|
||||||
{'name': 'Param 1.5', 'type': 'list', 'values': [1,2,3], 'value': 2},
|
|
||||||
{'name': 'Param 1.6', 'type': 'list', 'values': {"one": 1, "two": 2, "three": 3}, 'value': 2},
|
|
||||||
ComplexParameter(name='ComplexParam'),
|
|
||||||
ScalableGroup(name="ScalableGroup", children=[
|
|
||||||
{'name': 'ScalableParam 1', 'type': 'str', 'value': "hi"},
|
|
||||||
{'name': 'ScalableParam 2', 'type': 'str', 'value': "hi"},
|
|
||||||
|
|
||||||
])
|
|
||||||
]},
|
|
||||||
{'name': 'Param 5', 'type': 'bool', 'value': True, 'tip': "This is a checkbox"},
|
|
||||||
{'name': 'Param 6', 'type': 'color', 'value': "FF0", 'tip': "This is a color button. It cam be renamed.", 'renamable': True},
|
|
||||||
{'name': 'TextParam', 'type': 'text', 'value': 'Some text...'},
|
|
||||||
]
|
|
||||||
|
|
||||||
#p = pTypes.ParameterSet("params", params)
|
|
||||||
p = Parameter(name='params', type='group', children=params)
|
|
||||||
def change(param, changes):
|
|
||||||
print("tree changes:")
|
|
||||||
for param, change, data in changes:
|
|
||||||
print(" [" + '.'.join(p.childPath(param))+ "] ", change, data)
|
|
||||||
|
|
||||||
p.sigTreeStateChanged.connect(change)
|
|
||||||
|
|
||||||
|
|
||||||
t = ParameterTree()
|
|
||||||
t.setParameters(p, showTop=False)
|
|
||||||
t.show()
|
|
||||||
t.resize(400,600)
|
|
||||||
t2 = ParameterTree()
|
|
||||||
t2.setParameters(p, showTop=False)
|
|
||||||
t2.show()
|
|
||||||
t2.resize(400,600)
|
|
||||||
|
|
||||||
import sys
|
|
||||||
if sys.flags.interactive == 0:
|
|
||||||
app.exec_()
|
|
|
@ -1,4 +1,5 @@
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
from pyqtgraph.python2_3 import asUnicode
|
||||||
from .Parameter import Parameter, registerParameterType
|
from .Parameter import Parameter, registerParameterType
|
||||||
from .ParameterItem import ParameterItem
|
from .ParameterItem import ParameterItem
|
||||||
from pyqtgraph.widgets.SpinBox import SpinBox
|
from pyqtgraph.widgets.SpinBox import SpinBox
|
||||||
|
@ -348,6 +349,7 @@ class GroupParameterItem(ParameterItem):
|
||||||
|
|
||||||
def treeWidgetChanged(self):
|
def treeWidgetChanged(self):
|
||||||
ParameterItem.treeWidgetChanged(self)
|
ParameterItem.treeWidgetChanged(self)
|
||||||
|
self.treeWidget().setFirstItemColumnSpanned(self, True)
|
||||||
if self.addItem is not None:
|
if self.addItem is not None:
|
||||||
self.treeWidget().setItemWidget(self.addItem, 0, self.addWidgetBox)
|
self.treeWidget().setItemWidget(self.addItem, 0, self.addWidgetBox)
|
||||||
self.treeWidget().setFirstItemColumnSpanned(self.addItem, True)
|
self.treeWidget().setFirstItemColumnSpanned(self.addItem, True)
|
||||||
|
|
13
python2_3.py
13
python2_3.py
|
@ -42,8 +42,9 @@ def sortList(l, cmpFunc):
|
||||||
if sys.version_info[0] == 3:
|
if sys.version_info[0] == 3:
|
||||||
import builtins
|
import builtins
|
||||||
builtins.basestring = str
|
builtins.basestring = str
|
||||||
builtins.asUnicode = asUnicode
|
#builtins.asUnicode = asUnicode
|
||||||
builtins.sortList = sortList
|
#builtins.sortList = sortList
|
||||||
|
basestring = str
|
||||||
def cmp(a,b):
|
def cmp(a,b):
|
||||||
if a>b:
|
if a>b:
|
||||||
return 1
|
return 1
|
||||||
|
@ -52,7 +53,7 @@ if sys.version_info[0] == 3:
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
builtins.cmp = cmp
|
builtins.cmp = cmp
|
||||||
else:
|
#else: ## don't use __builtin__ -- this confuses things like pyshell and ActiveState's lazy import recipe
|
||||||
import __builtin__
|
#import __builtin__
|
||||||
__builtin__.asUnicode = asUnicode
|
#__builtin__.asUnicode = asUnicode
|
||||||
__builtin__.sortList = sortList
|
#__builtin__.sortList = sortList
|
||||||
|
|
|
@ -34,6 +34,7 @@ def reloadAll(prefix=None, debug=False):
|
||||||
- Skips reload if the file has not been updated (if .pyc is newer than .py)
|
- Skips reload if the file has not been updated (if .pyc is newer than .py)
|
||||||
- if prefix is None, checks all loaded modules
|
- if prefix is None, checks all loaded modules
|
||||||
"""
|
"""
|
||||||
|
failed = []
|
||||||
for modName, mod in list(sys.modules.items()): ## don't use iteritems; size may change during reload
|
for modName, mod in list(sys.modules.items()): ## don't use iteritems; size may change during reload
|
||||||
if not inspect.ismodule(mod):
|
if not inspect.ismodule(mod):
|
||||||
continue
|
continue
|
||||||
|
@ -58,7 +59,10 @@ def reloadAll(prefix=None, debug=False):
|
||||||
reload(mod, debug=debug)
|
reload(mod, debug=debug)
|
||||||
except:
|
except:
|
||||||
printExc("Error while reloading module %s, skipping\n" % mod)
|
printExc("Error while reloading module %s, skipping\n" % mod)
|
||||||
|
failed.append(mod.__name__)
|
||||||
|
|
||||||
|
if len(failed) > 0:
|
||||||
|
raise Exception("Some modules failed to reload: %s" % ', '.join(failed))
|
||||||
|
|
||||||
def reload(module, debug=False, lists=False, dicts=False):
|
def reload(module, debug=False, lists=False, dicts=False):
|
||||||
"""Replacement for the builtin reload function:
|
"""Replacement for the builtin reload function:
|
||||||
|
|
|
@ -15,7 +15,7 @@ class GradientWidget(GraphicsView):
|
||||||
|
|
||||||
def __init__(self, parent=None, orientation='bottom', *args, **kargs):
|
def __init__(self, parent=None, orientation='bottom', *args, **kargs):
|
||||||
GraphicsView.__init__(self, parent, useOpenGL=False, background=None)
|
GraphicsView.__init__(self, parent, useOpenGL=False, background=None)
|
||||||
self.maxDim = 27
|
self.maxDim = 31
|
||||||
kargs['tickPen'] = 'k'
|
kargs['tickPen'] = 'k'
|
||||||
self.item = GradientEditorItem(*args, **kargs)
|
self.item = GradientEditorItem(*args, **kargs)
|
||||||
self.item.sigGradientChanged.connect(self.sigGradientChanged)
|
self.item.sigGradientChanged.connect(self.sigGradientChanged)
|
||||||
|
@ -24,7 +24,7 @@ class GradientWidget(GraphicsView):
|
||||||
self.setCacheMode(self.CacheNone)
|
self.setCacheMode(self.CacheNone)
|
||||||
self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing)
|
self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing)
|
||||||
self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain)
|
self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain)
|
||||||
self.setBackgroundRole(QtGui.QPalette.NoRole)
|
#self.setBackgroundRole(QtGui.QPalette.NoRole)
|
||||||
#self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
|
#self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
|
||||||
#self.setAutoFillBackground(False)
|
#self.setAutoFillBackground(False)
|
||||||
#self.setAttribute(QtCore.Qt.WA_PaintOnScreen, False)
|
#self.setAttribute(QtCore.Qt.WA_PaintOnScreen, False)
|
||||||
|
|
|
@ -6,6 +6,7 @@ Distributed under MIT/X11 license. See license.txt for more infomation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pyqtgraph.Qt import QtCore, QtGui
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pyqtgraph.Qt import QtOpenGL
|
from pyqtgraph.Qt import QtOpenGL
|
||||||
|
@ -13,12 +14,8 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAVE_OPENGL = False
|
HAVE_OPENGL = False
|
||||||
|
|
||||||
#from numpy import vstack
|
|
||||||
#import time
|
|
||||||
from pyqtgraph.Point import Point
|
from pyqtgraph.Point import Point
|
||||||
#from vector import *
|
|
||||||
import sys, os
|
import sys, os
|
||||||
#import debug
|
|
||||||
from .FileDialog import FileDialog
|
from .FileDialog import FileDialog
|
||||||
from pyqtgraph.GraphicsScene import GraphicsScene
|
from pyqtgraph.GraphicsScene import GraphicsScene
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -29,6 +26,20 @@ import pyqtgraph
|
||||||
__all__ = ['GraphicsView']
|
__all__ = ['GraphicsView']
|
||||||
|
|
||||||
class GraphicsView(QtGui.QGraphicsView):
|
class GraphicsView(QtGui.QGraphicsView):
|
||||||
|
"""Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the
|
||||||
|
viewed coordinate range. Also automatically creates a GraphicsScene and a central QGraphicsWidget
|
||||||
|
that is automatically scaled to the full view geometry.
|
||||||
|
|
||||||
|
This widget is the basis for :class:`PlotWidget <pyqtgraph.PlotWidget>`,
|
||||||
|
:class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>`, and the view widget in
|
||||||
|
:class:`ImageView <pyqtgraph.ImageView>`.
|
||||||
|
|
||||||
|
By default, the view coordinate system matches the widget's pixel coordinates and
|
||||||
|
automatically updates when the view is resized. This can be overridden by setting
|
||||||
|
autoPixelRange=False. The exact visible range can be set with setRange().
|
||||||
|
|
||||||
|
The view can be panned using the middle mouse button and scaled using the right mouse button if
|
||||||
|
enabled via enableMouse() (but ordinarily, we use ViewBox for this functionality)."""
|
||||||
|
|
||||||
sigRangeChanged = QtCore.Signal(object, object)
|
sigRangeChanged = QtCore.Signal(object, object)
|
||||||
sigMouseReleased = QtCore.Signal(object)
|
sigMouseReleased = QtCore.Signal(object)
|
||||||
|
@ -37,17 +48,25 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
sigScaleChanged = QtCore.Signal(object)
|
sigScaleChanged = QtCore.Signal(object)
|
||||||
lastFileDir = None
|
lastFileDir = None
|
||||||
|
|
||||||
def __init__(self, parent=None, useOpenGL=None, background='k'):
|
def __init__(self, parent=None, useOpenGL=None, background='default'):
|
||||||
"""Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the
|
"""
|
||||||
viewed coordinate range. Also automatically creates a QGraphicsScene and a central QGraphicsWidget
|
============ ============================================================
|
||||||
that is automatically scaled to the full view geometry.
|
Arguments:
|
||||||
|
parent Optional parent widget
|
||||||
|
useOpenGL If True, the GraphicsView will use OpenGL to do all of its
|
||||||
|
rendering. This can improve performance on some systems,
|
||||||
|
but may also introduce bugs (the combination of
|
||||||
|
QGraphicsView and QGLWidget is still an 'experimental'
|
||||||
|
feature of Qt)
|
||||||
|
background Set the background color of the GraphicsView. Accepts any
|
||||||
|
single argument accepted by
|
||||||
|
:func:`mkColor <pyqtgraph.mkColor>`. By
|
||||||
|
default, the background color is determined using the
|
||||||
|
'backgroundColor' configuration option (see
|
||||||
|
:func:`setConfigOption <pyqtgraph.setConfigOption>`.
|
||||||
|
============ ============================================================
|
||||||
|
"""
|
||||||
|
|
||||||
By default, the view coordinate system matches the widget's pixel coordinates and
|
|
||||||
automatically updates when the view is resized. This can be overridden by setting
|
|
||||||
autoPixelRange=False. The exact visible range can be set with setRange().
|
|
||||||
|
|
||||||
The view can be panned using the middle mouse button and scaled using the right mouse button if
|
|
||||||
enabled via enableMouse() (but ordinarily, we use ViewBox for this functionality)."""
|
|
||||||
self.closed = False
|
self.closed = False
|
||||||
|
|
||||||
QtGui.QGraphicsView.__init__(self, parent)
|
QtGui.QGraphicsView.__init__(self, parent)
|
||||||
|
@ -62,9 +81,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
## This might help, but it's probably dangerous in the general case..
|
## This might help, but it's probably dangerous in the general case..
|
||||||
#self.setOptimizationFlag(self.DontSavePainterState, True)
|
#self.setOptimizationFlag(self.DontSavePainterState, True)
|
||||||
|
|
||||||
if background is not None:
|
self.setBackground(background)
|
||||||
brush = fn.mkBrush(background)
|
|
||||||
self.setBackgroundBrush(brush)
|
|
||||||
|
|
||||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||||
self.setFrameShape(QtGui.QFrame.NoFrame)
|
self.setFrameShape(QtGui.QFrame.NoFrame)
|
||||||
|
@ -75,13 +92,10 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate)
|
self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate)
|
||||||
|
|
||||||
|
|
||||||
#self.setSceneRect(QtCore.QRectF(-1e10, -1e10, 2e10, 2e10))
|
|
||||||
|
|
||||||
self.lockedViewports = []
|
self.lockedViewports = []
|
||||||
self.lastMousePos = None
|
self.lastMousePos = None
|
||||||
self.setMouseTracking(True)
|
self.setMouseTracking(True)
|
||||||
self.aspectLocked = False
|
self.aspectLocked = False
|
||||||
#self.yInverted = True
|
|
||||||
self.range = QtCore.QRectF(0, 0, 1, 1)
|
self.range = QtCore.QRectF(0, 0, 1, 1)
|
||||||
self.autoPixelRange = True
|
self.autoPixelRange = True
|
||||||
self.currentItem = None
|
self.currentItem = None
|
||||||
|
@ -90,6 +104,11 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
self.sceneObj = GraphicsScene()
|
self.sceneObj = GraphicsScene()
|
||||||
self.setScene(self.sceneObj)
|
self.setScene(self.sceneObj)
|
||||||
|
|
||||||
|
## Workaround for PySide crash
|
||||||
|
## This ensures that the scene will outlive the view.
|
||||||
|
if pyqtgraph.Qt.USE_PYSIDE:
|
||||||
|
self.sceneObj._view_ref_workaround = self
|
||||||
|
|
||||||
## by default we set up a central widget with a grid layout.
|
## by default we set up a central widget with a grid layout.
|
||||||
## this can be replaced if needed.
|
## this can be replaced if needed.
|
||||||
self.centralWidget = None
|
self.centralWidget = None
|
||||||
|
@ -101,15 +120,25 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
|
self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False)
|
||||||
self.clickAccepted = False
|
self.clickAccepted = False
|
||||||
|
|
||||||
#def paintEvent(self, *args):
|
def setBackground(self, background):
|
||||||
#prof = debug.Profiler('GraphicsView.paintEvent '+str(id(self)), disabled=False)
|
"""
|
||||||
#QtGui.QGraphicsView.paintEvent(self, *args)
|
Set the background color of the GraphicsView.
|
||||||
#prof.finish()
|
To use the defaults specified py pyqtgraph.setConfigOption, use background='default'.
|
||||||
|
To make the background transparent, use background=None.
|
||||||
|
"""
|
||||||
|
self._background = background
|
||||||
|
if background == 'default':
|
||||||
|
background = pyqtgraph.getConfigOption('background')
|
||||||
|
if background is None:
|
||||||
|
self.setBackgroundRole(QtGui.QPalette.NoRole)
|
||||||
|
else:
|
||||||
|
brush = fn.mkBrush(background)
|
||||||
|
self.setBackgroundBrush(brush)
|
||||||
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.centralWidget = None
|
self.centralWidget = None
|
||||||
self.scene().clear()
|
self.scene().clear()
|
||||||
#print " ", self.scene().itemCount()
|
|
||||||
self.currentItem = None
|
self.currentItem = None
|
||||||
self.sceneObj = None
|
self.sceneObj = None
|
||||||
self.closed = True
|
self.closed = True
|
||||||
|
@ -123,11 +152,9 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
else:
|
else:
|
||||||
v = QtGui.QWidget()
|
v = QtGui.QWidget()
|
||||||
|
|
||||||
#v.setStyleSheet("background-color: #000000;")
|
|
||||||
self.setViewport(v)
|
self.setViewport(v)
|
||||||
|
|
||||||
def keyPressEvent(self, ev):
|
def keyPressEvent(self, ev):
|
||||||
#QtGui.QGraphicsView.keyPressEvent(self, ev)
|
|
||||||
self.scene().keyPressEvent(ev) ## bypass view, hand event directly to scene
|
self.scene().keyPressEvent(ev) ## bypass view, hand event directly to scene
|
||||||
## (view likes to eat arrow key events)
|
## (view likes to eat arrow key events)
|
||||||
|
|
||||||
|
@ -136,7 +163,8 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
return self.setCentralWidget(item)
|
return self.setCentralWidget(item)
|
||||||
|
|
||||||
def setCentralWidget(self, item):
|
def setCentralWidget(self, item):
|
||||||
"""Sets a QGraphicsWidget to automatically fill the entire view."""
|
"""Sets a QGraphicsWidget to automatically fill the entire view (the item will be automatically
|
||||||
|
resize whenever the GraphicsView is resized)."""
|
||||||
if self.centralWidget is not None:
|
if self.centralWidget is not None:
|
||||||
self.scene().removeItem(self.centralWidget)
|
self.scene().removeItem(self.centralWidget)
|
||||||
self.centralWidget = item
|
self.centralWidget = item
|
||||||
|
@ -162,15 +190,18 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
return
|
return
|
||||||
if self.autoPixelRange:
|
if self.autoPixelRange:
|
||||||
self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height())
|
self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height())
|
||||||
GraphicsView.setRange(self, self.range, padding=0, disableAutoPixel=False)
|
GraphicsView.setRange(self, self.range, padding=0, disableAutoPixel=False) ## we do this because some subclasses like to redefine setRange in an incompatible way.
|
||||||
self.updateMatrix()
|
self.updateMatrix()
|
||||||
|
|
||||||
def updateMatrix(self, propagate=True):
|
def updateMatrix(self, propagate=True):
|
||||||
self.setSceneRect(self.range)
|
self.setSceneRect(self.range)
|
||||||
if self.aspectLocked:
|
if self.autoPixelRange:
|
||||||
self.fitInView(self.range, QtCore.Qt.KeepAspectRatio)
|
self.resetTransform()
|
||||||
else:
|
else:
|
||||||
self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio)
|
if self.aspectLocked:
|
||||||
|
self.fitInView(self.range, QtCore.Qt.KeepAspectRatio)
|
||||||
|
else:
|
||||||
|
self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio)
|
||||||
|
|
||||||
self.sigRangeChanged.emit(self, self.range)
|
self.sigRangeChanged.emit(self, self.range)
|
||||||
|
|
||||||
|
@ -196,11 +227,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
scale = [sx, sy]
|
scale = [sx, sy]
|
||||||
if self.aspectLocked:
|
if self.aspectLocked:
|
||||||
scale[0] = scale[1]
|
scale[0] = scale[1]
|
||||||
#adj = (self.range.width()*0.5*(1.0-(1.0/scale[0])), self.range.height()*0.5*(1.0-(1.0/scale[1])))
|
|
||||||
#print "======\n", scale, adj
|
|
||||||
#print self.range
|
|
||||||
#self.range.adjust(adj[0], adj[1], -adj[0], -adj[1])
|
|
||||||
#print self.range
|
|
||||||
|
|
||||||
if self.scaleCenter:
|
if self.scaleCenter:
|
||||||
center = None
|
center = None
|
||||||
|
@ -270,13 +296,6 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
r1.setBottom(r.bottom())
|
r1.setBottom(r.bottom())
|
||||||
GraphicsView.setRange(self, r1, padding=[0, padding], propagate=False)
|
GraphicsView.setRange(self, r1, padding=[0, padding], propagate=False)
|
||||||
|
|
||||||
#def invertY(self, invert=True):
|
|
||||||
##if self.yInverted != invert:
|
|
||||||
##self.scale[1] *= -1.
|
|
||||||
#self.yInverted = invert
|
|
||||||
#self.updateMatrix()
|
|
||||||
|
|
||||||
|
|
||||||
def wheelEvent(self, ev):
|
def wheelEvent(self, ev):
|
||||||
QtGui.QGraphicsView.wheelEvent(self, ev)
|
QtGui.QGraphicsView.wheelEvent(self, ev)
|
||||||
if not self.mouseEnabled:
|
if not self.mouseEnabled:
|
||||||
|
@ -289,39 +308,11 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
def setAspectLocked(self, s):
|
def setAspectLocked(self, s):
|
||||||
self.aspectLocked = s
|
self.aspectLocked = s
|
||||||
|
|
||||||
#def mouseDoubleClickEvent(self, ev):
|
|
||||||
#QtGui.QGraphicsView.mouseDoubleClickEvent(self, ev)
|
|
||||||
#pass
|
|
||||||
|
|
||||||
### This function is here because interactive mode is disabled due to bugs.
|
|
||||||
#def graphicsSceneEvent(self, ev, pev=None, fev=None):
|
|
||||||
#ev1 = GraphicsSceneMouseEvent()
|
|
||||||
#ev1.setPos(QtCore.QPointF(ev.pos().x(), ev.pos().y()))
|
|
||||||
#ev1.setButtons(ev.buttons())
|
|
||||||
#ev1.setButton(ev.button())
|
|
||||||
#ev1.setModifiers(ev.modifiers())
|
|
||||||
#ev1.setScenePos(self.mapToScene(QtCore.QPoint(ev.pos())))
|
|
||||||
#if pev is not None:
|
|
||||||
#ev1.setLastPos(pev.pos())
|
|
||||||
#ev1.setLastScenePos(pev.scenePos())
|
|
||||||
#ev1.setLastScreenPos(pev.screenPos())
|
|
||||||
#if fev is not None:
|
|
||||||
#ev1.setButtonDownPos(fev.pos())
|
|
||||||
#ev1.setButtonDownScenePos(fev.scenePos())
|
|
||||||
#ev1.setButtonDownScreenPos(fev.screenPos())
|
|
||||||
#return ev1
|
|
||||||
|
|
||||||
def leaveEvent(self, ev):
|
def leaveEvent(self, ev):
|
||||||
self.scene().leaveEvent(ev) ## inform scene when mouse leaves
|
self.scene().leaveEvent(ev) ## inform scene when mouse leaves
|
||||||
|
|
||||||
def mousePressEvent(self, ev):
|
def mousePressEvent(self, ev):
|
||||||
QtGui.QGraphicsView.mousePressEvent(self, ev)
|
QtGui.QGraphicsView.mousePressEvent(self, ev)
|
||||||
|
|
||||||
#print "Press over:"
|
|
||||||
#for i in self.items(ev.pos()):
|
|
||||||
# print i.zValue(), int(i.acceptedMouseButtons()), i, i.scenePos()
|
|
||||||
#print "Event accepted:", ev.isAccepted()
|
|
||||||
#print "Grabber:", self.scene().mouseGrabberItem()
|
|
||||||
|
|
||||||
|
|
||||||
if not self.mouseEnabled:
|
if not self.mouseEnabled:
|
||||||
|
@ -333,39 +324,14 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
self.scene().clearSelection()
|
self.scene().clearSelection()
|
||||||
return ## Everything below disabled for now..
|
return ## Everything below disabled for now..
|
||||||
|
|
||||||
#self.currentItem = None
|
|
||||||
#maxZ = None
|
|
||||||
#for i in self.items(ev.pos()):
|
|
||||||
#if maxZ is None or maxZ < i.zValue():
|
|
||||||
#self.currentItem = i
|
|
||||||
#maxZ = i.zValue()
|
|
||||||
#print "make event"
|
|
||||||
#self.pev = self.graphicsSceneEvent(ev)
|
|
||||||
#self.fev = self.pev
|
|
||||||
#if self.currentItem is not None:
|
|
||||||
#self.currentItem.mousePressEvent(self.pev)
|
|
||||||
##self.clearMouse()
|
|
||||||
##self.mouseTrail.append(Point(self.mapToScene(ev.pos())))
|
|
||||||
#self.emit(QtCore.SIGNAL("mousePressed(PyQt_PyObject)"), self.mouseTrail)
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, ev):
|
def mouseReleaseEvent(self, ev):
|
||||||
QtGui.QGraphicsView.mouseReleaseEvent(self, ev)
|
QtGui.QGraphicsView.mouseReleaseEvent(self, ev)
|
||||||
if not self.mouseEnabled:
|
if not self.mouseEnabled:
|
||||||
return
|
return
|
||||||
#self.mouseTrail.append(Point(self.mapToScene(ev.pos())))
|
|
||||||
#self.emit(QtCore.SIGNAL("mouseReleased"), ev)
|
|
||||||
self.sigMouseReleased.emit(ev)
|
self.sigMouseReleased.emit(ev)
|
||||||
self.lastButtonReleased = ev.button()
|
self.lastButtonReleased = ev.button()
|
||||||
return ## Everything below disabled for now..
|
return ## Everything below disabled for now..
|
||||||
|
|
||||||
##self.mouseTrail.append(Point(self.mapToScene(ev.pos())))
|
|
||||||
#self.emit(QtCore.SIGNAL("mouseReleased(PyQt_PyObject)"), self.mouseTrail)
|
|
||||||
#if self.currentItem is not None:
|
|
||||||
#pev = self.graphicsSceneEvent(ev, self.pev, self.fev)
|
|
||||||
#self.pev = pev
|
|
||||||
#self.currentItem.mouseReleaseEvent(pev)
|
|
||||||
#self.currentItem = None
|
|
||||||
|
|
||||||
def mouseMoveEvent(self, ev):
|
def mouseMoveEvent(self, ev):
|
||||||
if self.lastMousePos is None:
|
if self.lastMousePos is None:
|
||||||
self.lastMousePos = Point(ev.pos())
|
self.lastMousePos = Point(ev.pos())
|
||||||
|
@ -375,10 +341,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
QtGui.QGraphicsView.mouseMoveEvent(self, ev)
|
QtGui.QGraphicsView.mouseMoveEvent(self, ev)
|
||||||
if not self.mouseEnabled:
|
if not self.mouseEnabled:
|
||||||
return
|
return
|
||||||
#self.emit(QtCore.SIGNAL("sceneMouseMoved(PyQt_PyObject)"), self.mapToScene(ev.pos()))
|
|
||||||
self.sigSceneMouseMoved.emit(self.mapToScene(ev.pos()))
|
self.sigSceneMouseMoved.emit(self.mapToScene(ev.pos()))
|
||||||
#print "moved. Grabber:", self.scene().mouseGrabberItem()
|
|
||||||
|
|
||||||
|
|
||||||
if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it.
|
if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it.
|
||||||
return
|
return
|
||||||
|
@ -386,10 +349,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
if ev.buttons() == QtCore.Qt.RightButton:
|
if ev.buttons() == QtCore.Qt.RightButton:
|
||||||
delta = Point(np.clip(delta[0], -50, 50), np.clip(-delta[1], -50, 50))
|
delta = Point(np.clip(delta[0], -50, 50), np.clip(-delta[1], -50, 50))
|
||||||
scale = 1.01 ** delta
|
scale = 1.01 ** delta
|
||||||
#if self.yInverted:
|
|
||||||
#scale[0] = 1. / scale[0]
|
|
||||||
self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos))
|
self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos))
|
||||||
#self.emit(QtCore.SIGNAL('regionChanged(QRectF)'), self.range)
|
|
||||||
self.sigRangeChanged.emit(self, self.range)
|
self.sigRangeChanged.emit(self, self.range)
|
||||||
|
|
||||||
elif ev.buttons() in [QtCore.Qt.MidButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button.
|
elif ev.buttons() in [QtCore.Qt.MidButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button.
|
||||||
|
@ -397,23 +357,8 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
tr = -delta * px
|
tr = -delta * px
|
||||||
|
|
||||||
self.translate(tr[0], tr[1])
|
self.translate(tr[0], tr[1])
|
||||||
#self.emit(QtCore.SIGNAL('regionChanged(QRectF)'), self.range)
|
|
||||||
self.sigRangeChanged.emit(self, self.range)
|
self.sigRangeChanged.emit(self, self.range)
|
||||||
|
|
||||||
#return ## Everything below disabled for now..
|
|
||||||
|
|
||||||
##self.mouseTrail.append(Point(self.mapToScene(ev.pos())))
|
|
||||||
#if self.currentItem is not None:
|
|
||||||
#pev = self.graphicsSceneEvent(ev, self.pev, self.fev)
|
|
||||||
#self.pev = pev
|
|
||||||
#self.currentItem.mouseMoveEvent(pev)
|
|
||||||
|
|
||||||
#def paintEvent(self, ev):
|
|
||||||
#prof = debug.Profiler('GraphicsView.paintEvent (0x%x)' % id(self))
|
|
||||||
#QtGui.QGraphicsView.paintEvent(self, ev)
|
|
||||||
#prof.finish()
|
|
||||||
|
|
||||||
|
|
||||||
def pixelSize(self):
|
def pixelSize(self):
|
||||||
"""Return vector with the length and width of one view pixel in scene coordinates"""
|
"""Return vector with the length and width of one view pixel in scene coordinates"""
|
||||||
p0 = Point(0,0)
|
p0 = Point(0,0)
|
||||||
|
@ -423,80 +368,7 @@ class GraphicsView(QtGui.QGraphicsView):
|
||||||
p11 = tr.map(p1)
|
p11 = tr.map(p1)
|
||||||
return Point(p11 - p01)
|
return Point(p11 - p01)
|
||||||
|
|
||||||
|
|
||||||
#def writeSvg(self, fileName=None):
|
|
||||||
#if fileName is None:
|
|
||||||
#self.fileDialog = FileDialog()
|
|
||||||
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
|
||||||
#self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
|
||||||
#if GraphicsView.lastFileDir is not None:
|
|
||||||
#self.fileDialog.setDirectory(GraphicsView.lastFileDir)
|
|
||||||
#self.fileDialog.show()
|
|
||||||
#self.fileDialog.fileSelected.connect(self.writeSvg)
|
|
||||||
#return
|
|
||||||
#fileName = str(fileName)
|
|
||||||
#GraphicsView.lastFileDir = os.path.split(fileName)[0]
|
|
||||||
#self.svg = QtSvg.QSvgGenerator()
|
|
||||||
#self.svg.setFileName(fileName)
|
|
||||||
#self.svg.setSize(self.size())
|
|
||||||
#self.svg.setResolution(600)
|
|
||||||
#painter = QtGui.QPainter(self.svg)
|
|
||||||
#self.render(painter)
|
|
||||||
|
|
||||||
#def writeImage(self, fileName=None):
|
|
||||||
#if fileName is None:
|
|
||||||
#self.fileDialog = FileDialog()
|
|
||||||
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
|
||||||
#self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) ## this is the line that makes the fileDialog not show on mac
|
|
||||||
#if GraphicsView.lastFileDir is not None:
|
|
||||||
#self.fileDialog.setDirectory(GraphicsView.lastFileDir)
|
|
||||||
#self.fileDialog.show()
|
|
||||||
#self.fileDialog.fileSelected.connect(self.writeImage)
|
|
||||||
#return
|
|
||||||
#fileName = str(fileName)
|
|
||||||
#GraphicsView.lastFileDir = os.path.split(fileName)[0]
|
|
||||||
#self.png = QtGui.QImage(self.size(), QtGui.QImage.Format_ARGB32)
|
|
||||||
#painter = QtGui.QPainter(self.png)
|
|
||||||
#rh = self.renderHints()
|
|
||||||
#self.setRenderHints(QtGui.QPainter.Antialiasing)
|
|
||||||
#self.render(painter)
|
|
||||||
#self.setRenderHints(rh)
|
|
||||||
#self.png.save(fileName)
|
|
||||||
|
|
||||||
#def writePs(self, fileName=None):
|
|
||||||
#if fileName is None:
|
|
||||||
#self.fileDialog = FileDialog()
|
|
||||||
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
|
||||||
#self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
|
||||||
#self.fileDialog.show()
|
|
||||||
#self.fileDialog.fileSelected.connect(self.writePs)
|
|
||||||
#return
|
|
||||||
##if fileName is None:
|
|
||||||
## fileName = str(QtGui.QFileDialog.getSaveFileName())
|
|
||||||
#printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
|
|
||||||
#printer.setOutputFileName(fileName)
|
|
||||||
#painter = QtGui.QPainter(printer)
|
|
||||||
#self.render(painter)
|
|
||||||
#painter.end()
|
|
||||||
|
|
||||||
def dragEnterEvent(self, ev):
|
def dragEnterEvent(self, ev):
|
||||||
ev.ignore() ## not sure why, but for some reason this class likes to consume drag events
|
ev.ignore() ## not sure why, but for some reason this class likes to consume drag events
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#def getFreehandLine(self):
|
|
||||||
|
|
||||||
## Wait for click
|
|
||||||
#self.clearMouse()
|
|
||||||
#while self.lastButtonReleased != QtCore.Qt.LeftButton:
|
|
||||||
#QtGui.qApp.sendPostedEvents()
|
|
||||||
#QtGui.qApp.processEvents()
|
|
||||||
#time.sleep(0.01)
|
|
||||||
#fl = vstack(self.mouseTrail)
|
|
||||||
#return fl
|
|
||||||
|
|
||||||
#def getClick(self):
|
|
||||||
#fl = self.getFreehandLine()
|
|
||||||
#return fl[-1]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ class HistogramLUTWidget(GraphicsView):
|
||||||
self.item = HistogramLUTItem(*args, **kargs)
|
self.item = HistogramLUTItem(*args, **kargs)
|
||||||
self.setCentralItem(self.item)
|
self.setCentralItem(self.item)
|
||||||
self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
|
self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)
|
||||||
self.setMinimumWidth(92)
|
self.setMinimumWidth(95)
|
||||||
|
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
|
|
96
widgets/LayoutWidget.py
Normal file
96
widgets/LayoutWidget.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
|
||||||
|
__all__ = ['LayoutWidget']
|
||||||
|
class LayoutWidget(QtGui.QWidget):
|
||||||
|
"""
|
||||||
|
Convenience class used for laying out QWidgets in a grid.
|
||||||
|
(It's just a little less effort to use than QGridLayout)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QtGui.QWidget.__init__(self, parent)
|
||||||
|
self.layout = QtGui.QGridLayout()
|
||||||
|
self.setLayout(self.layout)
|
||||||
|
self.items = {}
|
||||||
|
self.rows = {}
|
||||||
|
self.currentRow = 0
|
||||||
|
self.currentCol = 0
|
||||||
|
|
||||||
|
def nextRow(self):
|
||||||
|
"""Advance to next row for automatic widget placement"""
|
||||||
|
self.currentRow += 1
|
||||||
|
self.currentCol = 0
|
||||||
|
|
||||||
|
def nextColumn(self, colspan=1):
|
||||||
|
"""Advance to next column, while returning the current column number
|
||||||
|
(generally only for internal use--called by addWidget)"""
|
||||||
|
self.currentCol += colspan
|
||||||
|
return self.currentCol-colspan
|
||||||
|
|
||||||
|
def nextCol(self, *args, **kargs):
|
||||||
|
"""Alias of nextColumn"""
|
||||||
|
return self.nextColumn(*args, **kargs)
|
||||||
|
|
||||||
|
|
||||||
|
def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs):
|
||||||
|
"""
|
||||||
|
Create a QLabel with *text* and place it in the next available cell (or in the cell specified)
|
||||||
|
All extra keyword arguments are passed to QLabel().
|
||||||
|
Returns the created widget.
|
||||||
|
"""
|
||||||
|
text = QtGui.QLabel(text, **kargs)
|
||||||
|
self.addItem(text, row, col, rowspan, colspan)
|
||||||
|
return text
|
||||||
|
|
||||||
|
def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs):
|
||||||
|
"""
|
||||||
|
Create an empty LayoutWidget and place it in the next available cell (or in the cell specified)
|
||||||
|
All extra keyword arguments are passed to :func:`LayoutWidget.__init__ <pyqtgraph.LayoutWidget.__init__>`
|
||||||
|
Returns the created widget.
|
||||||
|
"""
|
||||||
|
layout = LayoutWidget(**kargs)
|
||||||
|
self.addItem(layout, row, col, rowspan, colspan)
|
||||||
|
return layout
|
||||||
|
|
||||||
|
def addWidget(self, item, row=None, col=None, rowspan=1, colspan=1):
|
||||||
|
"""
|
||||||
|
Add a widget to the layout and place it in the next available cell (or in the cell specified).
|
||||||
|
"""
|
||||||
|
if row is None:
|
||||||
|
row = self.currentRow
|
||||||
|
if col is None:
|
||||||
|
col = self.nextCol(colspan)
|
||||||
|
|
||||||
|
if row not in self.rows:
|
||||||
|
self.rows[row] = {}
|
||||||
|
self.rows[row][col] = item
|
||||||
|
self.items[item] = (row, col)
|
||||||
|
|
||||||
|
self.layout.addWidget(item, row, col, rowspan, colspan)
|
||||||
|
|
||||||
|
def getWidget(self, row, col):
|
||||||
|
"""Return the widget in (*row*, *col*)"""
|
||||||
|
return self.row[row][col]
|
||||||
|
|
||||||
|
#def itemIndex(self, item):
|
||||||
|
#for i in range(self.layout.count()):
|
||||||
|
#if self.layout.itemAt(i).graphicsItem() is item:
|
||||||
|
#return i
|
||||||
|
#raise Exception("Could not determine index of item " + str(item))
|
||||||
|
|
||||||
|
#def removeItem(self, item):
|
||||||
|
#"""Remove *item* from the layout."""
|
||||||
|
#ind = self.itemIndex(item)
|
||||||
|
#self.layout.removeAt(ind)
|
||||||
|
#self.scene().removeItem(item)
|
||||||
|
#r,c = self.items[item]
|
||||||
|
#del self.items[item]
|
||||||
|
#del self.rows[r][c]
|
||||||
|
#self.update()
|
||||||
|
|
||||||
|
#def clear(self):
|
||||||
|
#items = []
|
||||||
|
#for i in list(self.items.keys()):
|
||||||
|
#self.removeItem(i)
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,8 @@ class PlotWidget(GraphicsView):
|
||||||
other methods, use :func:`getPlotItem <pyqtgraph.PlotWidget.getPlotItem>`.
|
other methods, use :func:`getPlotItem <pyqtgraph.PlotWidget.getPlotItem>`.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent=None, **kargs):
|
def __init__(self, parent=None, **kargs):
|
||||||
|
"""When initializing PlotWidget, all keyword arguments except *parent* are passed
|
||||||
|
to :func:`PlotItem.__init__() <pyqtgraph.PlotItem.__init__>`."""
|
||||||
GraphicsView.__init__(self, parent)
|
GraphicsView.__init__(self, parent)
|
||||||
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
|
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
|
||||||
self.enableMouse(False)
|
self.enableMouse(False)
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ProgressDialog(QtGui.QProgressDialog):
|
||||||
if dlg.wasCanceled():
|
if dlg.wasCanceled():
|
||||||
raise Exception("Processing canceled by user")
|
raise Exception("Processing canceled by user")
|
||||||
"""
|
"""
|
||||||
def __init__(self, labelText, minimum=0, maximum=100, cancelText='Cancel', parent=None, wait=250, busyCursor=False):
|
def __init__(self, labelText, minimum=0, maximum=100, cancelText='Cancel', parent=None, wait=250, busyCursor=False, disable=False):
|
||||||
"""
|
"""
|
||||||
============== ================================================================
|
============== ================================================================
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
|
@ -25,15 +25,16 @@ class ProgressDialog(QtGui.QProgressDialog):
|
||||||
parent
|
parent
|
||||||
wait Length of time (im ms) to wait before displaying dialog
|
wait Length of time (im ms) to wait before displaying dialog
|
||||||
busyCursor If True, show busy cursor until dialog finishes
|
busyCursor If True, show busy cursor until dialog finishes
|
||||||
|
disable If True, the progress dialog will not be displayed
|
||||||
|
and calls to wasCanceled() will always return False.
|
||||||
|
If ProgressDialog is entered from a non-gui thread, it will
|
||||||
|
always be disabled.
|
||||||
============== ================================================================
|
============== ================================================================
|
||||||
"""
|
"""
|
||||||
|
|
||||||
isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread()
|
isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread()
|
||||||
if not isGuiThread:
|
self.disabled = disable or (not isGuiThread)
|
||||||
self.disabled = True
|
if self.disabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.disabled = False
|
|
||||||
|
|
||||||
noCancel = False
|
noCancel = False
|
||||||
if cancelText is None:
|
if cancelText is None:
|
||||||
|
|
172
widgets/RemoteGraphicsView.py
Normal file
172
widgets/RemoteGraphicsView.py
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
import pyqtgraph.multiprocess as mp
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from .GraphicsView import GraphicsView
|
||||||
|
import numpy as np
|
||||||
|
import mmap, tempfile, ctypes, atexit
|
||||||
|
|
||||||
|
__all__ = ['RemoteGraphicsView']
|
||||||
|
|
||||||
|
class RemoteGraphicsView(QtGui.QWidget):
|
||||||
|
"""
|
||||||
|
Replacement for GraphicsView that does all scene management and rendering on a remote process,
|
||||||
|
while displaying on the local widget.
|
||||||
|
|
||||||
|
GraphicsItems must be created by proxy to the remote process.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, parent=None, *args, **kwds):
|
||||||
|
self._img = None
|
||||||
|
self._imgReq = None
|
||||||
|
QtGui.QWidget.__init__(self)
|
||||||
|
self._proc = mp.QtProcess()
|
||||||
|
self.pg = self._proc._import('pyqtgraph')
|
||||||
|
self.pg.setConfigOptions(self.pg.CONFIG_OPTIONS)
|
||||||
|
rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView')
|
||||||
|
self._view = rpgRemote.Renderer(*args, **kwds)
|
||||||
|
self._view._setProxyOptions(deferGetattr=True)
|
||||||
|
self.setFocusPolicy(self._view.focusPolicy())
|
||||||
|
|
||||||
|
shmFileName = self._view.shmFileName()
|
||||||
|
self.shmFile = open(shmFileName, 'r')
|
||||||
|
self.shm = mmap.mmap(self.shmFile.fileno(), mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_READ)
|
||||||
|
|
||||||
|
self._view.sceneRendered.connect(mp.proxy(self.remoteSceneChanged))
|
||||||
|
|
||||||
|
for method in ['scene', 'setCentralItem']:
|
||||||
|
setattr(self, method, getattr(self._view, method))
|
||||||
|
|
||||||
|
def resizeEvent(self, ev):
|
||||||
|
ret = QtGui.QWidget.resizeEvent(self, ev)
|
||||||
|
self._view.resize(self.size(), _callSync='off')
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def remoteSceneChanged(self, data):
|
||||||
|
w, h, size = data
|
||||||
|
if self.shm.size != size:
|
||||||
|
self.shm.close()
|
||||||
|
self.shm = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ)
|
||||||
|
self.shm.seek(0)
|
||||||
|
self._img = QtGui.QImage(self.shm.read(w*h*4), w, h, QtGui.QImage.Format_ARGB32)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def paintEvent(self, ev):
|
||||||
|
if self._img is None:
|
||||||
|
return
|
||||||
|
p = QtGui.QPainter(self)
|
||||||
|
p.drawImage(self.rect(), self._img, QtCore.QRect(0, 0, self._img.width(), self._img.height()))
|
||||||
|
p.end()
|
||||||
|
|
||||||
|
def mousePressEvent(self, ev):
|
||||||
|
self._view.mousePressEvent(ev.type(), ev.pos(), ev.globalPos(), ev.button(), int(ev.buttons()), int(ev.modifiers()), _callSync='off')
|
||||||
|
ev.accept()
|
||||||
|
return QtGui.QWidget.mousePressEvent(self, ev)
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, ev):
|
||||||
|
self._view.mouseReleaseEvent(ev.type(), ev.pos(), ev.globalPos(), ev.button(), int(ev.buttons()), int(ev.modifiers()), _callSync='off')
|
||||||
|
ev.accept()
|
||||||
|
return QtGui.QWidget.mouseReleaseEvent(self, ev)
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, ev):
|
||||||
|
self._view.mouseMoveEvent(ev.type(), ev.pos(), ev.globalPos(), ev.button(), int(ev.buttons()), int(ev.modifiers()), _callSync='off')
|
||||||
|
ev.accept()
|
||||||
|
return QtGui.QWidget.mouseMoveEvent(self, ev)
|
||||||
|
|
||||||
|
def wheelEvent(self, ev):
|
||||||
|
self._view.wheelEvent(ev.pos(), ev.globalPos(), ev.delta(), int(ev.buttons()), int(ev.modifiers()), ev.orientation(), _callSync='off')
|
||||||
|
ev.accept()
|
||||||
|
return QtGui.QWidget.wheelEvent(self, ev)
|
||||||
|
|
||||||
|
def keyEvent(self, ev):
|
||||||
|
if self._view.keyEvent(ev.type(), int(ev.modifiers()), text, autorep, count):
|
||||||
|
ev.accept()
|
||||||
|
return QtGui.QWidget.keyEvent(self, ev)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Renderer(GraphicsView):
|
||||||
|
|
||||||
|
sceneRendered = QtCore.Signal(object)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
## Create shared memory for rendered image
|
||||||
|
#fd = os.open('/tmp/mmaptest', os.O_CREAT | os.O_TRUNC | os.O_RDWR)
|
||||||
|
#os.write(fd, '\x00' * mmap.PAGESIZE)
|
||||||
|
self.shmFile = tempfile.NamedTemporaryFile(prefix='pyqtgraph_shmem_')
|
||||||
|
self.shmFile.write('\x00' * mmap.PAGESIZE)
|
||||||
|
#fh.flush()
|
||||||
|
fd = self.shmFile.fileno()
|
||||||
|
self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE)
|
||||||
|
atexit.register(self.close)
|
||||||
|
|
||||||
|
GraphicsView.__init__(self, *args, **kwds)
|
||||||
|
self.scene().changed.connect(self.update)
|
||||||
|
self.img = None
|
||||||
|
self.renderTimer = QtCore.QTimer()
|
||||||
|
self.renderTimer.timeout.connect(self.renderView)
|
||||||
|
self.renderTimer.start(16)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.shm.close()
|
||||||
|
self.shmFile.close()
|
||||||
|
|
||||||
|
def shmFileName(self):
|
||||||
|
return self.shmFile.name
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.img = None
|
||||||
|
return GraphicsView.update(self)
|
||||||
|
|
||||||
|
def resize(self, size):
|
||||||
|
oldSize = self.size()
|
||||||
|
GraphicsView.resize(self, size)
|
||||||
|
self.resizeEvent(QtGui.QResizeEvent(size, oldSize))
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def renderView(self):
|
||||||
|
if self.img is None:
|
||||||
|
## make sure shm is large enough and get its address
|
||||||
|
size = self.width() * self.height() * 4
|
||||||
|
if size > self.shm.size():
|
||||||
|
self.shm.resize(size)
|
||||||
|
address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0))
|
||||||
|
|
||||||
|
## render the scene directly to shared memory
|
||||||
|
self.img = QtGui.QImage(address, self.width(), self.height(), QtGui.QImage.Format_ARGB32)
|
||||||
|
self.img.fill(0xffffffff)
|
||||||
|
p = QtGui.QPainter(self.img)
|
||||||
|
self.render(p, self.viewRect(), self.rect())
|
||||||
|
p.end()
|
||||||
|
self.sceneRendered.emit((self.width(), self.height(), self.shm.size()))
|
||||||
|
|
||||||
|
def mousePressEvent(self, typ, pos, gpos, btn, btns, mods):
|
||||||
|
typ = QtCore.QEvent.Type(typ)
|
||||||
|
btns = QtCore.Qt.MouseButtons(btns)
|
||||||
|
mods = QtCore.Qt.KeyboardModifiers(mods)
|
||||||
|
return GraphicsView.mousePressEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods))
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, typ, pos, gpos, btn, btns, mods):
|
||||||
|
typ = QtCore.QEvent.Type(typ)
|
||||||
|
btns = QtCore.Qt.MouseButtons(btns)
|
||||||
|
mods = QtCore.Qt.KeyboardModifiers(mods)
|
||||||
|
return GraphicsView.mouseMoveEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods))
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, typ, pos, gpos, btn, btns, mods):
|
||||||
|
typ = QtCore.QEvent.Type(typ)
|
||||||
|
btns = QtCore.Qt.MouseButtons(btns)
|
||||||
|
mods = QtCore.Qt.KeyboardModifiers(mods)
|
||||||
|
return GraphicsView.mouseReleaseEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods))
|
||||||
|
|
||||||
|
def wheelEvent(self, pos, gpos, d, btns, mods, ori):
|
||||||
|
btns = QtCore.Qt.MouseButtons(btns)
|
||||||
|
mods = QtCore.Qt.KeyboardModifiers(mods)
|
||||||
|
return GraphicsView.wheelEvent(self, QtGui.QWheelEvent(pos, gpos, d, btns, mods, ori))
|
||||||
|
|
||||||
|
def keyEvent(self, typ, mods, text, autorep, count):
|
||||||
|
typ = QtCore.QEvent.Type(typ)
|
||||||
|
mods = QtCore.Qt.KeyboardModifiers(mods)
|
||||||
|
GraphicsView.keyEvent(self, QtGui.QKeyEvent(typ, mods, text, autorep, count))
|
||||||
|
return ev.accepted()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
from pyqtgraph.python2_3 import asUnicode
|
||||||
from pyqtgraph.SignalProxy import SignalProxy
|
from pyqtgraph.SignalProxy import SignalProxy
|
||||||
|
|
||||||
import pyqtgraph.functions as fn
|
import pyqtgraph.functions as fn
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from pyqtgraph.Qt import QtGui, QtCore
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
from pyqtgraph.python2_3 import asUnicode
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user