From: <zk...@us...> - 2009-03-17 13:42:46
|
Revision: 621 http://pyphant.svn.sourceforge.net/pyphant/?rev=621&view=rev Author: zklaus Date: 2009-03-17 13:42:37 +0000 (Tue, 17 Mar 2009) Log Message: ----------- Merge branch 'SampleContainerSlicing' Conflicts: src/pyphant/pyphant/core/DataContainer.py src/pyphant/pyphant/tests/TestDataContainer.py Modified Paths: -------------- trunk/src/pyphant/pyphant/core/DataContainer.py trunk/src/pyphant/pyphant/tests/TestDataContainer.py trunk/src/workers/ImageProcessing/ImageProcessing/__init__.py Added Paths: ----------- trunk/src/workers/ImageProcessing/ImageProcessing/FilterWorker.py Modified: trunk/src/pyphant/pyphant/core/DataContainer.py =================================================================== --- trunk/src/pyphant/pyphant/core/DataContainer.py 2009-03-17 13:41:49 UTC (rev 620) +++ trunk/src/pyphant/pyphant/core/DataContainer.py 2009-03-17 13:42:37 UTC (rev 621) @@ -265,8 +265,9 @@ def numberOfColumns(self): return len(self.columns) - #helper method for filter(expression) which parses human input to python code, ensuring no harm can be done by eval() - def _parseExpression(self, expression): + #helper method for filterDiscarded(expression) which parses human input to python code, ensuring no harm can be done by eval() + #this method is discarded + def _parseExpressionDiscarded(self, expression): import re reDoubleQuotes = re.compile(r'("[^"][^"]*")') reSplit = re.compile(r'(<(?!=)|<=|>(?!=)|>=|==|!=|and|or|not|AND|OR|NOT|\(|\))') @@ -308,15 +309,27 @@ return None #resolve multiple CompareOps like a <= b <= c == d: - ral = abstractlist[:] #future resolved abstractlist + ral = abstractlist[:] #<-- future resolved abstractlist i = 0 values = ['PhysQuant', 'SCColumn', 'Number'] + start_sequence = -1 while i < len(ral) - 4: if (ral[i][0] in values) and (ral[i+1][0] == 'CompareOp') and (ral[i+2][0] in values) and (ral[i+3][0] == 'CompareOp') and (ral[i+4][0] in values): + if start_sequence == -1: start_sequence = i ral.insert(i+3, ('Delimiter', 'and')) ral.insert(i+4, ral[i+2]) i += 4 - else: i += 1 + else: + if start_sequence != -1: #<-- this is necessary because 'not' has higher precedence than 'and' + ral.insert(start_sequence, ('Delimiter', '(')) + ral.insert(i+4, ('Delimiter', ')')) + start_sequence = -1 + i += 3 + else: + i += 1 + if start_sequence != -1: + ral.insert(start_sequence, ('Delimiter', '(')) + ral.insert(i+4, ('Delimiter', ')')) #parse splitted expression to fit requierements of python eval() method: parsed = '' @@ -331,13 +344,16 @@ #returns new SampleContainer containing all entries that match expression - def filter(self, expression): - parsed = self._parseExpression(expression) + #this method has been replaced by the new version of SampleContainer.filter + def filterDiscarded(self, expression): + if expression == '': + return copy.deepcopy(self) + + parsed = self._parseExpressionDiscarded(expression) if parsed == None: return None - #TODO: Nicer Iteration, reject multidim arrays or even better: handle them correctly, - # check whether all columns have same length + #TODO: check whether all columns have same length #create the selection mask mask = [] @@ -370,6 +386,335 @@ return result + #This method generates nested tuples of filter commands to be applied to a SampleContainer out of a string expression + def _getCommands(self, expression): + #TODO: compare SCColumns with each other, + # allow multi dimensional columns (think up new syntax) + + import re + #test for expressions containing whitespaces only: + if len(re.findall(r'\S', expression)) == 0: + return () + + #prepare regular expressions + reDoubleQuotes = re.compile(r'("[^"][^"]*")') + reSplit = re.compile(r'(<(?!=)|<=|>(?!=)|>=|==|!=|not|NOT|and|AND|or|OR|\(|\))') + reCompareOp = re.compile(r'<|>|==|!=') + + #split the expression + DQList = reDoubleQuotes.split(expression) + splitlist = [] + for dq in DQList: + if reDoubleQuotes.match(dq) != None: splitlist.append(dq) + else: splitlist.extend(reSplit.split(dq)) + + #identify splitted Elements + al = [] #<-- abstractlist containing tuples of (type, expression) + for e in splitlist: + if len(re.findall(r'\S', e)) == 0: pass #<-- skip whitespaces + elif reCompareOp.match(e) != None: + al.append(('CompareOp', e)) + elif reSplit.match(e) != None: + al.append(('Delimiter', e.lower())) + elif reDoubleQuotes.match(e) != None: + column = None + try: + column = self[e[1:-1]] + except: + raise IndexError, 'Could not find column ' + e + ' in "' + self.longname + '".' + al.append(('SCColumn', column)) + else: + try: + phq = PhysicalQuantity(e) + al.append(('PhysQuant', phq)) + continue + except: + try: + number = PhysicalQuantity(e+' m') + al.append(('Number', eval(e))) + continue + except: pass + raise ValueError, "Error parsing expression: " + e + + #resolve multiple CompareOps like a <= b <= c == d: + i = 0 + values = ['PhysQuant', 'SCColumn', 'Number'] + start_sequence = -1 + while i < len(al) - 4: + if (al[i][0] in values) and (al[i+1][0] == 'CompareOp') and (al[i+2][0] in values) and (al[i+3][0] == 'CompareOp') and (al[i+4][0] in values): + if start_sequence == -1: + start_sequence = i + al.insert(i+3, ('Delimiter', 'and')) + al.insert(i+4, al[i+2]) + i += 4 + else: + if start_sequence != -1: #<-- this is necessary because 'not' has higher precedence than 'and' + al.insert(start_sequence, ('Delimiter', '(')) + al.insert(i+4, ('Delimiter', ')')) + start_sequence = -1 + i += 3 + else: + i += 1 + if start_sequence != -1: + al.insert(start_sequence, ('Delimiter', '(')) + al.insert(i+4, ('Delimiter', ')')) + + #identify atomar components like "a" <= 10m, 10s > "b", ... and compress them: + if al[0][0] == 'CompareOp': + raise ValueError, al[0][1] + " may not stand at the very beginning of an expression!" + i = 1 + valid = False + while i < len(al): + if al[i][0] == 'CompareOp': + left = al.pop(i-1) + middle = al.pop(i-1) + right = al.pop(i-1) + if left[0] not in values: + raise TypeError, str(left[1]) + " is not a proper value." + if right[0] not in values: + raise TypeError, str(right[1]) + " is not a proper value." + al.insert(i-1, ('Atomar', left, middle[1], right)) + valid = True + i += 1 + if not valid: raise ValueError, "There has to be at least one valid comparison: " + expression + + #identify braces and compress them recursively: + def compressBraces(sublist): + openbraces = 0 + start = 0 + end = 0 + finished = False + for i in range(len(sublist)): + if sublist[i] == ('Delimiter', '('): + openbraces += 1 + if openbraces == 1 and not finished: + start = i + elif sublist[i] == ('Delimiter', ')'): + openbraces -= 1 + if openbraces == 0 and not finished: + end = i + finished = True + if openbraces != 0: + raise ValueError, "There are unmatched braces within the expression: " + expression + if start==0 and end==0: + #no more braces found: end of recursion + return sublist[:] + else: + if end-start == 1: + raise ValueError, "There are braces enclosing nothing in the expression: " + expression + middle = None + if end-start == 2: + #discard unnecessary braces in order to reduce recursion depth later on: + middle = sublist[start+1:start+2] + else: + middle = [('Brace', compressBraces(sublist[start+1:end]))] + return sublist[0:start] + middle + compressBraces(sublist[end+1:]) + + #TODO: The following three methods could be merged into one generalized method for compressing unitary and binary operators. This would be useful in a future version when there are lots of operators to be supported. + + #identify "not"s and compress them recursively: + def compressNot(sublist): + i = 0 + while i < len(sublist): + if sublist[i] == ('Delimiter', 'not'): + if i+1 >= len(sublist): + raise ValueError, "'not' must not stand at the very end of an expression: " + expression + x = sublist[i+1] + if x[0] == 'Atomar': + return sublist[0:i] + [('NOT', x)] + compressNot(sublist[i+2:]) + elif x[0] == 'Brace': + return sublist[0:i] + [('NOT', ('Brace', compressNot(x[1])))] + compressNot(sublist[i+2:]) + else: + raise ValueError, "'not' cannot be applied to " + str(x) + "." + elif sublist[i][0] == 'Brace': + return sublist[0:i] + [('Brace', compressNot(sublist[i][1]))] + compressNot(sublist[i+1:]) + i += 1 + return sublist[:] + + #identify "and"s and compress them recursively: + def compressAnd(sublist, start=0): + i = start #<-- start=1 indicates that the 1st element of sublist has already been compressed. This is necessary for binary operators. + while i < len(sublist): + if sublist[i] == ('Delimiter', 'and'): + left = None + if start == 1 and i == 1: left = sublist[i-1] + else: left = compressAnd(sublist[i-1:i])[0] + if left[0] not in ['NOT', 'AND', 'Brace', 'Atomar']: + raise ValueError, "'and' cannot be applied to " + str(left) + "." + right = compressAnd(sublist[i+1:i+2])[0] + if right[0] not in ['NOT', 'AND', 'Brace', 'Atomar']: + raise ValueError, "'and' cannot be applied to " + str(right) + "." + return sublist[0:i-1] + compressAnd([('AND', left, right)] + sublist[i+2:], 1) + elif sublist[i][0] == 'Brace': + return sublist[0:i] + compressAnd([('Brace', compressAnd(sublist[i][1]))] + sublist[i+1:], 1) + elif sublist[i][0] == 'NOT': + return sublist[0:i] + compressAnd([('NOT', compressAnd([sublist[i][1]])[0])] + sublist[i+1:], 1) + i += 1 + return sublist[:] + + #identify "or"s and compress them recursively, decompress braces in order to reduce recursion depth later on: + def compressOrDCB(sublist, start=0): + i = start #<-- start=1 indicates that the 1st element of sublist has already been compressed. This is necessary for binary operators. + while i < len(sublist): + if sublist[i] == ('Delimiter', 'or'): + left = None + if start == 1 and i == 1: left = sublist[i-1] + else: left = compressOrDCB(sublist[i-1:i])[0] + if left[0] not in ['NOT', 'AND', 'Atomar', 'OR']: + raise ValueError, "'or' cannot be applied to " + str(left) + "." + right = compressOrDCB(sublist[i+1:i+2])[0] + if right[0] not in ['NOT', 'AND', 'Atomar', 'OR']: + raise ValueError, "'or' cannot be applied to " + str(right) + "." + return sublist[0:i-1] + compressOrDCB([('OR', left, right)] + sublist[i+2:], 1) + elif sublist[i][0] == 'Brace': + inner = compressOrDCB(sublist[i][1]) + if len(inner) != 1: + raise ValueError, "Expression could not be parsed completely. (probably missing keyword): " + expression + return sublist[0:i] + compressOrDCB(inner + sublist[i+1:], 1) + elif sublist[i][0] == 'NOT': + return sublist[0:i] + compressOrDCB([('NOT', compressOrDCB([sublist[i][1]])[0])] + sublist[i+1:], 1) + elif sublist[i][0] == 'AND': + return sublist[0:i] + compressOrDCB([('AND', compressOrDCB([sublist[i][1]])[0], compressOrDCB([sublist[i][2]])[0])] + sublist[i+1:], 1) + i += 1 + return sublist[:] + + compressed = compressOrDCB(compressAnd(compressNot(compressBraces(al)))) + if len(compressed) != 1: + raise ValueError, "Expression could not be parsed completely (probably missing keyword): " + expression + return compressed[0] + + + #returns a new SampleContainer with filter commands applied to it. Expression can be either a string expression or nested tuples of commands + def filter(self, expression): + #determine type of expression: + from types import StringType, UnicodeType, TupleType + commands = None + if isinstance(expression, UnicodeType): + commands = self._getCommands(expression.encode('utf-8')) + elif isinstance(expression, StringType): + commands = self._getCommands(expression) + elif isinstance(expression, TupleType) or expression==None: + commands = expression + else: + raise TypeError, "Expression has to be of StringType, UnicodeType, TupleType or None. Found " + str(type(expression)) + " instead!" + + #check for empty commands: + if commands == None or commands==(): + return copy.deepcopy(self) + + #generate boolean numpymask from commands using fast numpy methods: + def evaluateAtomar(atomar): + left = atomar[1] + if left[0] == 'SCColumn': + if left[1].data.ndim != 1: + raise NotImplementedError, 'Comparing columns of dimensions other than one is not yet implemented: "' + left[1].longname + '"' + + right = atomar[3] + if right[0] == 'SCColumn': + if right[1].data.ndim != 1: + raise NotImplementedError, 'Comparing columns of dimensions other than one is not yet implemented: "' + right[1].longname + '"' + + leftvalue = None + rightvalue = None + if left[0] == 'SCColumn' and right[0] == 'SCColumn': + number = right[1].unit/left[1].unit + if isPhysicalQuantity(number): + raise TypeError, 'Cannot compare "' + left[1].longname + '" to "' + right[1].longname + '".' + leftvalue = left[1].data + rightvalue = right[1].data*number + elif left[0] == 'SCColumn': + number = right[1]/left[1].unit + if isPhysicalQuantity(number): + raise TypeError, 'Cannot compare "' + left[1].longname + '" to ' + str(right[1]) + '".' + leftvalue = left[1].data + rightvalue = number + elif right[0] == 'SCColumn': + number = left[1]/right[1].unit + if isPhysicalQuantity(number): + raise TypeError, "Cannot compare " + str(left[1]) + ' to "' + right[1].longname + '".' + leftvalue = number + rightvalue = right[1].data + else: + raise ValueError, "At least one argument of '" + atomar[2][1] + "' has to be a column." + + if atomar[2] == '==': return leftvalue == rightvalue + elif atomar[2] == '!=': return leftvalue != rightvalue + elif atomar[2] == '<=': return leftvalue <= rightvalue + elif atomar[2] == '<' : return leftvalue < rightvalue + elif atomar[2] == '>=': return leftvalue >= rightvalue + elif atomar[2] == '>' : return leftvalue > rightvalue + raise ValueError, "Invalid atomar expression: " + str(atomar) + + def getMaskFromCommands(cmds): + if cmds[0] == 'Atomar': + return evaluateAtomar(cmds) + elif cmds[0] == 'AND': + left = getMaskFromCommands(cmds[1]) + right = getMaskFromCommands(cmds[2]) + if left.shape != right.shape: + raise TypeError, "Cannot apply 'and' to columns of different shape: " + str(left.shape) + ", " + str(right.shape) + return numpy.logical_and(left, right) + elif cmds[0] == 'OR': + left = getMaskFromCommands(cmds[1]) + right = getMaskFromCommands(cmds[2]) + if left.shape != right.shape: + raise TypeError, "Cannot apply 'or' to columns of different shape: " + str(left.shape) + ", " + str(right.shape) + return numpy.logical_or(left, right) + elif cmds[0] == 'NOT': + return numpy.logical_not(getMaskFromCommands(cmds[1])) + + numpymask = getMaskFromCommands(commands) + + #the following code is a bit longish whereas time consuming copy.deepcopy operations with subsequent masking are avoided wherever possible + + #generate new columns with the boolean mask applied to them using fast numpy slicing + maskedcolumns = [] + for c in self.columns: + #mask dimension + mdims = [] + try: + for d in c.dimensions: + md = copy.deepcopy(d) #<-- could be avoided too, but creating a new FieldContainer always ends up with standard dimensions whereas dimensions are not supposed to bear dimensions themselves + if mdims == []: #<-- only primary axis has to be masked + #mask errors of dimensions if there are any + if d.error != None: + md.error = d.error[numpymask] + + #mask data of dimensions + if d.data != None: + md.data = d.data[numpymask] + + mdims.append(md) + + #mask errors: + cerr = None + if c.error != None: cerr = c.error[numpymask] + + #mask data: + cdata = None + if c.data != None: cdata = c.data[numpymask] + + maskedcolumns.append(FieldContainer(cdata, + copy.deepcopy(c.unit), + cerr, + copy.deepcopy(c.mask), + mdims, + longname=c.longname, + shortname=c.shortname, + attributes=copy.deepcopy(c.attributes), + rescale=False)) + except ValueError: + raise ValueError, 'Column "' + c.longname + '" has not enough rows!' + + #build new SampleContainer from masked columns and return it + result = SampleContainer(maskedcolumns, + longname=self.longname, + shortname=self.shortname, + attributes=copy.deepcopy(self.attributes)) + return result + + + def assertEqual(con1,con2,rtol=1e-5,atol=1e-8): diagnosis=StringIO.StringIO() testReport = logging.StreamHandler(diagnosis) Modified: trunk/src/pyphant/pyphant/tests/TestDataContainer.py =================================================================== --- trunk/src/pyphant/pyphant/tests/TestDataContainer.py 2009-03-17 13:41:49 UTC (rev 620) +++ trunk/src/pyphant/pyphant/tests/TestDataContainer.py 2009-03-17 13:42:37 UTC (rev 621) @@ -393,15 +393,56 @@ class SampleContainerSlicingTests(SampleContainerTest): - #TODO: Write more tests with more complicated expressions + def setUp(self): + super(SampleContainerSlicingTests, self).setUp() + time_data = numpy.array([10.0, 20.0, 30.0, 5.0, 9000.0]) + time_error = numpy.array([1.0, 2.0, 3.0, .5, 900.0]) + time_unit = PhysicalQuantity('2s') + time_FC = FieldContainer(time_data, time_unit, time_error, None, None, "Zeit", "t", None, False) + + length_data = numpy.array([-20.0, 0.0, 20.0, 10.0, 5.5]) + length_error = numpy.array([2.0, 0.1, 2.0, 1.0, .5]) + length_unit = PhysicalQuantity('1000m') + length_FC = FieldContainer(length_data, length_unit, length_error, None, None, "Strecke", "l", None, False) + + + temperature_data = numpy.array([[10.1, 10.2, 10.3], + [20.1, 20.2, 20.3], + [30.1, 30.2, 30.3], + [40.1, 40.2, 40.3], + [50.1, 50.2, 50.3]]) + temperature_error = numpy.array([[0.1, 0.2, 0.3], + [1.1, 1.2, 1.3], + [2.1, 2.2, 2.3], + [3.1, 3.2, 3.3], + [4.1, 4.2, 4.3]]) + temperature_unit = PhysicalQuantity('1mK') + + temperature_FC = FieldContainer(temperature_data, temperature_unit, temperature_error, None, None, "Temperatur", "T", None, False) + + self.sc2d = SampleContainer([length_FC, temperature_FC, time_FC], "Test Container", "TestC") + + self.sc2d["t"].dimensions[0].unit = PhysicalQuantity('5m') + self.sc2d["t"].dimensions[0].data = numpy.array([-20, -10, 0, 10, 20]) + + self.sc2d["l"].dimensions[0].unit = PhysicalQuantity('2mm') + self.sc2d["l"].dimensions[0].data = numpy.array([-1, -0.5, 0, 0.5, 1]) + + self.sc2d["T"].dimensions[0].unit = PhysicalQuantity('0.5mm') + self.sc2d["T"].dimensions[0].data = numpy.array([-3, -1.5, 0, 1.5, 3]) + self.sc2d["T"].dimensions[1].unit = PhysicalQuantity('10nm') + self.sc2d["T"].dimensions[1].data = numpy.array([-1, 0, 1]) + + + #purely one dimensional Tests: def testConsistancy(self): result1 = self.sampleContainer.filter('20m < "i" and 80m > "i"') result2 = self.sampleContainer.filter('20m < "i" < 80m') self.assertEqual(result1[0], result2[0]) self.assertEqual(result1[1], result2[1]) - def testSimpleExpression(self): - result = self.sampleContainer.filter('50m <= "i" < 57m') + def testSimpleUnicodeExpression(self): + result = self.sampleContainer.filter(u'50m <= "i" < 57m') self.assertEqual(len(result.columns), 2) self.assertEqual(len(result[0].data), 7) self.assertEqual(len(result[1].data), 7) @@ -425,7 +466,54 @@ self.assertEqual(result[0], expectedi) self.assertEqual(result[1], expectedt) + #tests involving 2 dimensional FieldContainers: + def _compareExpected(self, expression, ind): + indices = numpy.array(ind) + result = self.sc2d.filter(expression) + expectedSC = copy.deepcopy(self.sc2d) + for FC in expectedSC: + FC.data = FC.data[indices] + FC.error = FC.error[indices] + FC.dimensions[0].data = FC.dimensions[0].data[indices] + self.assertEqual(result, expectedSC) + def testEmpty2dExpression(self): + result = self.sc2d.filter('') + self.assertEqual(result, self.sc2d) + result = self.sc2d.filter(()) + self.assertEqual(result, self.sc2d) + + def testAtomar2dExpressions(self): + self._compareExpected('"t" <= 40.0s', [True, True, False, True, False]) + self._compareExpected('"l" < 10000m', [True, True, False, False, True]) + self._compareExpected('"Zeit" >= 20.0s', [True, True, True, False, True]) + self._compareExpected('"l" > 5500m', [False, False, True, True, False]) + self._compareExpected('"t" == 18000s', [False, False, False, False, True]) + self._compareExpected('"Strecke" != 20000m', [True, True, False, True, True]) + + def testNot2dExpression(self): + self._compareExpected('not "t" == 10s', [True, True, True, False, True]) + + def testAnd2dExpression(self): + self._compareExpected('"Zeit" == 60s and 20000m == "Strecke"', [False, False, True, False, False]) + + def testOr2dExpression(self): + self._compareExpected('"Zeit" < 60s or "Strecke" == 5500m', [True, True, False, True, True]) + + def testPrecedence2dExpression(self): + self._compareExpected('0m > "l" or not ("t" == 20s or "t" == 40s) and (("l" == -20000m or "t" == 40s) or "l" == 5500m)', [True, False, False, False, True]) + + def testNestedTuple2dExpression(self): + self._compareExpected(('AND', ('Atomar', ('SCColumn', self.sc2d["t"]), '==',('PhysQuant', PhysicalQuantity('20s'))), ('Atomar', ('SCColumn', self.sc2d["l"]), '==', ('PhysQuant', PhysicalQuantity('-20000m')))), [True, False, False, False, False]) + + def testMultipleCompareOpPrecedence2dExpression(self): + self._compareExpected('not 0m <= "l" <= 10000m', [True, False, True, False, False]) + + def testColumnToColumn2dExpression(self): + self._compareExpected('"l" == "Strecke"', [True, True, True, True, True]) + self._compareExpected('"t" != "Zeit"', [False, False, False, False, False]) + + class FieldContainerRescaling(unittest.TestCase): def setUp(self): self.testData = scipy.array([[0,1,2],[3,4,5],[6,7,8]]) Copied: trunk/src/workers/ImageProcessing/ImageProcessing/FilterWorker.py (from rev 620, trunk/src/workers/ImageProcessing/ImageProcessing/__init__.py) =================================================================== --- trunk/src/workers/ImageProcessing/ImageProcessing/FilterWorker.py (rev 0) +++ trunk/src/workers/ImageProcessing/ImageProcessing/FilterWorker.py 2009-03-17 13:42:37 UTC (rev 621) @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009, Rectorate of the University of Freiburg +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the Freiburg Materials Research Center, +# University of Freiburg nor the names of its contributors may be used to +# endorse or promote products derived from this software without specific +# prior written permission. +# +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +u""" + +""" + +__id__ = "$Id$" +__author__ = "$Author$" +__version__ = "$Revision$" +# $Source$ + +from pyphant.core import Worker, Connectors,\ + Param, DataContainer + +class FilterWorker(Worker.Worker): + API = 2 + VERSION = 1 + REVISION = "$Revision$"[11:-1] + name = "SC Filter" + _sockets = [("table", Connectors.TYPE_ARRAY)] + _params = [("expression", "Filter Expression", '', None)] + + @Worker.plug(Connectors.TYPE_ARRAY) + def applyfilter(self, table, subscriber=0): + result = table.filter(self.paramExpression.value) + result.seal() + return result + Modified: trunk/src/workers/ImageProcessing/ImageProcessing/__init__.py =================================================================== --- trunk/src/workers/ImageProcessing/ImageProcessing/__init__.py 2009-03-17 13:41:49 UTC (rev 620) +++ trunk/src/workers/ImageProcessing/ImageProcessing/__init__.py 2009-03-17 13:42:37 UTC (rev 621) @@ -47,6 +47,7 @@ "DistanceMapper", "EdgeFillWorker", "EdgeTouchingFeatureRemover", + "FilterWorker", "ImageLoaderWorker", "InvertWorker", "Medianiser", This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |