Flowchart:

* Replaced dynamic imports with static
* Added NodeLibrary allowing multiple customized collections of Node types
This commit is contained in:
Luke Campagnola 2013-12-15 23:50:11 -05:00
parent 59f07a03ee
commit 19be6959f3
4 changed files with 197 additions and 99 deletions

View File

@ -83,9 +83,8 @@ class ImageViewNode(Node):
else: else:
self.view.setImage(data) 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. ## We will define an unsharp masking filter node as a subclass of CtrlNode.
## CtrlNode is just a convenience class that automatically creates its ## CtrlNode is just a convenience class that automatically creates its
@ -113,12 +112,25 @@ class UnsharpMaskNode(CtrlNode):
strength = self.ctrls['strength'].value() strength = self.ctrls['strength'].value()
output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma))) output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma)))
return {'dataOut': output} 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. ## Method 1: Register to global default library:
## It will appear in a new 'image' sub-menu. #fclib.registerNodeType(ImageViewNode, [('Display',)])
fclib.registerNodeType(UnsharpMaskNode, [('Image',)]) #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. ## 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 ## Normally, the user will do this manually or by loading a pre-generated

View File

@ -14,7 +14,7 @@ else:
from .Terminal import Terminal from .Terminal import Terminal
from numpy import ndarray from numpy import ndarray
from . import library from .library import LIBRARY
from pyqtgraph.debug import printExc from pyqtgraph.debug import printExc
import pyqtgraph.configfile as configfile import pyqtgraph.configfile as configfile
import pyqtgraph.dockarea as dockarea import pyqtgraph.dockarea as dockarea
@ -67,7 +67,8 @@ class Flowchart(Node):
sigChartLoaded = QtCore.Signal() sigChartLoaded = QtCore.Signal()
sigStateChanged = 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: if name is None:
name = "Flowchart" name = "Flowchart"
if terminals is None: if terminals is None:
@ -105,6 +106,10 @@ class Flowchart(Node):
for name, opts in terminals.items(): for name, opts in terminals.items():
self.addTerminal(name, **opts) self.addTerminal(name, **opts)
def setLibrary(self, lib):
self.library = lib
self.widget().chartWidget.buildMenu()
def setInput(self, **args): def setInput(self, **args):
"""Set the input values of the flowchart. This will automatically propagate """Set the input values of the flowchart. This will automatically propagate
the new values throughout the flowchart, (possibly) causing the output to change. the new values throughout the flowchart, (possibly) causing the output to change.
@ -194,7 +199,7 @@ class Flowchart(Node):
break break
n += 1 n += 1
node = library.getNodeType(nodeType)(name) node = self.library.getNodeType(nodeType)(name)
self.addNode(node, name, pos) self.addNode(node, name, pos)
return node return node
@ -846,13 +851,13 @@ class FlowchartWidget(dockarea.DockArea):
self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered) self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered)
self.nodeMenu = None self.nodeMenu = None
self.subMenus = [] self.subMenus = []
library.loadLibrary(reloadLibs=True) self.chart.library.reload()
self.buildMenu() self.buildMenu()
def buildMenu(self, pos=None): def buildMenu(self, pos=None):
self.nodeMenu = QtGui.QMenu() self.nodeMenu = QtGui.QMenu()
self.subMenus = [] self.subMenus = []
for section, nodes in library.getNodeTree().items(): for section, nodes in self.chart.library.getNodeTree().items():
menu = QtGui.QMenu(section) menu = QtGui.QMenu(section)
self.nodeMenu.addMenu(menu) self.nodeMenu.addMenu(menu)
for name in nodes: for name in nodes:

View File

@ -0,0 +1,84 @@
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()

View File

