Documentation:
- Added documentation on export system - Added flowchart documentation and custom node example Bugfixes: - prevent PlotCurveItem drawing shadow when unnecessary - deprecated flowchart.Node.__getattr__ -- causes too many problems.
This commit is contained in:
commit
79c0ab8ad8
67
doc/source/exporting.rst
Normal file
67
doc/source/exporting.rst
Normal file
@ -0,0 +1,67 @@
|
||||
Exporting
|
||||
=========
|
||||
|
||||
PyQtGraph provides a variety of export formats for all 2D graphics. For 3D graphics, see `Exporting 3D Graphics`_ below.
|
||||
|
||||
Exporting from the GUI
|
||||
----------------------
|
||||
|
||||
Any 2D graphics can be exported by right-clicking on the graphic, then selecting 'export' from the context menu.
|
||||
This will display the export dialog in which the user must:
|
||||
|
||||
#. Select an item (or the entire scene) to export. Selecting an item will cause the item to be hilighted in the original
|
||||
graphic window (but this hilight will not be displayed in the exported file).
|
||||
#. Select an export format.
|
||||
#. Change any desired export options.
|
||||
#. Click the 'export' button.
|
||||
|
||||
Export Formats
|
||||
--------------
|
||||
|
||||
* Image - PNG is the default format. The exact set of image formats supported will depend on your Qt libraries. However,
|
||||
common formats such as PNG, JPG, and TIFF are almost always available.
|
||||
* SVG - Graphics exported as SVG are targeted to work as well as possible with both Inkscape and
|
||||
Adobe Illustrator. For high quality SVG export, please use PyQtGraph version 0.9.3 or later.
|
||||
This is the preferred method for generating publication graphics from PyQtGraph.
|
||||
* CSV - Exports plotted data as CSV. This exporter _only_ works if a PlotItem is selected for export.
|
||||
* Matplotlib - This exporter opens a new window and attempts to re-plot the
|
||||
data using matplotlib (if available). Note that some graphic features are either not implemented
|
||||
for this exporter or not available in matplotlib. This exporter _only_ works if a PlotItem is selected
|
||||
for export.
|
||||
* Printer - Exports to the operating system's printing service. This exporter is provided for completeness,
|
||||
but is not well supported due to problems with Qt's printing system.
|
||||
|
||||
|
||||
|
||||
Exporting from the API
|
||||
----------------------
|
||||
|
||||
To export a file programatically, follow this example::
|
||||
|
||||
import pyqtgraph as pg
|
||||
|
||||
# generate something to export
|
||||
plt = pg.plot([1,5,2,4,3])
|
||||
|
||||
# create an exporter instance, as an argument give it
|
||||
# the item you wish to export
|
||||
exporter = pg.exporters.ImageExporter.ImageExporter(plt.plotItem)
|
||||
|
||||
# set export parameters if needed
|
||||
exporter.parameters()['width'] = 100 # (note this also affects height parameter)
|
||||
|
||||
# save to file
|
||||
exporter.export('fileName.png')
|
||||
|
||||
|
||||
Exporting 3D Graphics
|
||||
---------------------
|
||||
|
||||
The exporting functionality described above is not yet available for 3D graphics. However, it is possible to
|
||||
generate an image from a GLViewWidget by using QGLWidget.grabFrameBuffer or QGLWidget.renderPixmap::
|
||||
|
||||
glview.grabFrameBuffer().save('fileName.png')
|
||||
|
||||
See the Qt documentation for more information.
|
||||
|
||||
|
8
doc/source/flowchart/flowchart.rst
Normal file
8
doc/source/flowchart/flowchart.rst
Normal file
@ -0,0 +1,8 @@
|
||||
flowchart.Flowchart
|
||||
===================
|
||||
|
||||
.. autoclass:: pyqtgraph.flowchart.Flowchart
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.flowchart.Flowchart.__init__
|
||||
|
145
doc/source/flowchart/index.rst
Normal file
145
doc/source/flowchart/index.rst
Normal file
@ -0,0 +1,145 @@
|
||||
Visual Programming with Flowcharts
|
||||
==================================
|
||||
|
||||
PyQtGraph's flowcharts provide a visual programming environment similar in concept to LabView--functional modules are added to a flowchart and connected by wires to define a more complex and arbitrarily configurable algorithm. A small number of predefined modules (called Nodes) are included with pyqtgraph, but most flowchart developers will want to define their own library of Nodes. At their core, the Nodes are little more than 1) a python function 2) a list of input/output terminals, and 3) an optional widget providing a control panel for the Node. Nodes may transmit/receive any type of Python object via their terminals.
|
||||
|
||||
One major limitation of flowcharts is that there is no mechanism for looping within a flowchart. (however individual Nodes may contain loops (they may contain any Python code at all), and an entire flowchart may be executed from within a loop).
|
||||
|
||||
There are two distinct modes of executing the code in a flowchart:
|
||||
|
||||
1. Provide data to the input terminals of the flowchart. This method is slower and will provide a graphical representation of the data as it passes through the flowchart. This is useful for debugging as it allows the user to inspect the data at each terminal and see where exceptions occurred within the flowchart.
|
||||
2. Call :func:`Flowchart.process() <pyqtgraph.flowchart.Flowchart.process>`. This method does not update the displayed state of the flowchart and only retains the state of each terminal as long as it is needed. Additionally, Nodes which do not contribute to the output values of the flowchart (such as plotting nodes) are ignored. This mode allows for faster processing of large data sets and avoids memory issues which can occur if too much data is present in the flowchart at once (e.g., when processing image data through several stages).
|
||||
|
||||
See the flowchart example for more information.
|
||||
|
||||
API Reference:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
flowchart
|
||||
node
|
||||
terminal
|
||||
|
||||
Basic Use
|
||||
---------
|
||||
|
||||
Flowcharts are most useful in situations where you have a processing stage in your application that you would like to be arbitrarily configurable by the user. Rather than giving a pre-defined algorithm with parameters for the user to tweak, you supply a set of pre-defined functions and allow the user to arrange and connect these functions how they like. A very common example is the use of filter networks in audio / video processing applications.
|
||||
|
||||
To begin, you must decide what the input and output variables will be for your flowchart. Create a flowchart with one terminal defined for each variable::
|
||||
|
||||
## This example creates just a single input and a single output.
|
||||
## Flowcharts may define any number of terminals, though.
|
||||
from pyqtgraph.flowchart import Flowchart
|
||||
fc = Flowchart(terminals={
|
||||
'nameOfInputTerminal': {'io': 'in'},
|
||||
'nameOfOutputTerminal': {'io': 'out'}
|
||||
})
|
||||
|
||||
In the example above, each terminal is defined by a dictionary of options which define the behavior of that terminal (see :func:`Terminal.__init__() <pyqtgraph.flowchart.Terminal.__init__>` for more information and options). Note that Terminals are not typed; any python object may be passed from one Terminal to another.
|
||||
|
||||
Once the flowchart is created, add its control widget to your application::
|
||||
|
||||
ctrl = fc.ctrlWidget()
|
||||
myLayout.addWidget(ctrl) ## read Qt docs on QWidget and layouts for more information
|
||||
|
||||
The control widget provides several features:
|
||||
|
||||
* Displays a list of all nodes in the flowchart containing the control widget for
|
||||
each node.
|
||||
* Provides access to the flowchart design window via the 'flowchart' button
|
||||
* Interface for saving / restoring flowcharts to disk.
|
||||
|
||||
At this point your user has the ability to generate flowcharts based on the built-in node library. It is recommended to provide a default set of flowcharts for your users to build from.
|
||||
|
||||
All that remains is to process data through the flowchart. As noted above, there are two ways to do this:
|
||||
|
||||
.. _processing methods:
|
||||
|
||||
1. Set the values of input terminals with :func:`Flowchart.setInput() <pyqtgraph.flowchart.Flowchart.setInput>`, then read the values of output terminals with :func:`Flowchart.output() <pyqtgraph.flowchart.Flowchart.output>`::
|
||||
|
||||
fc.setInput(nameOfInputTerminal=newValue)
|
||||
output = fc.output() # returns {terminalName:value}
|
||||
|
||||
This method updates all of the values displayed in the flowchart design window, allowing the user to inspect values at all terminals in the flowchart and indicating the location of errors that occurred during processing.
|
||||
2. Call :func:`Flowchart.process() <pyqtgraph.flowchart.Flowchart.process>`::
|
||||
|
||||
output = fc.process(nameOfInputTerminal=newValue)
|
||||
|
||||
This method processes data without updating any of the displayed terminal values. Additionally, all :func:`Node.process() <pyqtgraph.flowchart.Node.process>` methods are called with display=False to request that they not invoke any custom display code. This allows data to be processed both more quickly and with a smaller memory footprint, but errors that occur during Flowchart.process() will be more difficult for the user to diagnose. It is thus recommended to use this method for batch processing through flowcharts that have already been tested and debugged with method 1.
|
||||
|
||||
Implementing Custom Nodes
|
||||
-------------------------
|
||||
|
||||
PyQtGraph includes a small library of built-in flowchart nodes. This library is intended to cover some of the most commonly-used functions as well as provide examples for some more exotic Node types. Most applications that use the flowchart system will find the built-in library insufficient and will thus need to implement custom Node classes.
|
||||
|
||||
A node subclass implements at least:
|
||||
|
||||
1) A list of input / output terminals and their properties
|
||||
2) A :func:`process() <pyqtgraph.flowchart.Node.process>` function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys.
|
||||
|
||||
Optionally, a Node subclass can implement the :func:`ctrlWidget() <pyqtgraph.flowchart.Node.ctrlWidget>` method, which must return a QWidget (usually containing other widgets) that will be displayed in the flowchart control panel. A minimal Node subclass looks like::
|
||||
|
||||
class SpecialFunctionNode(Node):
|
||||
"""SpecialFunction: short description
|
||||
|
||||
This description will appear in the flowchart design window when the user
|
||||
selects a node of this type.
|
||||
"""
|
||||
nodeName = 'SpecialFunction' # Node type name that will appear to the user.
|
||||
|
||||
def __init__(self, name): # all Nodes are provided a unique name when they
|
||||
# are created.
|
||||
Node.__init__(self, name, terminals={ # Initialize with a dict
|
||||
# describing the I/O terminals
|
||||
# on this Node.
|
||||
'inputTerminalName': {'io': 'in'},
|
||||
'anotherInputTerminal': {'io': 'in'},
|
||||
'outputTerminalName': {'io': 'out'},
|
||||
})
|
||||
|
||||
def process(self, **kwds):
|
||||
# kwds will have one keyword argument per input terminal.
|
||||
|
||||
return {'outputTerminalName': result}
|
||||
|
||||
def ctrlWidget(self): # this method is optional
|
||||
return someQWidget
|
||||
|
||||
Some nodes implement fairly complex control widgets, but most nodes follow a simple form-like pattern: a list of parameter names and a single value (represented as spin box, check box, etc..) for each parameter. To make this easier, the :class:`~pyqtgraph.flowchart.library.common.CtrlNode` subclass allows you to instead define a simple data structure that CtrlNode will use to automatically generate the control widget. This is used in many of the built-in library nodes (especially the filters).
|
||||
|
||||
There are many other optional parameters for nodes and terminals -- whether the user is allowed to add/remove/rename terminals, whether one terminal may be connected to many others or just one, etc. See the documentation on the :class:`~pyqtgraph.flowchart.Node` and :class:`~pyqtgraph.flowchart.Terminal` classes for more details.
|
||||
|
||||
After implementing a new Node subclass, you will most likely want to register the class so that it appears in the menu of Nodes the user can select from::
|
||||
|
||||
import pyqtgraph.flowchart.library as fclib
|
||||
fclib.registerNodeType(SpecialFunctionNode, [('Category', 'Sub-Category')])
|
||||
|
||||
The second argument to registerNodeType is a list of tuples, with each tuple describing a menu location in which SpecialFunctionNode should appear.
|
||||
|
||||
See the FlowchartCustomNode example for more information.
|
||||
|
||||
|
||||
Debugging Custom Nodes
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When designing flowcharts or custom Nodes, it is important to set the input of the flowchart with data that at least has the same types and structure as the data you intend to process (see `processing methods`_ #1 above). When you use :func:`Flowchart.setInput() <pyqtgraph.flowchart.Flowchart.setInput>`, the flowchart displays visual feedback in its design window that can tell you what data is present at any terminal and whether there were errors in processing. Nodes that generated errors are displayed with a red border. If you select a Node, its input and output values will be displayed as well as the exception that occurred while the node was processing, if any.
|
||||
|
||||
|
||||
Using Nodes Without Flowcharts
|
||||
------------------------------
|
||||
|
||||
Flowchart Nodes implement a very useful generalization in data processing by combining a function with a GUI for configuring that function. This generalization is useful even outside the context of a flowchart. For example::
|
||||
|
||||
## We defined a useful filter Node for use in flowcharts, but would like to
|
||||
## re-use its processing code and GUI without having a flowchart present.
|
||||
filterNode = MyFilterNode("filterNodeName")
|
||||
|
||||
## get the Node's control widget and place it inside the main window
|
||||
filterCtrl = filterNode.ctrlWidget()
|
||||
someLayout.addWidget(filterCtrl)
|
||||
|
||||
## later on, process data through the node
|
||||
filteredData = filterNode.process(inputTerminal=rawData)
|
||||
|
||||
|
8
doc/source/flowchart/node.rst
Normal file
8
doc/source/flowchart/node.rst
Normal file
@ -0,0 +1,8 @@
|
||||
flowchart.Node
|
||||
==============
|
||||
|
||||
.. autoclass:: pyqtgraph.flowchart.Node
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.flowchart.Node.__init__
|
||||
|
8
doc/source/flowchart/terminal.rst
Normal file
8
doc/source/flowchart/terminal.rst
Normal file
@ -0,0 +1,8 @@
|
||||
flowchart.Terminal
|
||||
==================
|
||||
|
||||
.. autoclass:: pyqtgraph.flowchart.Terminal
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.flowchart.Terminal.__init__
|
||||
|
@ -20,8 +20,10 @@ Contents:
|
||||
3dgraphics
|
||||
style
|
||||
region_of_interest
|
||||
exporting
|
||||
prototyping
|
||||
parametertree/index
|
||||
flowchart/index
|
||||
internals
|
||||
apireference
|
||||
|
||||
|
@ -18,14 +18,8 @@ Visual Programming Flowcharts
|
||||
|
||||
Pyqtgraph's flowcharts provide a visual programming environment similar in concept to LabView--functional modules are added to a flowchart and connected by wires to define a more complex and arbitrarily configurable algorithm. A small number of predefined modules (called Nodes) are included with pyqtgraph, but most flowchart developers will want to define their own library of Nodes. At their core, the Nodes are little more than 1) a Python function 2) a list of input/output terminals, and 3) an optional widget providing a control panel for the Node. Nodes may transmit/receive any type of Python object via their terminals.
|
||||
|
||||
One major limitation of flowcharts is that there is no mechanism for looping within a flowchart. (however individual Nodes may contain loops (they may contain any Python code at all), and an entire flowchart may be executed from within a loop).
|
||||
See the `flowchart documentation <flowchart>`_ and the flowchart examples for more information.
|
||||
|
||||
There are two distinct modes of executing the code in a flowchart:
|
||||
|
||||
1. Provide data to the input terminals of the flowchart. This method is slower and will provide a graphical representation of the data as it passes through the flowchart. This is useful for debugging as it allows the user to inspect the data at each terminal and see where exceptions occurred within the flowchart.
|
||||
2. Call Flowchart.process. This method does not update the displayed state of the flowchart and only retains the state of each terminal as long as it is needed. Additionally, Nodes which do not contribute to the output values of the flowchart (such as plotting nodes) are ignored. This mode allows for faster processing of large data sets and avoids memory issues which can occur if doo much data is present in the flowchart at once (e.g., when processing image data through several stages).
|
||||
|
||||
See the flowchart example for more information.
|
||||
|
||||
Graphical Canvas
|
||||
----------------
|
||||
|
28
doc/source/qtcrashcourse.rst
Normal file
28
doc/source/qtcrashcourse.rst
Normal file
@ -0,0 +1,28 @@
|
||||
Qt Crash Course
|
||||
===============
|
||||
|
||||
Pyqtgraph makes extensive use of Qt for generating nearly all of its visual output and interfaces. Qt's documentation is very well written and we encourage all pyqtgraph developers to familiarize themselves with it. The purpose of this section is to provide an introduction to programming with Qt (using either PyQt or PySide) for the pyqtgraph developer.
|
||||
|
||||
|
||||
QWidgets and Layouts
|
||||
--------------------
|
||||
|
||||
Signals, Slots, and Events
|
||||
--------------------------
|
||||
|
||||
|
||||
GraphicsView and GraphicsItems
|
||||
------------------------------
|
||||
|
||||
|
||||
Coordinate Systems
|
||||
------------------
|
||||
|
||||
|
||||
Mouse and Keyboard Input
|
||||
------------------------
|
||||
|
||||
|
||||
QTimer, the Event Loop, and Multi-Threading
|
||||
-------------------------------------------
|
||||
|
@ -21,21 +21,24 @@ import pyqtgraph.metaarray as metaarray
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
|
||||
## Create main window with grid layout
|
||||
win = QtGui.QMainWindow()
|
||||
cw = QtGui.QWidget()
|
||||
win.setCentralWidget(cw)
|
||||
layout = QtGui.QGridLayout()
|
||||
cw.setLayout(layout)
|
||||
|
||||
## Create flowchart, define input/output terminals
|
||||
fc = Flowchart(terminals={
|
||||
'dataIn': {'io': 'in'},
|
||||
'dataOut': {'io': 'out'}
|
||||
})
|
||||
w = fc.widget()
|
||||
|
||||
## Add flowchart control panel to the main window
|
||||
layout.addWidget(fc.widget(), 0, 0, 2, 1)
|
||||
|
||||
## Add two plot widgets
|
||||
pw1 = pg.PlotWidget()
|
||||
pw2 = pg.PlotWidget()
|
||||
layout.addWidget(pw1, 0, 1)
|
||||
@ -43,14 +46,17 @@ layout.addWidget(pw2, 1, 1)
|
||||
|
||||
win.show()
|
||||
|
||||
|
||||
## generate signal data to pass through the flowchart
|
||||
data = np.random.normal(size=1000)
|
||||
data[200:300] += 1
|
||||
data += np.sin(np.linspace(0, 100, 1000))
|
||||
data = metaarray.MetaArray(data, info=[{'name': 'Time', 'values': np.linspace(0, 1.0, len(data))}, {}])
|
||||
|
||||
## Feed data into the input terminal of the flowchart
|
||||
fc.setInput(dataIn=data)
|
||||
|
||||
## populate the flowchart with a basic set of processing nodes.
|
||||
## (usually we let the user do this)
|
||||
pw1Node = fc.createNode('PlotWidget', pos=(0, -150))
|
||||
pw1Node.setPlot(pw1)
|
||||
|
||||
@ -59,42 +65,12 @@ pw2Node.setPlot(pw2)
|
||||
|
||||
fNode = fc.createNode('GaussianFilter', pos=(0, 0))
|
||||
fNode.ctrls['sigma'].setValue(5)
|
||||
fc.connectTerminals(fc.dataIn, fNode.In)
|
||||
fc.connectTerminals(fc.dataIn, pw1Node.In)
|
||||
fc.connectTerminals(fNode.Out, pw2Node.In)
|
||||
fc.connectTerminals(fNode.Out, fc.dataOut)
|
||||
fc.connectTerminals(fc['dataIn'], fNode['In'])
|
||||
fc.connectTerminals(fc['dataIn'], pw1Node['In'])
|
||||
fc.connectTerminals(fNode['Out'], pw2Node['In'])
|
||||
fc.connectTerminals(fNode['Out'], fc['dataOut'])
|
||||
|
||||
|
||||
#n1 = fc.createNode('Add', pos=(0,-80))
|
||||
#n2 = fc.createNode('Subtract', pos=(140,-10))
|
||||
#n3 = fc.createNode('Abs', pos=(0, 80))
|
||||
#n4 = fc.createNode('Add', pos=(140,100))
|
||||
|
||||
#fc.connectTerminals(fc.dataIn, n1.A)
|
||||
#fc.connectTerminals(fc.dataIn, n1.B)
|
||||
#fc.connectTerminals(fc.dataIn, n2.A)
|
||||
#fc.connectTerminals(n1.Out, n4.A)
|
||||
#fc.connectTerminals(n1.Out, n2.B)
|
||||
#fc.connectTerminals(n2.Out, n3.In)
|
||||
#fc.connectTerminals(n3.Out, n4.B)
|
||||
#fc.connectTerminals(n4.Out, fc.dataOut)
|
||||
|
||||
|
||||
#def process(**kargs):
|
||||
#return fc.process(**kargs)
|
||||
|
||||
|
||||
#print process(dataIn=7)
|
||||
|
||||
#fc.setInput(dataIn=3)
|
||||
|
||||
#s = fc.saveState()
|
||||
#fc.clear()
|
||||
|
||||
#fc.restoreState(s)
|
||||
|
||||
#fc.setInput(dataIn=3)
|
||||
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
if __name__ == '__main__':
|
||||
|
144
examples/FlowchartCustomNode.py
Normal file
144
examples/FlowchartCustomNode.py
Normal file
@ -0,0 +1,144 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This example demonstrates writing a custom Node subclass for use with flowcharts.
|
||||
|
||||
We implement a couple of simple image processing nodes.
|
||||
"""
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
from pyqtgraph.flowchart import Flowchart, Node
|
||||
import pyqtgraph.flowchart.library as fclib
|
||||
from pyqtgraph.flowchart.library.common import CtrlNode
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
import scipy.ndimage
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
## Create main window with a grid layout inside
|
||||
win = QtGui.QMainWindow()
|
||||
cw = QtGui.QWidget()
|
||||
win.setCentralWidget(cw)
|
||||
layout = QtGui.QGridLayout()
|
||||
cw.setLayout(layout)
|
||||
|
||||
## Create an empty flowchart with a single input and output
|
||||
fc = Flowchart(terminals={
|
||||
'dataIn': {'io': 'in'},
|
||||
'dataOut': {'io': 'out'}
|
||||
})
|
||||
w = fc.widget()
|
||||
|
||||
layout.addWidget(fc.widget(), 0, 0, 2, 1)
|
||||
|
||||
## Create two ImageView widgets to display the raw and processed data with contrast
|
||||
## and color control.
|
||||
v1 = pg.ImageView()
|
||||
v2 = pg.ImageView()
|
||||
layout.addWidget(v1, 0, 1)
|
||||
layout.addWidget(v2, 1, 1)
|
||||
|
||||
win.show()
|
||||
|
||||
## generate random input data
|
||||
data = np.random.normal(size=(100,100))
|
||||
data = 25 * scipy.ndimage.gaussian_filter(data, (5,5))
|
||||
data += np.random.normal(size=(100,100))
|
||||
data[40:60, 40:60] += 15.0
|
||||
data[30:50, 30:50] += 15.0
|
||||
#data += np.sin(np.linspace(0, 100, 1000))
|
||||
#data = metaarray.MetaArray(data, info=[{'name': 'Time', 'values': np.linspace(0, 1.0, len(data))}, {}])
|
||||
|
||||
## Set the raw data as the input value to the flowchart
|
||||
fc.setInput(dataIn=data)
|
||||
|
||||
|
||||
## At this point, we need some custom Node classes since those provided in the library
|
||||
## are not sufficient. Each node will define a set of input/output terminals, a
|
||||
## processing function, and optionally a control widget (to be displayed in the
|
||||
## flowchart control panel)
|
||||
|
||||
class ImageViewNode(Node):
|
||||
"""Node that displays image data in an ImageView widget"""
|
||||
nodeName = 'ImageView'
|
||||
|
||||
def __init__(self, name):
|
||||
self.view = None
|
||||
## Initialize node with only a single input terminal
|
||||
Node.__init__(self, name, terminals={'data': {'io':'in'}})
|
||||
|
||||
def setView(self, view): ## setView must be called by the program
|
||||
self.view = view
|
||||
|
||||
def process(self, data, display=True):
|
||||
## if process is called with display=False, then the flowchart is being operated
|
||||
## in batch processing mode, so we should skip displaying to improve performance.
|
||||
|
||||
if display and self.view is not None:
|
||||
## the 'data' argument is the value given to the 'data' terminal
|
||||
if data is None:
|
||||
self.view.setImage(np.zeros((1,1))) # give a blank array to clear the view
|
||||
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
|
||||
## control widget based on a simple data structure.
|
||||
class UnsharpMaskNode(CtrlNode):
|
||||
"""Return the input data passed through scipy.ndimage.gaussian_filter."""
|
||||
nodeName = "UnsharpMask"
|
||||
uiTemplate = [
|
||||
('sigma', 'spin', {'value': 1.0, 'step': 1.0, 'range': [0.0, None]}),
|
||||
('strength', 'spin', {'value': 1.0, 'dec': True, 'step': 0.5, 'minStep': 0.01, 'range': [0.0, None]}),
|
||||
]
|
||||
def __init__(self, name):
|
||||
## Define the input / output terminals available on this node
|
||||
terminals = {
|
||||
'dataIn': dict(io='in'), # each terminal needs at least a name and
|
||||
'dataOut': dict(io='out'), # to specify whether it is input or output
|
||||
} # other more advanced options are available
|
||||
# as well..
|
||||
|
||||
CtrlNode.__init__(self, name, terminals=terminals)
|
||||
|
||||
def process(self, dataIn, display=True):
|
||||
# CtrlNode has created self.ctrls, which is a dict containing {ctrlName: widget}
|
||||
sigma = self.ctrls['sigma'].value()
|
||||
strength = self.ctrls['strength'].value()
|
||||
output = dataIn - (strength * scipy.ndimage.gaussian_filter(dataIn, (sigma,sigma)))
|
||||
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',)])
|
||||
|
||||
|
||||
|
||||
## 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
|
||||
## flowchart file.
|
||||
|
||||
v1Node = fc.createNode('ImageView', pos=(0, -150))
|
||||
v1Node.setView(v1)
|
||||
|
||||
v2Node = fc.createNode('ImageView', pos=(150, -150))
|
||||
v2Node.setView(v2)
|
||||
|
||||
fNode = fc.createNode('UnsharpMask', pos=(0, 0))
|
||||
fc.connectTerminals(fc['dataIn'], fNode['dataIn'])
|
||||
fc.connectTerminals(fc['dataIn'], v1Node['data'])
|
||||
fc.connectTerminals(fNode['dataOut'], v2Node['data'])
|
||||
fc.connectTerminals(fNode['dataOut'], fc['dataOut'])
|
||||
|
||||
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||
QtGui.QApplication.instance().exec_()
|
@ -67,6 +67,7 @@ examples = OrderedDict([
|
||||
|
||||
('GraphicsScene', 'GraphicsScene.py'),
|
||||
('Flowcharts', 'Flowchart.py'),
|
||||
('Custom Flowchart Nodes', 'FlowchartCustomNode.py'),
|
||||
#('Canvas', '../canvas'),
|
||||
#('MultiPlotWidget', 'MultiPlotWidget.py'),
|
||||
])
|
||||
|
@ -106,6 +106,9 @@ class Flowchart(Node):
|
||||
self.addTerminal(name, **opts)
|
||||
|
||||
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.
|
||||
"""
|
||||
#print "setInput", args
|
||||
#Node.setInput(self, **args)
|
||||
#print " ....."
|
||||
@ -113,10 +116,15 @@ class Flowchart(Node):
|
||||
self.inputNode.setOutput(**args)
|
||||
|
||||
def outputChanged(self):
|
||||
self.widget().outputChanged(self.outputNode.inputValues())
|
||||
self.sigOutputChanged.emit(self)
|
||||
## called when output of internal node has changed
|
||||
vals = self.outputNode.inputValues()
|
||||
self.widget().outputChanged(vals)
|
||||
self.setOutput(**vals)
|
||||
#self.sigOutputChanged.emit(self)
|
||||
|
||||
def output(self):
|
||||
"""Return a dict of the values on the Flowchart's output terminals.
|
||||
"""
|
||||
return self.outputNode.inputValues()
|
||||
|
||||
def nodes(self):
|
||||
@ -261,7 +269,9 @@ class Flowchart(Node):
|
||||
def process(self, **args):
|
||||
"""
|
||||
Process data through the flowchart, returning the output.
|
||||
Keyword arguments must be the names of input terminals
|
||||
|
||||
Keyword arguments must be the names of input terminals.
|
||||
The return value is a dict with one key per output terminal.
|
||||
|
||||
"""
|
||||
data = {} ## Stores terminal:value pairs
|
||||
|
@ -13,6 +13,18 @@ def strDict(d):
|
||||
return dict([(str(k), v) for k, v in d.items()])
|
||||
|
||||
class Node(QtCore.QObject):
|
||||
"""
|
||||
Node represents the basic processing unit of a flowchart.
|
||||
A Node subclass implements at least:
|
||||
|
||||
1) A list of input / ouptut terminals and their properties
|
||||
2) a process() function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys.
|
||||
|
||||
A flowchart thus consists of multiple instances of Node subclasses, each of which is connected
|
||||
to other by wires between their terminals. A flowchart is, itself, also a special subclass of Node.
|
||||
This allows Nodes within the flowchart to connect to the input/output nodes of the flowchart itself.
|
||||
|
||||
Optionally, a node class can implement the ctrlWidget() method, which must return a QWidget (usually containing other widgets) that will be displayed in the flowchart control panel. Some nodes implement fairly complex control widgets, but most nodes follow a simple form-like pattern: a list of parameter names and a single value (represented as spin box, check box, etc..) for each parameter. To make this easier, the CtrlNode subclass allows you to instead define a simple data structure that CtrlNode will use to automatically generate the control widget. """
|
||||
|
||||
sigOutputChanged = QtCore.Signal(object) # self
|
||||
sigClosed = QtCore.Signal(object)
|
||||
@ -23,6 +35,31 @@ class Node(QtCore.QObject):
|
||||
|
||||
|
||||
def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True):
|
||||
"""
|
||||
============== ============================================================
|
||||
Arguments
|
||||
name The name of this specific node instance. It can be any
|
||||
string, but must be unique within a flowchart. Usually,
|
||||
we simply let the flowchart decide on a name when calling
|
||||
Flowchart.addNode(...)
|
||||
terminals Dict-of-dicts specifying the terminals present on this Node.
|
||||
Terminal specifications look like::
|
||||
|
||||
'inputTerminalName': {'io': 'in'}
|
||||
'outputTerminalName': {'io': 'out'}
|
||||
|
||||
There are a number of optional parameters for terminals:
|
||||
multi, pos, renamable, removable, multiable, bypass. See
|
||||
the Terminal class for more information.
|
||||
allowAddInput bool; whether the user is allowed to add inputs by the
|
||||
context menu.
|
||||
allowAddOutput bool; whether the user is allowed to add outputs by the
|
||||
context menu.
|
||||
allowRemove bool; whether the user is allowed to remove this node by the
|
||||
context menu.
|
||||
============== ============================================================
|
||||
|
||||
"""
|
||||
QtCore.QObject.__init__(self)
|
||||
self._name = name
|
||||
self._bypass = False
|
||||
@ -52,15 +89,25 @@ class Node(QtCore.QObject):
|
||||
return name2
|
||||
|
||||
def addInput(self, name="Input", **args):
|
||||
"""Add a new input terminal to this Node with the given name. Extra
|
||||
keyword arguments are passed to Terminal.__init__.
|
||||
|
||||
This is a convenience function that just calls addTerminal(io='in', ...)"""
|
||||
#print "Node.addInput called."
|
||||
return self.addTerminal(name, io='in', **args)
|
||||
|
||||
def addOutput(self, name="Output", **args):
|
||||
"""Add a new output terminal to this Node with the given name. Extra
|
||||
keyword arguments are passed to Terminal.__init__.
|
||||
|
||||
This is a convenience function that just calls addTerminal(io='out', ...)"""
|
||||
return self.addTerminal(name, io='out', **args)
|
||||
|
||||
def removeTerminal(self, term):
|
||||
## term may be a terminal or its name
|
||||
"""Remove the specified terminal from this Node. May specify either the
|
||||
terminal's name or the terminal itself.
|
||||
|
||||
Causes sigTerminalRemoved to be emitted."""
|
||||
if isinstance(term, Terminal):
|
||||
name = term.name()
|
||||
else:
|
||||
@ -80,7 +127,9 @@ class Node(QtCore.QObject):
|
||||
|
||||
|
||||
def terminalRenamed(self, term, oldName):
|
||||
"""Called after a terminal has been renamed"""
|
||||
"""Called after a terminal has been renamed
|
||||
|
||||
Causes sigTerminalRenamed to be emitted."""
|
||||
newName = term.name()
|
||||
for d in [self.terminals, self._inputs, self._outputs]:
|
||||
if oldName not in d:
|
||||
@ -92,6 +141,10 @@ class Node(QtCore.QObject):
|
||||
self.sigTerminalRenamed.emit(term, oldName)
|
||||
|
||||
def addTerminal(self, name, **opts):
|
||||
"""Add a new terminal to this Node with the given name. Extra
|
||||
keyword arguments are passed to Terminal.__init__.
|
||||
|
||||
Causes sigTerminalAdded to be emitted."""
|
||||
name = self.nextTerminalName(name)
|
||||
term = Terminal(self, name, **opts)
|
||||
self.terminals[name] = term
|
||||
@ -105,38 +158,60 @@ class Node(QtCore.QObject):
|
||||
|
||||
|
||||
def inputs(self):
|
||||
"""Return dict of all input terminals.
|
||||
Warning: do not modify."""
|
||||
return self._inputs
|
||||
|
||||
def outputs(self):
|
||||
"""Return dict of all output terminals.
|
||||
Warning: do not modify."""
|
||||
return self._outputs
|
||||
|
||||
def process(self, **kargs):
|
||||
"""Process data through this node. Each named argument supplies data to the corresponding terminal."""
|
||||
"""Process data through this node. This method is called any time the flowchart
|
||||
wants the node to process data. It will be called with one keyword argument
|
||||
corresponding to each input terminal, and must return a dict mapping the name
|
||||
of each output terminal to its new value.
|
||||
|
||||
This method is also called with a 'display' keyword argument, which indicates
|
||||
whether the node should update its display (if it implements any) while processing
|
||||
this data. This is primarily used to disable expensive display operations
|
||||
during batch processing.
|
||||
"""
|
||||
return {}
|
||||
|
||||
def graphicsItem(self):
|
||||
"""Return a (the?) graphicsitem for this node"""
|
||||
#print "Node.graphicsItem called."
|
||||
"""Return the GraphicsItem for this node. Subclasses may re-implement
|
||||
this method to customize their appearance in the flowchart."""
|
||||
if self._graphicsItem is None:
|
||||
#print "Creating NodeGraphicsItem..."
|
||||
self._graphicsItem = NodeGraphicsItem(self)
|
||||
#print "Node.graphicsItem is returning ", self._graphicsItem
|
||||
return self._graphicsItem
|
||||
|
||||
## this is just bad planning. Causes too many bugs.
|
||||
def __getattr__(self, attr):
|
||||
"""Return the terminal with the given name"""
|
||||
if attr not in self.terminals:
|
||||
raise AttributeError(attr)
|
||||
else:
|
||||
import traceback
|
||||
traceback.print_stack()
|
||||
print("Warning: use of node.terminalName is deprecated; use node['terminalName'] instead.")
|
||||
return self.terminals[attr]
|
||||
|
||||
def __getitem__(self, item):
|
||||
return getattr(self, item)
|
||||
#return getattr(self, item)
|
||||
"""Return the terminal with the given name"""
|
||||
if item not in self.terminals:
|
||||
raise KeyError(item)
|
||||
else:
|
||||
return self.terminals[item]
|
||||
|
||||
def name(self):
|
||||
"""Return the name of this node."""
|
||||
return self._name
|
||||
|
||||
def rename(self, name):
|
||||
"""Rename this node. This will cause sigRenamed to be emitted."""
|
||||
oldName = self._name
|
||||
self._name = name
|
||||
#self.emit(QtCore.SIGNAL('renamed'), self, oldName)
|
||||
@ -154,15 +229,29 @@ class Node(QtCore.QObject):
|
||||
return "<Node %s @%x>" % (self.name(), id(self))
|
||||
|
||||
def ctrlWidget(self):
|
||||
"""Return this Node's control widget.
|
||||
|
||||
By default, Nodes have no control widget. Subclasses may reimplement this
|
||||
method to provide a custom widget. This method is called by Flowcharts
|
||||
when they are constructing their Node list."""
|
||||
return None
|
||||
|
||||
def bypass(self, byp):
|
||||
"""Set whether this node should be bypassed.
|
||||
|
||||
When bypassed, a Node's process() method is never called. In some cases,
|
||||
data is automatically copied directly from specific input nodes to
|
||||
output nodes instead (see the bypass argument to Terminal.__init__).
|
||||
This is usually called when the user disables a node from the flowchart
|
||||
control panel.
|
||||
"""
|
||||
self._bypass = byp
|
||||
if self.bypassButton is not None:
|
||||
self.bypassButton.setChecked(byp)
|
||||
self.update()
|
||||
|
||||
def isBypassed(self):
|
||||
"""Return True if this Node is currently bypassed."""
|
||||
return self._bypass
|
||||
|
||||
def setInput(self, **args):
|
||||
@ -179,12 +268,14 @@ class Node(QtCore.QObject):
|
||||
self.update()
|
||||
|
||||
def inputValues(self):
|
||||
"""Return a dict of all input values currently assigned to this node."""
|
||||
vals = {}
|
||||
for n, t in self.inputs().items():
|
||||
vals[n] = t.value()
|
||||
return vals
|
||||
|
||||
def outputValues(self):
|
||||
"""Return a dict of all output values currently generated by this node."""
|
||||
vals = {}
|
||||
for n, t in self.outputs().items():
|
||||
vals[n] = t.value()
|
||||
@ -195,11 +286,15 @@ class Node(QtCore.QObject):
|
||||
pass
|
||||
|
||||
def disconnected(self, localTerm, remoteTerm):
|
||||
"""Called whenever one of this node's terminals is connected elsewhere."""
|
||||
"""Called whenever one of this node's terminals is disconnected from another."""
|
||||
pass
|
||||
|
||||
def update(self, signal=True):
|
||||
"""Collect all input values, attempt to process new output values, and propagate downstream."""
|
||||
"""Collect all input values, attempt to process new output values, and propagate downstream.
|
||||
Subclasses should call update() whenever thir internal state has changed
|
||||
(such as when the user interacts with the Node's control widget). Update
|
||||
is automatically called when the inputs to the node are changed.
|
||||
"""
|
||||
vals = self.inputValues()
|
||||
#print " inputs:", vals
|
||||
try:
|
||||
@ -227,6 +322,9 @@ class Node(QtCore.QObject):
|
||||
self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data
|
||||
|
||||
def processBypassed(self, args):
|
||||
"""Called when the flowchart would normally call Node.process, but this node is currently bypassed.
|
||||
The default implementation looks for output terminals with a bypass connection and returns the
|
||||
corresponding values. Most Node subclasses will _not_ need to reimplement this method."""
|
||||
result = {}
|
||||
for term in list(self.outputs().values()):
|
||||
byp = term.bypassValue()
|
||||
@ -266,6 +364,13 @@ class Node(QtCore.QObject):
|
||||
self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(150, 0, 0), 3))
|
||||
|
||||
def saveState(self):
|
||||
"""Return a dictionary representing the current state of this node
|
||||
(excluding input / output values). This is used for saving/reloading
|
||||
flowcharts. The default implementation returns this Node's position,
|
||||
bypass state, and information about each of its terminals.
|
||||
|
||||
Subclasses may want to extend this method, adding extra keys to the returned
|
||||
dict."""
|
||||
pos = self.graphicsItem().pos()
|
||||
state = {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()}
|
||||
termsEditable = self._allowAddInput | self._allowAddOutput
|
||||
@ -276,6 +381,8 @@ class Node(QtCore.QObject):
|
||||
return state
|
||||
|
||||
def restoreState(self, state):
|
||||
"""Restore the state of this node from a structure previously generated
|
||||
by saveState(). """
|
||||
pos = state.get('pos', (0,0))
|
||||
self.graphicsItem().setPos(*pos)
|
||||
self.bypass(state.get('bypass', False))
|
||||
|
@ -24,6 +24,8 @@ class Terminal(object):
|
||||
renamable (bool) Whether the terminal can be renamed by the user
|
||||
removable (bool) Whether the terminal can be removed by the user
|
||||
multiable (bool) Whether the user may toggle the *multi* option for this terminal
|
||||
bypass (str) Name of the terminal from which this terminal's value is derived
|
||||
when the Node is in bypass mode.
|
||||
============== =================================================================================
|
||||
"""
|
||||
self._io = io
|
||||
|
@ -426,10 +426,12 @@ class PlotCurveItem(GraphicsObject):
|
||||
p.fillPath(self.fillPath, self.opts['brush'])
|
||||
prof.mark('draw fill path')
|
||||
|
||||
sp = fn.mkPen(self.opts['shadowPen'])
|
||||
cp = fn.mkPen(self.opts['pen'])
|
||||
|
||||
## Copy pens and apply alpha adjustment
|
||||
sp = QtGui.QPen(self.opts['shadowPen'])
|
||||
cp = QtGui.QPen(self.opts['pen'])
|
||||
#sp = QtGui.QPen(self.opts['shadowPen'])
|
||||
#cp = QtGui.QPen(self.opts['pen'])
|
||||
#for pen in [sp, cp]:
|
||||
#if pen is None:
|
||||
#continue
|
||||
|
@ -379,9 +379,6 @@ class PlotDataItem(GraphicsObject):
|
||||
|
||||
|
||||
def updateItems(self):
|
||||
#for c in self.curves+self.scatters:
|
||||
#if c.scene() is not None:
|
||||
#c.scene().removeItem(c)
|
||||
|
||||
curveArgs = {}
|
||||
for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillBrush', 'brush'), ('antialias', 'antialias')]:
|
||||
@ -399,18 +396,12 @@ class PlotDataItem(GraphicsObject):
|
||||
self.curve.show()
|
||||
else:
|
||||
self.curve.hide()
|
||||
#curve = PlotCurveItem(x=x, y=y, **curveArgs)
|
||||
#curve.setParentItem(self)
|
||||
#self.curves.append(curve)
|
||||
|
||||
if scatterArgs['symbol'] is not None:
|
||||
self.scatter.setData(x=x, y=y, **scatterArgs)
|
||||
self.scatter.show()
|
||||
else:
|
||||
self.scatter.hide()
|
||||
#sp = ScatterPlotItem(x=x, y=y, **scatterArgs)
|
||||
#sp.setParentItem(self)
|
||||
#self.scatters.append(sp)
|
||||
|
||||
|
||||
def getData(self):
|
||||
|
Loading…
Reference in New Issue
Block a user