Commit 34889108 authored by Luke Campagnola's avatar Luke Campagnola
Browse files

Merge branch 'static_imports' into develop

No more dynamic imports; pg uses static imports throughout.
Flowcharts and exporters use plugin systems
parents 63f3b0ab 90b6b5b5
pyqtgraph-0.9.9 [unreleased]
API / behavior changes:
- Dynamic import system abandoned; pg now uses static imports throughout.
- Flowcharts and exporters have new pluggin systems
- Version strings:
- __init__.py in git repo now contains latest release version string
(previously, only packaged releases had version strings).
- installing from git checkout that does not correspond to a release
commit will result in a more descriptive version string.
- Speed improvements in functions.makeARGB
- ImageItem is faster by avoiding makeQImage(transpose=True)
New Features:
- New HDF5 example for working with very large datasets
- Added Qt.loadUiType function for PySide
- Simplified Profilers; can be activated with environmental variables
- Added Dock.raiseDock() method
Bugfixes:
- PlotCurveItem now has correct clicking behavior--clicks within a few px
of the line will trigger a signal.
- Fixes related to CSV exporter:
- CSV headers include data names, if available
- Exporter correctly handles items with no data
- pg.plot() avoids creating empty data item
- removed call to reduce() from exporter; not available in python 3
- Gave .name() methods to PlotDataItem, PlotCurveItem, and ScatterPlotItem
- fixed ImageItem handling of rgb images
- fixed makeARGB re-ordering of color channels
pyqtgraph-0.9.8 2013-11-24
API / behavior changes:
......
......@@ -83,9 +83,8 @@ class ImageViewNode(Node):
else:
self.view.setImage(data)
## register the class so it will appear in the menu of node types.
## It will appear in the 'display' sub-menu.
fclib.registerNodeType(ImageViewNode, [('Display',)])
## We will define an unsharp masking filter node as a subclass of CtrlNode.
## CtrlNode is just a convenience class that automatically creates its
......@@ -113,12 +112,25 @@ class UnsharpMaskNode(CtrlNode):
strength = self.ctrls['strength'].value()
output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma)))
return {'dataOut': output}
## To make our custom node classes available in the flowchart context menu,
## we can either register them with the default node library or make a
## new library.
## register the class so it will appear in the menu of node types.
## It will appear in a new 'image' sub-menu.
fclib.registerNodeType(UnsharpMaskNode, [('Image',)])
## Method 1: Register to global default library:
#fclib.registerNodeType(ImageViewNode, [('Display',)])
#fclib.registerNodeType(UnsharpMaskNode, [('Image',)])
## Method 2: If we want to make our custom node available only to this flowchart,
## then instead of registering the node type globally, we can create a new
## NodeLibrary:
library = fclib.LIBRARY.copy() # start with the default node set
library.addNodeType(ImageViewNode, [('Display',)])
library.addNodeType(UnsharpMaskNode, [('Image',)])
fc.setLibrary(library)
## Now we will programmatically add nodes to define the function of the flowchart.
## Normally, the user will do this manually or by loading a pre-generated
......
......@@ -130,56 +130,119 @@ if __version__ is None and not hasattr(sys, 'frozen') and sys.version_info[0] ==
## Import almost everything to make it available from a single namespace
## don't import the more complex systems--canvas, parametertree, flowchart, dockarea
## these must be imported separately.
from . import frozenSupport
def importModules(path, globals, locals, excludes=()):
"""Import all modules residing within *path*, return a dict of name: module pairs.
#from . import frozenSupport
#def importModules(path, globals, locals, excludes=()):
#"""Import all modules residing within *path*, return a dict of name: module pairs.
Note that *path* MUST be relative to the module doing the import.
"""
d = os.path.join(os.path.split(globals['__file__'])[0], path)
files = set()
for f in frozenSupport.listdir(d):
if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']:
files.add(f)
elif f[-3:] == '.py' and f != '__init__.py':
files.add(f[:-3])
elif f[-4:] == '.pyc' and f != '__init__.pyc':
files.add(f[:-4])
#Note that *path* MUST be relative to the module doing the import.
#"""
#d = os.path.join(os.path.split(globals['__file__'])[0], path)
#files = set()
#for f in frozenSupport.listdir(d):
#if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']:
#files.add(f)
#elif f[-3:] == '.py' and f != '__init__.py':
#files.add(f[:-3])
#elif f[-4:] == '.pyc' and f != '__init__.pyc':
#files.add(f[:-4])
mods = {}
path = path.replace(os.sep, '.')
for modName in files:
if modName in excludes:
continue
try:
if len(path) > 0:
modName = path + '.' + modName
#mod = __import__(modName, globals, locals, fromlist=['*'])
mod = __import__(modName, globals, locals, ['*'], 1)
mods[modName] = mod
except:
import traceback
traceback.print_stack()
sys.excepthook(*sys.exc_info())
print("[Error importing module: %s]" % modName)
#mods = {}
#path = path.replace(os.sep, '.')
#for modName in files:
#if modName in excludes:
#continue
#try:
#if len(path) > 0:
#modName = path + '.' + modName
#print( "from .%s import * " % modName)
#mod = __import__(modName, globals, locals, ['*'], 1)
#mods[modName] = mod
#except:
#import traceback
#traceback.print_stack()
#sys.excepthook(*sys.exc_info())
#print("[Error importing module: %s]" % modName)
return mods
def importAll(path, globals, locals, excludes=()):
"""Given a list of modules, import all names from each module into the global namespace."""
mods = importModules(path, globals, locals, excludes)
for mod in mods.values():
if hasattr(mod, '__all__'):
names = mod.__all__
else:
names = [n for n in dir(mod) if n[0] != '_']
for k in names:
if hasattr(mod, k):
globals[k] = getattr(mod, k)
importAll('graphicsItems', globals(), locals())
importAll('widgets', globals(), locals(),
excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView'])
#return mods
#def importAll(path, globals, locals, excludes=()):
#"""Given a list of modules, import all names from each module into the global namespace."""
#mods = importModules(path, globals, locals, excludes)
#for mod in mods.values():
#if hasattr(mod, '__all__'):
#names = mod.__all__
#else:
#names = [n for n in dir(mod) if n[0] != '_']
#for k in names:
#if hasattr(mod, k):
#globals[k] = getattr(mod, k)
# Dynamic imports are disabled. This causes too many problems.
#importAll('graphicsItems', globals(), locals())
#importAll('widgets', globals(), locals(),
#excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView'])
from .graphicsItems.VTickGroup import *
from .graphicsItems.GraphicsWidget import *
from .graphicsItems.ScaleBar import *
from .graphicsItems.PlotDataItem import *
from .graphicsItems.GraphItem import *
from .graphicsItems.TextItem import *
from .graphicsItems.GraphicsLayout import *
from .graphicsItems.UIGraphicsItem import *
from .graphicsItems.GraphicsObject import *
from .graphicsItems.PlotItem import *
from .graphicsItems.ROI import *
from .graphicsItems.InfiniteLine import *
from .graphicsItems.HistogramLUTItem import *
from .graphicsItems.GridItem import *
from .graphicsItems.GradientLegend import *
from .graphicsItems.GraphicsItem import *
from .graphicsItems.BarGraphItem import *
from .graphicsItems.ViewBox import *
from .graphicsItems.ArrowItem import *
from .graphicsItems.ImageItem import *
from .graphicsItems.AxisItem import *
from .graphicsItems.LabelItem import *
from .graphicsItems.CurvePoint import *
from .graphicsItems.GraphicsWidgetAnchor import *
from .graphicsItems.PlotCurveItem import *
from .graphicsItems.ButtonItem import *
from .graphicsItems.GradientEditorItem import *
from .graphicsItems.MultiPlotItem import *
from .graphicsItems.ErrorBarItem import *
from .graphicsItems.IsocurveItem import *
from .graphicsItems.LinearRegionItem import *
from .graphicsItems.FillBetweenItem import *
from .graphicsItems.LegendItem import *
from .graphicsItems.ScatterPlotItem import *
from .graphicsItems.ItemGroup import *
from .widgets.MultiPlotWidget import *
from .widgets.ScatterPlotWidget import *
from .widgets.ColorMapWidget import *
from .widgets.FileDialog import *
from .widgets.ValueLabel import *
from .widgets.HistogramLUTWidget import *
from .widgets.CheckTable import *
from .widgets.BusyCursor import *
from .widgets.PlotWidget import *
from .widgets.ComboBox import *
from .widgets.GradientWidget import *
from .widgets.DataFilterWidget import *
from .widgets.SpinBox import *
from .widgets.JoystickButton import *
from .widgets.GraphicsLayoutWidget import *
from .widgets.TreeWidget import *
from .widgets.PathButton import *
from .widgets.VerticalLabel import *
from .widgets.FeedbackButton import *
from .widgets.ColorButton import *
from .widgets.DataTreeWidget import *
from .widgets.GraphicsView import *
from .widgets.LayoutWidget import *
from .widgets.TableWidget import *
from .widgets.ProgressDialog import *
from .imageview import *
from .WidgetGroup import *
......@@ -194,6 +257,7 @@ from .SignalProxy import *
from .colormap import *
from .ptime import time
##############################################################
## PyQt and PySide both are prone to crashing on exit.
## There are two general approaches to dealing with this:
......
......@@ -60,6 +60,6 @@ class CSVExporter(Exporter):
fd.write('\n')
fd.close()
CSVExporter.register()
......@@ -11,6 +11,14 @@ class Exporter(object):
Abstract class used for exporting graphics to file / printer / whatever.
"""
allowCopy = False # subclasses set this to True if they can use the copy buffer
Exporters = []
@classmethod
def register(cls):
"""
Used to register Exporter classes to appear in the export dialog.
"""
Exporter.Exporters.append(cls)
def __init__(self, item):
"""
......@@ -20,9 +28,6 @@ class Exporter(object):
object.__init__(self)
self.item = item
#def item(self):
#return self.item
def parameters(self):
"""Return the parameters used to configure this exporter."""
raise Exception("Abstract method must be overridden in subclass.")
......@@ -131,45 +136,4 @@ class Exporter(object):
return preItems + rootItem + postItems
def render(self, painter, targetRect, sourceRect, item=None):
#if item is None:
#item = self.item
#preItems = []
#postItems = []
#if isinstance(item, QtGui.QGraphicsScene):
#childs = [i for i in item.items() if i.parentItem() is None]
#rootItem = []
#else:
#childs = item.childItems()
#rootItem = [item]
#childs.sort(lambda a,b: cmp(a.zValue(), b.zValue()))
#while len(childs) > 0:
#ch = childs.pop(0)
#if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0):
#preItems.extend(tree)
#else:
#postItems.extend(tree)
#for ch in preItems:
#self.render(painter, sourceRect, targetRect, item=ch)
### paint root here
#for ch in postItems:
#self.render(painter, sourceRect, targetRect, item=ch)
self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect))
#def writePs(self, fileName=None, item=None):
#if fileName is None:
#self.fileSaveDialog(self.writeSvg, filter="PostScript (*.ps)")
#return
#if item is None:
#item = self
#printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
#printer.setOutputFileName(fileName)
#painter = QtGui.QPainter(printer)
#self.render(painter)
#painter.end()
#def writeToPrinter(self):
#pass
......@@ -98,4 +98,5 @@ class ImageExporter(Exporter):
else:
self.png.save(fileName)
ImageExporter.register()
\ No newline at end of file
......@@ -57,6 +57,7 @@ class MatplotlibExporter(Exporter):
else:
raise Exception("Matplotlib export currently only works with plot items")
MatplotlibExporter.register()
class MatplotlibWindow(QtGui.QMainWindow):
......@@ -72,3 +73,5 @@ class MatplotlibWindow(QtGui.QMainWindow):
def closeEvent(self, ev):
MatplotlibExporter.windows.remove(self)
......@@ -63,3 +63,6 @@ class PrintExporter(Exporter):
finally:
self.setExportMode(False)
painter.end()
#PrintExporter.register()
......@@ -404,6 +404,10 @@ def correctCoordinates(node, item):
if removeTransform:
grp.removeAttribute('transform')
SVGExporter.register()
def itemTransform(item, root):
## Return the transformation mapping item to root
## (actually to parent coordinate system of root)
......
Exporters = []
from pyqtgraph import importModules
#from .. import frozenSupport
import os
d = os.path.split(__file__)[0]
#files = []
#for f in frozenSupport.listdir(d):
#if frozenSupport.isdir(os.path.join(d, f)) and f != '__pycache__':
#files.append(f)
#elif f[-3:] == '.py' and f not in ['__init__.py', 'Exporter.py']:
#files.append(f[:-3])
#for modName in files:
#mod = __import__(modName, globals(), locals(), fromlist=['*'])
for mod in importModules('', globals(), locals(), excludes=['Exporter']).values():
if hasattr(mod, '__all__'):
names = mod.__all__
else:
names = [n for n in dir(mod) if n[0] != '_']
for k in names:
if hasattr(mod, k):
Exporters.append(getattr(mod, k))
#Exporters = []
#from pyqtgraph import importModules
#import os
#d = os.path.split(__file__)[0]
#for mod in importModules('', globals(), locals(), excludes=['Exporter']).values():
#if hasattr(mod, '__all__'):
#names = mod.__all__
#else:
#names = [n for n in dir(mod) if n[0] != '_']
#for k in names:
#if hasattr(mod, k):
#Exporters.append(getattr(mod, k))
from .Exporter import Exporter
from .ImageExporter import *
from .SVGExporter import *
from .Matplotlib import *
from .CSVExporter import *
from .PrintExporter import *
def listExporters():
return Exporters[:]
return Exporter.Exporters[:]
......@@ -14,7 +14,7 @@ else:
from .Terminal import Terminal
from numpy import ndarray
from . import library
from .library import LIBRARY
from pyqtgraph.debug import printExc
import pyqtgraph.configfile as configfile
import pyqtgraph.dockarea as dockarea
......@@ -67,7 +67,8 @@ class Flowchart(Node):
sigChartLoaded = QtCore.Signal()
sigStateChanged = QtCore.Signal()
def __init__(self, terminals=None, name=None, filePath=None):
def __init__(self, terminals=None, name=None, filePath=None, library=None):
self.library = library or LIBRARY
if name is None:
name = "Flowchart"
if terminals is None:
......@@ -105,6 +106,10 @@ class Flowchart(Node):
for name, opts in terminals.items():
self.addTerminal(name, **opts)
def setLibrary(self, lib):
self.library = lib
self.widget().chartWidget.buildMenu()
def setInput(self, **args):
"""Set the input values of the flowchart. This will automatically propagate
the new values throughout the flowchart, (possibly) causing the output to change.
......@@ -194,7 +199,7 @@ class Flowchart(Node):
break
n += 1
node = library.getNodeType(nodeType)(name)
node = self.library.getNodeType(nodeType)(name)
self.addNode(node, name, pos)
return node
......@@ -846,13 +851,13 @@ class FlowchartWidget(dockarea.DockArea):
self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered)
self.nodeMenu = None
self.subMenus = []
library.loadLibrary(reloadLibs=True)
self.chart.library.reload()
self.buildMenu()
def buildMenu(self, pos=None):
self.nodeMenu = QtGui.QMenu()
self.subMenus = []
for section, nodes in library.getNodeTree().items():
for section, nodes in self.chart.library.getNodeTree().items():
menu = QtGui.QMenu(section)
self.nodeMenu.addMenu(menu)
for name in nodes:
......
from pyqtgraph.pgcollections import OrderedDict
from .Node import Node
def isNodeClass(cls):
try:
if not issubclass(cls, Node):
return False
except:
return False
return hasattr(cls, 'nodeName')
class NodeLibrary:
"""
A library of flowchart Node types. Custom libraries may be built to provide
each flowchart with a specific set of allowed Node types.
"""
def __init__(self):
self.nodeList = OrderedDict()
self.nodeTree = OrderedDict()
def addNodeType(self, nodeClass, paths, override=False):
"""
Register a new node type. If the type's name is already in use,
an exception will be raised (unless override=True).
Arguments:
nodeClass - a subclass of Node (must have typ.nodeName)
paths - list of tuples specifying the location(s) this
type will appear in the library tree.
override - if True, overwrite any class having the same name
"""
if not isNodeClass(nodeClass):
raise Exception("Object %s is not a Node subclass" % str(nodeClass))
name = nodeClass.nodeName
if not override and name in self.nodeList:
raise Exception("Node type name '%s' is already registered." % name)
self.nodeList[name] = nodeClass
for path in paths:
root = self.nodeTree
for n in path:
if n not in root:
root[n] = OrderedDict()
root = root[n]
root[name] = nodeClass
def getNodeType(self, name):
try:
return self.nodeList[name]
except KeyError:
raise Exception("No node type called '%s'" % name)
def getNodeTree(self):
return self.nodeTree
def copy(self):
"""
Return a copy of this library.
"""
lib = NodeLibrary()
lib.nodeList = self.nodeList.copy()
lib.nodeTree = self.treeCopy(self.nodeTree)
return lib
@staticmethod
def treeCopy(tree):
copy = OrderedDict()
for k,v in tree.items():
if isNodeClass(v):
copy[k] = v
else:
copy[k] = NodeLibrary.treeCopy(v)
return copy
def reload(self):
"""
Reload Node classes in this library.
"""
raise NotImplementedError()
# -*- coding: utf-8 -*-
from pyqtgraph.pgcollections import OrderedDict
from pyqtgraph import importModules
#from pyqtgraph import importModules
import os, types
from pyqtgraph.debug import printExc
from ..Node import Node
#from ..Node import Node
from ..NodeLibrary import NodeLibrary, isNodeClass
import pyqtgraph.reload as reload
NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses
NODE_TREE = OrderedDict() ## categorized tree of Node subclasses
# Build default library
LIBRARY = NodeLibrary()
def getNodeType(name):
try:
return NODE_LIST[name]
except KeyError:
raise Exception("No node type called '%s'" % name)
# For backward compatibility, expose the default library's properties here:
NODE_LIST = LIBRARY.nodeList
NODE_TREE = LIBRARY.nodeTree
registerNodeType = LIBRARY.addNodeType
getNodeTree = LIBRARY.getNodeTree
getNodeType = LIBRARY.getNodeType
def getNodeTree():
return NODE_TREE
# Add all nodes to the default library
from . import Data, Display, Filters, Operators
for mod in [Data, Display, Filters, Operators]:
#mod = getattr(__import__('', fromlist=[modName], level=1), modName)
#mod = __import__(modName, level=1)
nodes = [getattr(mod, name) for name in dir(mod) if isNodeClass(getattr(mod, name))]
for node in nodes:
LIBRARY.addNodeType(node, [(mod.__name__.split('.')[-1],)])
#NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses
#NODE_TREE = OrderedDict() ## categorized tree of Node subclasses
#def getNodeType(name):
#try:
#return NODE_LIST[name]
#except KeyError:
#raise Exception("No node type called '%s'" % name)
#def getNodeTree():
#return NODE_TREE