commit
cebc292b59
|
@ -166,6 +166,8 @@ class Flowchart(Node):
|
||||||
n[oldName].rename(newName)
|
n[oldName].rename(newName)
|
||||||
|
|
||||||
def createNode(self, nodeType, name=None, pos=None):
|
def createNode(self, nodeType, name=None, pos=None):
|
||||||
|
"""Create a new Node and add it to this flowchart.
|
||||||
|
"""
|
||||||
if name is None:
|
if name is None:
|
||||||
n = 0
|
n = 0
|
||||||
while True:
|
while True:
|
||||||
|
@ -179,6 +181,10 @@ class Flowchart(Node):
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def addNode(self, node, name, pos=None):
|
def addNode(self, node, name, pos=None):
|
||||||
|
"""Add an existing Node to this flowchart.
|
||||||
|
|
||||||
|
See also: createNode()
|
||||||
|
"""
|
||||||
if pos is None:
|
if pos is None:
|
||||||
pos = [0, 0]
|
pos = [0, 0]
|
||||||
if type(pos) in [QtCore.QPoint, QtCore.QPointF]:
|
if type(pos) in [QtCore.QPoint, QtCore.QPointF]:
|
||||||
|
@ -189,6 +195,7 @@ class Flowchart(Node):
|
||||||
self.viewBox.addItem(item)
|
self.viewBox.addItem(item)
|
||||||
item.moveBy(*pos)
|
item.moveBy(*pos)
|
||||||
self._nodes[name] = node
|
self._nodes[name] = node
|
||||||
|
if node is not self.inputNode and node is not self.outputNode:
|
||||||
self.widget().addNode(node)
|
self.widget().addNode(node)
|
||||||
node.sigClosed.connect(self.nodeClosed)
|
node.sigClosed.connect(self.nodeClosed)
|
||||||
node.sigRenamed.connect(self.nodeRenamed)
|
node.sigRenamed.connect(self.nodeRenamed)
|
||||||
|
@ -196,6 +203,8 @@ class Flowchart(Node):
|
||||||
self.sigChartChanged.emit(self, 'add', node)
|
self.sigChartChanged.emit(self, 'add', node)
|
||||||
|
|
||||||
def removeNode(self, node):
|
def removeNode(self, node):
|
||||||
|
"""Remove a Node from this flowchart.
|
||||||
|
"""
|
||||||
node.close()
|
node.close()
|
||||||
|
|
||||||
def nodeClosed(self, node):
|
def nodeClosed(self, node):
|
||||||
|
@ -233,7 +242,6 @@ class Flowchart(Node):
|
||||||
term2 = self.internalTerminal(term2)
|
term2 = self.internalTerminal(term2)
|
||||||
term1.connectTo(term2)
|
term1.connectTo(term2)
|
||||||
|
|
||||||
|
|
||||||
def process(self, **args):
|
def process(self, **args):
|
||||||
"""
|
"""
|
||||||
Process data through the flowchart, returning the output.
|
Process data through the flowchart, returning the output.
|
||||||
|
@ -325,7 +333,6 @@ class Flowchart(Node):
|
||||||
|
|
||||||
#print "DEPS:", deps
|
#print "DEPS:", deps
|
||||||
## determine correct node-processing order
|
## determine correct node-processing order
|
||||||
#deps[self] = []
|
|
||||||
order = fn.toposort(deps)
|
order = fn.toposort(deps)
|
||||||
#print "ORDER1:", order
|
#print "ORDER1:", order
|
||||||
|
|
||||||
|
@ -349,7 +356,6 @@ class Flowchart(Node):
|
||||||
if lastNode is None or ind > lastInd:
|
if lastNode is None or ind > lastInd:
|
||||||
lastNode = n
|
lastNode = n
|
||||||
lastInd = ind
|
lastInd = ind
|
||||||
#tdeps[t] = lastNode
|
|
||||||
if lastInd is not None:
|
if lastInd is not None:
|
||||||
dels.append((lastInd+1, t))
|
dels.append((lastInd+1, t))
|
||||||
dels.sort(key=lambda a: a[0], reverse=True)
|
dels.sort(key=lambda a: a[0], reverse=True)
|
||||||
|
@ -405,26 +411,24 @@ class Flowchart(Node):
|
||||||
else:
|
else:
|
||||||
self.sigStateChanged.emit()
|
self.sigStateChanged.emit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def chartGraphicsItem(self):
|
def chartGraphicsItem(self):
|
||||||
"""Return the graphicsItem which displays the internals of this flowchart.
|
"""Return the graphicsItem that displays the internal nodes and
|
||||||
(graphicsItem() still returns the external-view item)"""
|
connections of this flowchart.
|
||||||
#return self._chartGraphicsItem
|
|
||||||
|
Note that the similar method `graphicsItem()` is inherited from Node
|
||||||
|
and returns the *external* graphical representation of this flowchart."""
|
||||||
return self.viewBox
|
return self.viewBox
|
||||||
|
|
||||||
def widget(self):
|
def widget(self):
|
||||||
|
"""Return the control widget for this flowchart.
|
||||||
|
|
||||||
|
This widget provides GUI access to the parameters for each node and a
|
||||||
|
graphical representation of the flowchart.
|
||||||
|
"""
|
||||||
if self._widget is None:
|
if self._widget is None:
|
||||||
self._widget = FlowchartCtrlWidget(self)
|
self._widget = FlowchartCtrlWidget(self)
|
||||||
self.scene = self._widget.scene()
|
self.scene = self._widget.scene()
|
||||||
self.viewBox = self._widget.viewBox()
|
self.viewBox = self._widget.viewBox()
|
||||||
#self._scene = QtGui.QGraphicsScene()
|
|
||||||
#self._widget.setScene(self._scene)
|
|
||||||
#self.scene.addItem(self.chartGraphicsItem())
|
|
||||||
|
|
||||||
#ci = self.chartGraphicsItem()
|
|
||||||
#self.viewBox.addItem(ci)
|
|
||||||
#self.viewBox.autoRange()
|
|
||||||
return self._widget
|
return self._widget
|
||||||
|
|
||||||
def listConnections(self):
|
def listConnections(self):
|
||||||
|
@ -437,10 +441,11 @@ class Flowchart(Node):
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
def saveState(self):
|
def saveState(self):
|
||||||
|
"""Return a serializable data structure representing the current state of this flowchart.
|
||||||
|
"""
|
||||||
state = Node.saveState(self)
|
state = Node.saveState(self)
|
||||||
state['nodes'] = []
|
state['nodes'] = []
|
||||||
state['connects'] = []
|
state['connects'] = []
|
||||||
#state['terminals'] = self.saveTerminals()
|
|
||||||
|
|
||||||
for name, node in self._nodes.items():
|
for name, node in self._nodes.items():
|
||||||
cls = type(node)
|
cls = type(node)
|
||||||
|
@ -460,6 +465,8 @@ class Flowchart(Node):
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def restoreState(self, state, clear=False):
|
def restoreState(self, state, clear=False):
|
||||||
|
"""Restore the state of this flowchart from a previous call to `saveState()`.
|
||||||
|
"""
|
||||||
self.blockSignals(True)
|
self.blockSignals(True)
|
||||||
try:
|
try:
|
||||||
if clear:
|
if clear:
|
||||||
|
@ -469,7 +476,6 @@ class Flowchart(Node):
|
||||||
nodes.sort(key=lambda a: a['pos'][0])
|
nodes.sort(key=lambda a: a['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']].graphicsItem().moveBy(*n['pos'])
|
|
||||||
self._nodes[n['name']].restoreState(n['state'])
|
self._nodes[n['name']].restoreState(n['state'])
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
|
@ -477,7 +483,6 @@ class Flowchart(Node):
|
||||||
node.restoreState(n['state'])
|
node.restoreState(n['state'])
|
||||||
except:
|
except:
|
||||||
printExc("Error creating node %s: (continuing anyway)" % n['name'])
|
printExc("Error creating node %s: (continuing anyway)" % n['name'])
|
||||||
#node.graphicsItem().moveBy(*n['pos'])
|
|
||||||
|
|
||||||
self.inputNode.restoreState(state.get('inputNode', {}))
|
self.inputNode.restoreState(state.get('inputNode', {}))
|
||||||
self.outputNode.restoreState(state.get('outputNode', {}))
|
self.outputNode.restoreState(state.get('outputNode', {}))
|
||||||
|
@ -491,55 +496,52 @@ class Flowchart(Node):
|
||||||
print(self._nodes[n2].terminals)
|
print(self._nodes[n2].terminals)
|
||||||
printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2))
|
printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2))
|
||||||
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.blockSignals(False)
|
self.blockSignals(False)
|
||||||
|
|
||||||
self.sigChartLoaded.emit()
|
self.sigChartLoaded.emit()
|
||||||
self.outputChanged()
|
self.outputChanged()
|
||||||
self.sigStateChanged.emit()
|
self.sigStateChanged.emit()
|
||||||
#self.sigOutputChanged.emit()
|
|
||||||
|
|
||||||
def loadFile(self, fileName=None, startDir=None):
|
def loadFile(self, fileName=None, startDir=None):
|
||||||
|
"""Load a flowchart (*.fc) file.
|
||||||
|
"""
|
||||||
if fileName is None:
|
if fileName is None:
|
||||||
if startDir is None:
|
if startDir is None:
|
||||||
startDir = self.filePath
|
startDir = self.filePath
|
||||||
if startDir is None:
|
if startDir is None:
|
||||||
startDir = '.'
|
startDir = '.'
|
||||||
self.fileDialog = FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)")
|
self.fileDialog = FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)")
|
||||||
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
|
||||||
#self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
|
||||||
self.fileDialog.show()
|
self.fileDialog.show()
|
||||||
self.fileDialog.fileSelected.connect(self.loadFile)
|
self.fileDialog.fileSelected.connect(self.loadFile)
|
||||||
return
|
return
|
||||||
## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs..
|
## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs..
|
||||||
#fileName = QtGui.QFileDialog.getOpenFileName(None, "Load Flowchart..", startDir, "Flowchart (*.fc)")
|
|
||||||
fileName = unicode(fileName)
|
fileName = unicode(fileName)
|
||||||
state = configfile.readConfigFile(fileName)
|
state = configfile.readConfigFile(fileName)
|
||||||
self.restoreState(state, clear=True)
|
self.restoreState(state, clear=True)
|
||||||
self.viewBox.autoRange()
|
self.viewBox.autoRange()
|
||||||
#self.emit(QtCore.SIGNAL('fileLoaded'), fileName)
|
|
||||||
self.sigFileLoaded.emit(fileName)
|
self.sigFileLoaded.emit(fileName)
|
||||||
|
|
||||||
def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'):
|
def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'):
|
||||||
|
"""Save this flowchart to a .fc file
|
||||||
|
"""
|
||||||
if fileName is None:
|
if fileName is None:
|
||||||
if startDir is None:
|
if startDir is None:
|
||||||
startDir = self.filePath
|
startDir = self.filePath
|
||||||
if startDir is None:
|
if startDir is None:
|
||||||
startDir = '.'
|
startDir = '.'
|
||||||
self.fileDialog = FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)")
|
self.fileDialog = FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)")
|
||||||
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
|
|
||||||
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
|
||||||
#self.fileDialog.setDirectory(startDir)
|
|
||||||
self.fileDialog.show()
|
self.fileDialog.show()
|
||||||
self.fileDialog.fileSelected.connect(self.saveFile)
|
self.fileDialog.fileSelected.connect(self.saveFile)
|
||||||
return
|
return
|
||||||
#fileName = QtGui.QFileDialog.getSaveFileName(None, "Save Flowchart..", startDir, "Flowchart (*.fc)")
|
|
||||||
fileName = unicode(fileName)
|
fileName = unicode(fileName)
|
||||||
configfile.writeConfigFile(self.saveState(), fileName)
|
configfile.writeConfigFile(self.saveState(), fileName)
|
||||||
self.sigFileSaved.emit(fileName)
|
self.sigFileSaved.emit(fileName)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
"""Remove all nodes from this flowchart except the original input/output nodes.
|
||||||
|
"""
|
||||||
for n in list(self._nodes.values()):
|
for n in list(self._nodes.values()):
|
||||||
if n is self.inputNode or n is self.outputNode:
|
if n is self.inputNode or n is self.outputNode:
|
||||||
continue
|
continue
|
||||||
|
@ -552,18 +554,15 @@ class Flowchart(Node):
|
||||||
self.inputNode.clearTerminals()
|
self.inputNode.clearTerminals()
|
||||||
self.outputNode.clearTerminals()
|
self.outputNode.clearTerminals()
|
||||||
|
|
||||||
#class FlowchartGraphicsItem(QtGui.QGraphicsItem):
|
|
||||||
class FlowchartGraphicsItem(GraphicsObject):
|
class FlowchartGraphicsItem(GraphicsObject):
|
||||||
|
|
||||||
def __init__(self, chart):
|
def __init__(self, chart):
|
||||||
#print "FlowchartGraphicsItem.__init__"
|
|
||||||
#QtGui.QGraphicsItem.__init__(self)
|
|
||||||
GraphicsObject.__init__(self)
|
GraphicsObject.__init__(self)
|
||||||
self.chart = chart ## chart is an instance of Flowchart()
|
self.chart = chart ## chart is an instance of Flowchart()
|
||||||
self.updateTerminals()
|
self.updateTerminals()
|
||||||
|
|
||||||
def updateTerminals(self):
|
def updateTerminals(self):
|
||||||
#print "FlowchartGraphicsItem.updateTerminals"
|
|
||||||
self.terminals = {}
|
self.terminals = {}
|
||||||
bounds = self.boundingRect()
|
bounds = self.boundingRect()
|
||||||
inp = self.chart.inputs()
|
inp = self.chart.inputs()
|
||||||
|
@ -759,6 +758,7 @@ class FlowchartCtrlWidget(QtGui.QWidget):
|
||||||
item = self.items[node]
|
item = self.items[node]
|
||||||
self.ui.ctrlList.setCurrentItem(item)
|
self.ui.ctrlList.setCurrentItem(item)
|
||||||
|
|
||||||
|
|
||||||
class FlowchartWidget(dockarea.DockArea):
|
class FlowchartWidget(dockarea.DockArea):
|
||||||
"""Includes the actual graphical flowchart and debugging interface"""
|
"""Includes the actual graphical flowchart and debugging interface"""
|
||||||
def __init__(self, chart, ctrl):
|
def __init__(self, chart, ctrl):
|
||||||
|
|
|
@ -189,31 +189,36 @@ class EvalNode(Node):
|
||||||
|
|
||||||
self.ui = QtGui.QWidget()
|
self.ui = QtGui.QWidget()
|
||||||
self.layout = QtGui.QGridLayout()
|
self.layout = QtGui.QGridLayout()
|
||||||
#self.addInBtn = QtGui.QPushButton('+Input')
|
|
||||||
#self.addOutBtn = QtGui.QPushButton('+Output')
|
|
||||||
self.text = QtGui.QTextEdit()
|
self.text = QtGui.QTextEdit()
|
||||||
self.text.setTabStopWidth(30)
|
self.text.setTabStopWidth(30)
|
||||||
self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal")
|
self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal")
|
||||||
#self.layout.addWidget(self.addInBtn, 0, 0)
|
|
||||||
#self.layout.addWidget(self.addOutBtn, 0, 1)
|
|
||||||
self.layout.addWidget(self.text, 1, 0, 1, 2)
|
self.layout.addWidget(self.text, 1, 0, 1, 2)
|
||||||
self.ui.setLayout(self.layout)
|
self.ui.setLayout(self.layout)
|
||||||
|
|
||||||
#QtCore.QObject.connect(self.addInBtn, QtCore.SIGNAL('clicked()'), self.addInput)
|
|
||||||
#self.addInBtn.clicked.connect(self.addInput)
|
|
||||||
#QtCore.QObject.connect(self.addOutBtn, QtCore.SIGNAL('clicked()'), self.addOutput)
|
|
||||||
#self.addOutBtn.clicked.connect(self.addOutput)
|
|
||||||
self.text.focusOutEvent = self.focusOutEvent
|
self.text.focusOutEvent = self.focusOutEvent
|
||||||
self.lastText = None
|
self.lastText = None
|
||||||
|
|
||||||
def ctrlWidget(self):
|
def ctrlWidget(self):
|
||||||
return self.ui
|
return self.ui
|
||||||
|
|
||||||
#def addInput(self):
|
def setCode(self, code):
|
||||||
#Node.addInput(self, 'input', renamable=True)
|
# unindent code; this allows nicer inline code specification when
|
||||||
|
# calling this method.
|
||||||
|
ind = []
|
||||||
|
lines = code.split('\n')
|
||||||
|
for line in lines:
|
||||||
|
stripped = line.lstrip()
|
||||||
|
if len(stripped) > 0:
|
||||||
|
ind.append(len(line) - len(stripped))
|
||||||
|
if len(ind) > 0:
|
||||||
|
ind = min(ind)
|
||||||
|
code = '\n'.join([line[ind:] for line in lines])
|
||||||
|
|
||||||
#def addOutput(self):
|
self.text.clear()
|
||||||
#Node.addOutput(self, 'output', renamable=True)
|
self.text.insertPlainText(code)
|
||||||
|
|
||||||
|
def code(self):
|
||||||
|
return self.text.toPlainText()
|
||||||
|
|
||||||
def focusOutEvent(self, ev):
|
def focusOutEvent(self, ev):
|
||||||
text = str(self.text.toPlainText())
|
text = str(self.text.toPlainText())
|
||||||
|
@ -247,11 +252,11 @@ class EvalNode(Node):
|
||||||
|
|
||||||
def restoreState(self, state):
|
def restoreState(self, state):
|
||||||
Node.restoreState(self, state)
|
Node.restoreState(self, state)
|
||||||
self.text.clear()
|
self.setCode(state['text'])
|
||||||
self.text.insertPlainText(state['text'])
|
|
||||||
self.restoreTerminals(state['terminals'])
|
self.restoreTerminals(state['terminals'])
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
class ColumnJoinNode(Node):
|
class ColumnJoinNode(Node):
|
||||||
"""Concatenates record arrays and/or adds new columns"""
|
"""Concatenates record arrays and/or adds new columns"""
|
||||||
nodeName = 'ColumnJoin'
|
nodeName = 'ColumnJoin'
|
||||||
|
@ -354,3 +359,117 @@ class ColumnJoinNode(Node):
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
|
class Mean(CtrlNode):
|
||||||
|
"""Calculate the mean of an array across an axis.
|
||||||
|
"""
|
||||||
|
nodeName = 'Mean'
|
||||||
|
uiTemplate = [
|
||||||
|
('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def processData(self, data):
|
||||||
|
s = self.stateGroup.state()
|
||||||
|
ax = None if s['axis'] == -1 else s['axis']
|
||||||
|
return data.mean(axis=ax)
|
||||||
|
|
||||||
|
|
||||||
|
class Max(CtrlNode):
|
||||||
|
"""Calculate the maximum of an array across an axis.
|
||||||
|
"""
|
||||||
|
nodeName = 'Max'
|
||||||
|
uiTemplate = [
|
||||||
|
('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def processData(self, data):
|
||||||
|
s = self.stateGroup.state()
|
||||||
|
ax = None if s['axis'] == -1 else s['axis']
|
||||||
|
return data.max(axis=ax)
|
||||||
|
|
||||||
|
|
||||||
|
class Min(CtrlNode):
|
||||||
|
"""Calculate the minimum of an array across an axis.
|
||||||
|
"""
|
||||||
|
nodeName = 'Min'
|
||||||
|
uiTemplate = [
|
||||||
|
('axis', 'intSpin', {'value': 0, 'min': -1, 'max': 1000000}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def processData(self, data):
|
||||||
|
s = self.stateGroup.state()
|
||||||
|
ax = None if s['axis'] == -1 else s['axis']
|
||||||
|
return data.min(axis=ax)
|
||||||
|
|
||||||
|
|
||||||
|
class Stdev(CtrlNode):
|
||||||
|
"""Calculate the standard deviation of an array across an axis.
|
||||||
|
"""
|
||||||
|
nodeName = 'Stdev'
|
||||||
|
uiTemplate = [
|
||||||
|
('axis', 'intSpin', {'value': -0, 'min': -1, 'max': 1000000}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def processData(self, data):
|
||||||
|
s = self.stateGroup.state()
|
||||||
|
ax = None if s['axis'] == -1 else s['axis']
|
||||||
|
return data.std(axis=ax)
|
||||||
|
|
||||||
|
|
||||||
|
class Index(CtrlNode):
|
||||||
|
"""Select an index from an array axis.
|
||||||
|
"""
|
||||||
|
nodeName = 'Index'
|
||||||
|
uiTemplate = [
|
||||||
|
('axis', 'intSpin', {'value': 0, 'min': 0, 'max': 1000000}),
|
||||||
|
('index', 'intSpin', {'value': 0, 'min': 0, 'max': 1000000}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def processData(self, data):
|
||||||
|
s = self.stateGroup.state()
|
||||||
|
ax = s['axis']
|
||||||
|
ind = s['index']
|
||||||
|
if ax == 0:
|
||||||
|
# allow support for non-ndarray sequence types
|
||||||
|
return data[ind]
|
||||||
|
else:
|
||||||
|
return data.take(ind, axis=ax)
|
||||||
|
|
||||||
|
|
||||||
|
class Slice(CtrlNode):
|
||||||
|
"""Select a slice from an array axis.
|
||||||
|
"""
|
||||||
|
nodeName = 'Slice'
|
||||||
|
uiTemplate = [
|
||||||
|
('axis', 'intSpin', {'value': 0, 'min': 0, 'max': 1e6}),
|
||||||
|
('start', 'intSpin', {'value': 0, 'min': -1e6, 'max': 1e6}),
|
||||||
|
('stop', 'intSpin', {'value': -1, 'min': -1e6, 'max': 1e6}),
|
||||||
|
('step', 'intSpin', {'value': 1, 'min': -1e6, 'max': 1e6}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def processData(self, data):
|
||||||
|
s = self.stateGroup.state()
|
||||||
|
ax = s['axis']
|
||||||
|
start = s['start']
|
||||||
|
stop = s['stop']
|
||||||
|
step = s['step']
|
||||||
|
if ax == 0:
|
||||||
|
# allow support for non-ndarray sequence types
|
||||||
|
return data[start:stop:step]
|
||||||
|
else:
|
||||||
|
sl = [slice(None) for i in range(data.ndim)]
|
||||||
|
sl[ax] = slice(start, stop, step)
|
||||||
|
return data[sl]
|
||||||
|
|
||||||
|
|
||||||
|
class AsType(CtrlNode):
|
||||||
|
"""Convert an array to a different dtype.
|
||||||
|
"""
|
||||||
|
nodeName = 'AsType'
|
||||||
|
uiTemplate = [
|
||||||
|
('dtype', 'combo', {'values': ['float', 'int', 'float32', 'float64', 'float128', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64'], 'index': 0}),
|
||||||
|
]
|
||||||
|
|
||||||
|
def processData(self, data):
|
||||||
|
s = self.stateGroup.state()
|
||||||
|
return data.astype(s['dtype'])
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Bessel(CtrlNode):
|
||||||
nodeName = 'BesselFilter'
|
nodeName = 'BesselFilter'
|
||||||
uiTemplate = [
|
uiTemplate = [
|
||||||
('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}),
|
('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}),
|
||||||
('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
||||||
('order', 'intSpin', {'value': 4, 'min': 1, 'max': 16}),
|
('order', 'intSpin', {'value': 4, 'min': 1, 'max': 16}),
|
||||||
('bidir', 'check', {'checked': True})
|
('bidir', 'check', {'checked': True})
|
||||||
]
|
]
|
||||||
|
@ -57,10 +57,10 @@ class Butterworth(CtrlNode):
|
||||||
nodeName = 'ButterworthFilter'
|
nodeName = 'ButterworthFilter'
|
||||||
uiTemplate = [
|
uiTemplate = [
|
||||||
('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}),
|
('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}),
|
||||||
('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
||||||
('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
||||||
('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
||||||
('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
||||||
('bidir', 'check', {'checked': True})
|
('bidir', 'check', {'checked': True})
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -78,14 +78,14 @@ class ButterworthNotch(CtrlNode):
|
||||||
"""Butterworth notch filter"""
|
"""Butterworth notch filter"""
|
||||||
nodeName = 'ButterworthNotchFilter'
|
nodeName = 'ButterworthNotchFilter'
|
||||||
uiTemplate = [
|
uiTemplate = [
|
||||||
('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
||||||
('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
||||||
('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
||||||
('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
||||||
('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
||||||
('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
|
||||||
('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
||||||
('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'bounds': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
|
||||||
('bidir', 'check', {'checked': True})
|
('bidir', 'check', {'checked': True})
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -160,19 +160,13 @@ class Gaussian(CtrlNode):
|
||||||
|
|
||||||
@metaArrayWrapper
|
@metaArrayWrapper
|
||||||
def processData(self, data):
|
def processData(self, data):
|
||||||
|
sigma = self.ctrls['sigma'].value()
|
||||||
try:
|
try:
|
||||||
import scipy.ndimage
|
import scipy.ndimage
|
||||||
|
return scipy.ndimage.gaussian_filter(data, sigma)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise Exception("GaussianFilter node requires the package scipy.ndimage.")
|
return pgfn.gaussianFilter(data, sigma)
|
||||||
|
|
||||||
if hasattr(data, 'implements') and data.implements('MetaArray'):
|
|
||||||
info = data.infoCopy()
|
|
||||||
filt = pgfn.gaussianFilter(data.asarray(), self.ctrls['sigma'].value())
|
|
||||||
if 'values' in info[0]:
|
|
||||||
info[0]['values'] = info[0]['values'][:filt.shape[0]]
|
|
||||||
return metaarray.MetaArray(filt, info=info)
|
|
||||||
else:
|
|
||||||
return pgfn.gaussianFilter(data, self.ctrls['sigma'].value())
|
|
||||||
|
|
||||||
class Derivative(CtrlNode):
|
class Derivative(CtrlNode):
|
||||||
"""Returns the pointwise derivative of the input"""
|
"""Returns the pointwise derivative of the input"""
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from ..Node import Node
|
from ..Node import Node
|
||||||
|
from .common import CtrlNode
|
||||||
|
|
||||||
|
|
||||||
class UniOpNode(Node):
|
class UniOpNode(Node):
|
||||||
"""Generic node for performing any operation like Out = In.fn()"""
|
"""Generic node for performing any operation like Out = In.fn()"""
|
||||||
|
@ -13,11 +15,22 @@ class UniOpNode(Node):
|
||||||
def process(self, **args):
|
def process(self, **args):
|
||||||
return {'Out': getattr(args['In'], self.fn)()}
|
return {'Out': getattr(args['In'], self.fn)()}
|
||||||
|
|
||||||
class BinOpNode(Node):
|
class BinOpNode(CtrlNode):
|
||||||
"""Generic node for performing any operation like A.fn(B)"""
|
"""Generic node for performing any operation like A.fn(B)"""
|
||||||
|
|
||||||
|
_dtypes = [
|
||||||
|
'float64', 'float32', 'float16',
|
||||||
|
'int64', 'int32', 'int16', 'int8',
|
||||||
|
'uint64', 'uint32', 'uint16', 'uint8'
|
||||||
|
]
|
||||||
|
|
||||||
|
uiTemplate = [
|
||||||
|
('outputType', 'combo', {'values': ['no change', 'input A', 'input B'] + _dtypes , 'index': 0})
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, name, fn):
|
def __init__(self, name, fn):
|
||||||
self.fn = fn
|
self.fn = fn
|
||||||
Node.__init__(self, name, terminals={
|
CtrlNode.__init__(self, name, terminals={
|
||||||
'A': {'io': 'in'},
|
'A': {'io': 'in'},
|
||||||
'B': {'io': 'in'},
|
'B': {'io': 'in'},
|
||||||
'Out': {'io': 'out', 'bypass': 'A'}
|
'Out': {'io': 'out', 'bypass': 'A'}
|
||||||
|
@ -36,6 +49,18 @@ class BinOpNode(Node):
|
||||||
out = fn(args['B'])
|
out = fn(args['B'])
|
||||||
if out is NotImplemented:
|
if out is NotImplemented:
|
||||||
raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B']))))
|
raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B']))))
|
||||||
|
|
||||||
|
# Coerce dtype if requested
|
||||||
|
typ = self.stateGroup.state()['outputType']
|
||||||
|
if typ == 'no change':
|
||||||
|
pass
|
||||||
|
elif typ == 'input A':
|
||||||
|
out = out.astype(args['A'].dtype)
|
||||||
|
elif typ == 'input B':
|
||||||
|
out = out.astype(args['B'].dtype)
|
||||||
|
else:
|
||||||
|
out = out.astype(typ)
|
||||||
|
|
||||||
#print " ", fn, out
|
#print " ", fn, out
|
||||||
return {'Out': out}
|
return {'Out': out}
|
||||||
|
|
||||||
|
@ -71,4 +96,10 @@ class DivideNode(BinOpNode):
|
||||||
# try truediv first, followed by div
|
# try truediv first, followed by div
|
||||||
BinOpNode.__init__(self, name, ('__truediv__', '__div__'))
|
BinOpNode.__init__(self, name, ('__truediv__', '__div__'))
|
||||||
|
|
||||||
|
class FloorDivideNode(BinOpNode):
|
||||||
|
"""Returns A // B. Does not check input types."""
|
||||||
|
nodeName = 'FloorDivide'
|
||||||
|
def __init__(self, name):
|
||||||
|
BinOpNode.__init__(self, name, '__floordiv__')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,11 @@ def generateUi(opts):
|
||||||
k, t, o = opt
|
k, t, o = opt
|
||||||
else:
|
else:
|
||||||
raise Exception("Widget specification must be (name, type) or (name, type, {opts})")
|
raise Exception("Widget specification must be (name, type) or (name, type, {opts})")
|
||||||
|
|
||||||
|
## clean out these options so they don't get sent to SpinBox
|
||||||
|
hidden = o.pop('hidden', False)
|
||||||
|
tip = o.pop('tip', None)
|
||||||
|
|
||||||
if t == 'intSpin':
|
if t == 'intSpin':
|
||||||
w = QtGui.QSpinBox()
|
w = QtGui.QSpinBox()
|
||||||
if 'max' in o:
|
if 'max' in o:
|
||||||
|
@ -63,11 +68,12 @@ def generateUi(opts):
|
||||||
w = ColorButton()
|
w = ColorButton()
|
||||||
else:
|
else:
|
||||||
raise Exception("Unknown widget type '%s'" % str(t))
|
raise Exception("Unknown widget type '%s'" % str(t))
|
||||||
if 'tip' in o:
|
|
||||||
w.setToolTip(o['tip'])
|
if tip is not None:
|
||||||
|
w.setToolTip(tip)
|
||||||
w.setObjectName(k)
|
w.setObjectName(k)
|
||||||
l.addRow(k, w)
|
l.addRow(k, w)
|
||||||
if o.get('hidden', False):
|
if hidden:
|
||||||
w.hide()
|
w.hide()
|
||||||
label = l.labelForField(w)
|
label = l.labelForField(w)
|
||||||
label.hide()
|
label.hide()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user