diff --git a/configfile.py b/configfile.py new file mode 100644 index 00000000..c851edb1 --- /dev/null +++ b/configfile.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +""" +configfile.py - Human-readable text configuration file library +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Used for reading and writing dictionary objects to a python-like configuration +file format. Data structures may be nested and contain any data type as long +as it can be converted to/from a string using repr and eval. +""" + +import re, os, sys +from collections import OrderedDict +GLOBAL_PATH = None # so not thread safe. +import units + +class ParseError(Exception): + def __init__(self, message, lineNum, line, fileName=None): + self.lineNum = lineNum + self.line = line + #self.message = message + self.fileName = fileName + Exception.__init__(self, message) + + def __str__(self): + if self.fileName is None: + msg = "Error parsing string at line %d:\n" % self.lineNum + else: + msg = "Error parsing config file '%s' at line %d:\n" % (self.fileName, self.lineNum) + msg += "%s\n%s" % (self.line, self.message) + return msg + #raise Exception() + + +def writeConfigFile(data, fname): + s = genString(data) + fd = open(fname, 'w') + fd.write(s) + fd.close() + +def readConfigFile(fname): + #cwd = os.getcwd() + global GLOBAL_PATH + if GLOBAL_PATH is not None: + fname2 = os.path.join(GLOBAL_PATH, fname) + if os.path.exists(fname2): + fname = fname2 + + GLOBAL_PATH = os.path.dirname(os.path.abspath(fname)) + + try: + #os.chdir(newDir) ## bad. + fd = open(fname) + s = unicode(fd.read(), 'UTF-8') + fd.close() + s = s.replace("\r\n", "\n") + s = s.replace("\r", "\n") + data = parseString(s)[1] + except ParseError: + sys.exc_info()[1].fileName = fname + raise + except: + print "Error while reading config file %s:"% fname + raise + #finally: + #os.chdir(cwd) + return data + +def appendConfigFile(data, fname): + s = genString(data) + fd = open(fname, 'a') + fd.write(s) + fd.close() + + +def genString(data, indent=''): + s = '' + for k in data: + sk = str(k) + if len(sk) == 0: + print data + raise Exception('blank dict keys not allowed (see data above)') + if sk[0] == ' ' or ':' in sk: + print data + raise Exception('dict keys must not contain ":" or start with spaces [offending key is "%s"]' % sk) + if isinstance(data[k], dict): + s += indent + sk + ':\n' + s += genString(data[k], indent + ' ') + else: + s += indent + sk + ': ' + repr(data[k]) + '\n' + return s + +def parseString(lines, start=0): + + data = OrderedDict() + if isinstance(lines, basestring): + lines = lines.split('\n') + + indent = measureIndent(lines[start]) + ln = start - 1 + + try: + while True: + ln += 1 + #print ln + if ln >= len(lines): + break + + l = lines[ln] + + ## Skip blank lines or lines starting with # + if re.match(r'\s*#', l) or not re.search(r'\S', l): + continue + + ## Measure line indentation, make sure it is correct for this level + lineInd = measureIndent(l) + if lineInd < indent: + ln -= 1 + break + if lineInd > indent: + #print lineInd, indent + raise ParseError('Indentation is incorrect. Expected %d, got %d' % (indent, lineInd), ln+1, l) + + + if ':' not in l: + raise ParseError('Missing colon', ln+1, l) + + (k, p, v) = l.partition(':') + k = k.strip() + v = v.strip() + + ## set up local variables to use for eval + local = units.allUnits.copy() + local['OrderedDict'] = OrderedDict + local['readConfigFile'] = readConfigFile + if len(k) < 1: + raise ParseError('Missing name preceding colon', ln+1, l) + if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it. + try: + k1 = eval(k, local) + if type(k1) is tuple: + k = k1 + except: + pass + if re.search(r'\S', v) and v[0] != '#': ## eval the value + try: + val = eval(v, local) + except: + ex = sys.exc_info()[1] + raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l) + else: + if ln+1 >= len(lines) or measureIndent(lines[ln+1]) <= indent: + #print "blank dict" + val = {} + else: + #print "Going deeper..", ln+1 + (ln, val) = parseString(lines, start=ln+1) + data[k] = val + #print k, repr(val) + except ParseError: + raise + except: + ex = sys.exc_info()[1] + raise ParseError("%s: %s" % (ex.__class__.__name__, str(ex)), ln+1, l) + #print "Returning shallower..", ln+1 + return (ln, data) + +def measureIndent(s): + n = 0 + while n < len(s) and s[n] == ' ': + n += 1 + return n + + + +if __name__ == '__main__': + import tempfile + fn = tempfile.mktemp() + tf = open(fn, 'w') + cf = """ +key: 'value' +key2: + key21: 'value' + key22: [1,2,3] + key23: 234 #comment + """ + tf.write(cf) + tf.close() + print "=== Test:===" + print cf + print "============" + data = readConfigFile(fn) + print data + os.remove(fn) \ No newline at end of file diff --git a/flowchart/Flowchart.py b/flowchart/Flowchart.py index 3e854d54..e253741f 100644 --- a/flowchart/Flowchart.py +++ b/flowchart/Flowchart.py @@ -14,7 +14,7 @@ from Terminal import Terminal from numpy import ndarray import library from pyqtgraph.debug import printExc -import configfile +import pyqtgraph.configfile as configfile import pyqtgraph.dockarea as dockarea import pyqtgraph as pg import FlowchartGraphicsView diff --git a/flowchart/library/Filters.py b/flowchart/library/Filters.py index a88ea40e..6badff83 100644 --- a/flowchart/library/Filters.py +++ b/flowchart/library/Filters.py @@ -197,49 +197,4 @@ class HistogramDetrend(CtrlNode): return functions.histogramDetrend(data, window=ws, bins=bn) -class ExpDeconvolve(CtrlNode): - """Exponential deconvolution filter.""" - nodeName = 'ExpDeconvolve' - uiTemplate = [ - ('tau', 'spin', {'value': 10e-3, 'step': 1, 'minStep': 100e-6, 'dec': True, 'range': [0.0, None], 'suffix': 's', 'siPrefix': True}) - ] - def processData(self, data): - tau = self.ctrls['tau'].value() - return functions.expDeconvolve(data, tau) - #dt = 1 - #if isinstance(data, MetaArray): - #dt = data.xvals(0)[1] - data.xvals(0)[0] - #d = data[:-1] + (self.ctrls['tau'].value() / dt) * (data[1:] - data[:-1]) - #if isinstance(data, MetaArray): - #info = data.infoCopy() - #if 'values' in info[0]: - #info[0]['values'] = info[0]['values'][:-1] - #return MetaArray(d, info=info) - #else: - #return d - -class ExpReconvolve(CtrlNode): - """Exponential reconvolution filter. Only works with MetaArrays that were previously deconvolved.""" - nodeName = 'ExpReconvolve' - #uiTemplate = [ - #('tau', 'spin', {'value': 10e-3, 'step': 1, 'minStep': 100e-6, 'dec': True, 'range': [0.0, None], 'suffix': 's', 'siPrefix': True}) - #] - - def processData(self, data): - return functions.expReconvolve(data) - -class Tauiness(CtrlNode): - """Sliding-window exponential fit""" - nodeName = 'Tauiness' - uiTemplate = [ - ('window', 'intSpin', {'value': 100, 'min': 3, 'max': 1000000}), - ('skip', 'intSpin', {'value': 10, 'min': 0, 'max': 10000000}) - ] - - def processData(self, data): - return functions.tauiness(data, self.ctrls['window'].value(), self.ctrls['skip'].value()) - - - - \ No newline at end of file diff --git a/flowchart/library/__init__.py b/flowchart/library/__init__.py index 58b5b810..1efc4ea1 100644 --- a/flowchart/library/__init__.py +++ b/flowchart/library/__init__.py @@ -3,7 +3,7 @@ from collections import OrderedDict import os, types from pyqtgraph.debug import printExc from ..Node import Node -import reload +import pyqtgraph.reload as reload NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses diff --git a/units.py b/units.py new file mode 100644 index 00000000..6b7f3099 --- /dev/null +++ b/units.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +## Very simple unit support: +## - creates variable names like 'mV' and 'kHz' +## - the value assigned to the variable corresponds to the scale prefix +## (mV = 0.001) +## - the actual units are purely cosmetic for making code clearer: +## +## x = 20*pA is identical to x = 20*1e-12 + +## No unicode variable names (μ,Ω) allowed until python 3 + +SI_PREFIXES = 'yzafpnum kMGTPEZY' +UNITS = 'm,s,g,W,J,V,A,F,T,Hz,Ohm,S,N,C,px,b,B'.split(',') +allUnits = {} + +def addUnit(p, n): + g = globals() + v = 1000**n + for u in UNITS: + g[p+u] = v + allUnits[p+u] = v + +for p in SI_PREFIXES: + if p == ' ': + p = '' + n = 0 + elif p == 'u': + n = -2 + else: + n = SI_PREFIXES.index(p) - 8 + + addUnit(p, n) + +cm = 0.01 + + + + + + +def evalUnits(unitStr): + """ + Evaluate a unit string into ([numerators,...], [denominators,...]) + Examples: + N m/s^2 => ([N, m], [s, s]) + A*s / V => ([A, s], [V,]) + """ + pass + +def formatUnits(units): + """ + Format a unit specification ([numerators,...], [denominators,...]) + into a string (this is the inverse of evalUnits) + """ + pass + +def simplify(units): + """ + Cancel units that appear in both numerator and denominator, then attempt to replace + groups of units with single units where possible (ie, J/s => W) + """ + pass + + \ No newline at end of file