@ -1,103 +1,100 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from pyqtgraph.pgcollections import OrderedDict from pyqtgraph.pgcollections import OrderedDict
from pyqtgraph import importModules #from pyqtgraph import importModules
import os, types import os, types
from pyqtgraph.debug import printExc from pyqtgraph.debug import printExc
from ..Node import Node #from ..Node import Node
from ..NodeLibrary import NodeLibrary, isNodeClass
import pyqtgraph.reload as reload import pyqtgraph.reload as reload
NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses # Build default library
NODE_TREE = OrderedDict() ## categorized tree of Node subclasses LIBRARY = NodeLibrary()
def getNodeType(name): # For backward compatibility, expose the default library's properties here:
try: NODE_LIST = LIBRARY.nodeList
return NODE_LIST[name] NODE_TREE = LIBRARY.nodeTree
except KeyError: registerNodeType = LIBRARY.addNodeType
raise Exception("No node type called '%s'" % name) getNodeTree = LIBRARY.getNodeTree
getNodeType = LIBRARY.getNodeType
def getNodeTree(): # Add all nodes to the default library
return NODE_TREE for modName in ['Data', 'Display', 'Filters', 'Operators']:
mod = __import__(modName, globals(), locals(), [], -1)
def registerNodeType(cls, paths, override=False): nodes = [getattr(mod, name) for name in dir(mod) if isNodeClass(getattr(mod, name))]
""" for node in nodes:
Register a new node type. If the type's name is already in use, LIBRARY.addNodeType(node, [(modName,)])
an exception will be raised (unless override=True).
Arguments: #NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses
cls - a subclass of Node (must have typ.nodeName) #NODE_TREE = OrderedDict() ## categorized tree of Node subclasses
paths - list of tuples specifying the location(s) this
type will appear in the library tree. #def getNodeType(name):
override - if True, overwrite any class having the same name #try:
""" #return NODE_LIST[name]
if not isNodeClass(cls): #except KeyError:
raise Exception("Object %s is not a Node subclass" % str(cls)) #raise Exception("No node type called '%s'" % name)
#def getNodeTree():
#return NODE_TREE
#def registerNodeType(cls, 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).
name = cls.nodeName #Arguments:
if not override and name in NODE_LIST: #cls - a subclass of Node (must have typ.nodeName)
raise Exception("Node type name '%s' is already registered." % name) #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(cls):
#raise Exception("Object %s is not a Node subclass" % str(cls))
NODE_LIST[name] = cls #name = cls.nodeName
for path in paths: #if not override and name in NODE_LIST:
root = NODE_TREE #raise Exception("Node type name '%s' is already registered." % name)
for n in path:
if n not in root:
root[n] = OrderedDict()
root = root[n]
root[name] = cls
def isNodeClass(cls):
try:
if not issubclass(cls, Node):
return False
except:
return False
return hasattr(cls, 'nodeName')
def loadLibrary(reloadLibs=False, libPath=None):
"""Import all Node subclasses found within files in the library module."""
global NODE_LIST, NODE_TREE
#if libPath is None:
#libPath = os.path.dirname(os.path.abspath(__file__))
if reloadLibs: #NODE_LIST[name] = cls
reload.reloadAll(libPath) #for path in paths:
#root = NODE_TREE
#for n in path:
#if n not in root:
#root[n] = OrderedDict()
#root = root[n]
#root[name] = cls
#def isNodeClass(cls):
#try:
#if not issubclass(cls, Node):
#return False
#except:
#return False
#return hasattr(cls, 'nodeName')
#def loadLibrary(reloadLibs=False, libPath=None):
#"""Import all Node subclasses found within files in the library module."""
#global NODE_LIST, NODE_TREE
#if reloadLibs:
#reload.reloadAll(libPath)
mods = importModules('', globals(), locals()) #mods = importModules('', globals(), locals())
#for f in frozenSupport.listdir(libPath):
#pathName, ext = os.path.splitext(f)
#if ext not in ('.py', '.pyc') or '__init__' in pathName or '__pycache__' in pathName:
#continue
#try:
##print "importing from", f
#mod = __import__(pathName, globals(), locals())
#except:
#printExc("Error loading flowchart library %s:" % pathName)
#continue
for name, mod in mods.items(): #for name, mod in mods.items():
nodes = [] #nodes = []
for n in dir(mod): #for n in dir(mod):
o = getattr(mod, n) #o = getattr(mod, n)
if isNodeClass(o): #if isNodeClass(o):
#print " ", str(o) #registerNodeType(o, [(name,)], override=reloadLibs)
registerNodeType(o, [(name,)], override=reloadLibs)
#nodes.append((o.nodeName, o))
#if len(nodes) > 0:
#NODE_TREE[name] = OrderedDict(nodes)
#NODE_LIST.extend(nodes)
#NODE_LIST = OrderedDict(NODE_LIST)
def reloadLibrary(): #def reloadLibrary():
loadLibrary(reloadLibs=True) #loadLibrary(reloadLibs=True)
loadLibrary() #loadLibrary()
#NODE_LIST = []
#for o in locals().values():
#if type(o) is type(AddNode) and issubclass(o, Node) and o is not Node and hasattr(o, 'nodeName'):
#NODE_LIST.append((o.nodeName, o))
#NODE_LIST.sort(lambda a,b: cmp(a[0], b[0]))
#NODE_LIST = OrderedDict(NODE_LIST)