Flowchart:
* Replaced dynamic imports with static * Added NodeLibrary allowing multiple customized collections of Node types
This commit is contained in:
parent
59f07a03ee
commit
19be6959f3
@ -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
|
||||||
@ -114,10 +113,23 @@ class UnsharpMaskNode(CtrlNode):
|
|||||||
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}
|
||||||
|
|
||||||
## 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',)])
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
## Now we will programmatically add nodes to define the function of the flowchart.
|
||||||
|
@ -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:
|
||||||
|
84
pyqtgraph/flowchart/NodeLibrary.py
Normal file
84
pyqtgraph/flowchart/NodeLibrary.py
Normal 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()
|
@ -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)
|
||||||
|
nodes = [getattr(mod, name) for name in dir(mod) if isNodeClass(getattr(mod, name))]
|
||||||
|
for node in nodes:
|
||||||
|
LIBRARY.addNodeType(node, [(modName,)])
|
||||||
|
|
||||||
def registerNodeType(cls, paths, override=False):
|
#NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses
|
||||||
"""
|
#NODE_TREE = OrderedDict() ## categorized tree of Node subclasses
|
||||||
Register a new node type. If the type's name is already in use,
|
|
||||||
an exception will be raised (unless override=True).
|
|
||||||
|
|
||||||
Arguments:
|
#def getNodeType(name):
|
||||||
cls - a subclass of Node (must have typ.nodeName)
|
#try:
|
||||||
paths - list of tuples specifying the location(s) this
|
#return NODE_LIST[name]
|
||||||
type will appear in the library tree.
|
#except KeyError:
|
||||||
override - if True, overwrite any class having the same name
|
#raise Exception("No node type called '%s'" % name)
|
||||||
"""
|
|
||||||
if not isNodeClass(cls):
|
|
||||||
raise Exception("Object %s is not a Node subclass" % str(cls))
|
|
||||||
|
|
||||||
name = cls.nodeName
|
#def getNodeTree():
|
||||||
if not override and name in NODE_LIST:
|
#return NODE_TREE
|
||||||
raise Exception("Node type name '%s' is already registered." % name)
|
|
||||||
|
|
||||||
NODE_LIST[name] = cls
|
#def registerNodeType(cls, paths, override=False):
|
||||||
for path in paths:
|
#"""
|
||||||
root = NODE_TREE
|
#Register a new node type. If the type's name is already in use,
|
||||||
for n in path:
|
#an exception will be raised (unless override=True).
|
||||||
if n not in root:
|
|
||||||
root[n] = OrderedDict()
|
#Arguments:
|
||||||
root = root[n]
|
#cls - a subclass of Node (must have typ.nodeName)
|
||||||
root[name] = cls
|
#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))
|
||||||
|
|
||||||
|
#name = cls.nodeName
|
||||||
|
#if not override and name in NODE_LIST:
|
||||||
|
#raise Exception("Node type name '%s' is already registered." % name)
|
||||||
|
|
||||||
|
#NODE_LIST[name] = cls
|
||||||
|
#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):
|
#def isNodeClass(cls):
|
||||||
try:
|
#try:
|
||||||
if not issubclass(cls, Node):
|
#if not issubclass(cls, Node):
|
||||||
return False
|
#return False
|
||||||
except:
|
#except:
|
||||||
return False
|
#return False
|
||||||
return hasattr(cls, 'nodeName')
|
#return hasattr(cls, 'nodeName')
|
||||||
|
|
||||||
def loadLibrary(reloadLibs=False, libPath=None):
|
#def loadLibrary(reloadLibs=False, libPath=None):
|
||||||
"""Import all Node subclasses found within files in the library module."""
|
#"""Import all Node subclasses found within files in the library module."""
|
||||||
|
|
||||||
global NODE_LIST, NODE_TREE
|
#global NODE_LIST, NODE_TREE
|
||||||
#if libPath is None:
|
|
||||||
#libPath = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
|
|
||||||
if reloadLibs:
|
#if reloadLibs:
|
||||||
reload.reloadAll(libPath)
|
#reload.reloadAll(libPath)
|
||||||
|
|
||||||
mods = importModules('', globals(), locals())
|
#mods = importModules('', globals(), locals())
|
||||||
#for f in frozenSupport.listdir(libPath):
|
|
||||||
#pathName, ext = os.path.splitext(f)
|
#for name, mod in mods.items():
|
||||||
#if ext not in ('.py', '.pyc') or '__init__' in pathName or '__pycache__' in pathName:
|
#nodes = []
|
||||||
#continue
|
#for n in dir(mod):
|
||||||
#try:
|
#o = getattr(mod, n)
|
||||||
##print "importing from", f
|
#if isNodeClass(o):
|
||||||
#mod = __import__(pathName, globals(), locals())
|
#registerNodeType(o, [(name,)], override=reloadLibs)
|
||||||
#except:
|
|
||||||
#printExc("Error loading flowchart library %s:" % pathName)
|
#def reloadLibrary():
|
||||||
#continue
|
#loadLibrary(reloadLibs=True)
|
||||||
|
|
||||||
|
#loadLibrary()
|
||||||
|
|
||||||
for name, mod in mods.items():
|
|
||||||
nodes = []
|
|
||||||
for n in dir(mod):
|
|
||||||
o = getattr(mod, n)
|
|
||||||
if isNodeClass(o):
|
|
||||||
#print " ", str(o)
|
|
||||||
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():
|
|
||||||
loadLibrary(reloadLibs=True)
|
|
||||||
|
|
||||||
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)
|
|
Loading…
Reference in New Issue
Block a user