Changeset 4633


Ignore:
Timestamp:
09/08/08 07:11:00 (5 years ago)
Author:
werner
Message:

Highlights:

  • a lot more wave operations: mathematics, deglitching, digitizing
  • wavefirms can now be loaded from a file
  • made a first pass at cleaning up triggers (finally !)
  • some renaming, e.g., tmc.wave.analog_wave is now called just tmc.wave.analog
  • waveforms are now downloaded from the scope, not the channel
  • more examples in demo/. The bottom half of the SPI example has actually some real-life significance.

Note that some of the changes are still unfinished/untested and that there
may be regressions.

  • lib/wave.py (waves): new class for iteration and I/O of groups of waves
  • lib/wave.py (analog_wave.append): ignore duplicate entries
  • lib/wave.py (wave): added arithmetic operations between waves and numbers and between waves (+, -, *, /, , unary -, abs)
  • lib/wave.py (wave.load): load waveform data from a file
  • lib/wave.py (wave.dump), demo/deglitch.py: renamed to wave.dump to wave.save
  • lib/wave.py (digital_wave.edges): moved to trigger.edge.apply
  • lib/wave.py: renamed analog_wave to "analog" and digital_wave to "digital"
  • lib/wave.py: finished the digitizing function, greatly simplifying it
  • README: mention the libraries
  • lib/instrument.py (settings): moved most of the shape_settings functionality to its super class, generalized callbacks
  • scope.py: moved trigger force, state, and sweep to the horizontal system
  • scope.py (trigger): removed, and import tmc.trigger now
  • scope.py (rigol_ds1000c.download_wave): simplified contorted logic to determine channel number
  • lib/scope.py: made "wave" a scope method instead of a channel method, so that we can retrieve multiple channels in one operation
  • lib/trigger.py: moved the trigger class from scope.py and did some cleanup. The design still doesn't really make sense, but it'll have to do for now.
  • demo/wavemath.py: wave mathematics example
  • demo/spi.py: SPI decode example (to illustrate the use of triggers)
  • demo/digitize.py: digitizing example
Location:
developers/werner/ahrt/host/tmc
Files:
3 added
10 edited

Legend:

Unmodified
Added
Removed
  • developers/werner/ahrt/host/tmc/README

    r4228 r4633  
    2626=============== 
    2727 
     28The tmc package contains a set of functions to communicate with test 
     29and measurement instruments. (Currently always using SCPI, but devices 
     30speaking different protocols could also be supported.) 
     31 
     32These basic functions are described below. 
     33 
     34On top of basic communication, tmc provides a rich set of abstractions 
     35and libraries with helper functions written in Python. The goal is to 
     36allow routine tasks to be expressed in a concise and understandable 
     37way, and to facilitate the construction of complex automated experiments. 
     38 
     39These libraries are not documented yet, but there is a number of 
     40annotated examples in the demo/ directory. 
     41 
     42 
     43Low-level interface 
     44=================== 
     45 
    2846This suite supports instruments that speak SCPI or a dialect thereof 
    2947using one of the following transport protocols: 
     
    5068of the tmc.Instr class: 
    5169 
    52     import tmc 
    53  
    54     instr = tmc.Instr(transport, arg, ...) 
     70    import _tmc 
     71 
     72    instr = _tmc.Instr(transport, arg, ...) 
    5573 
    5674 
     
    118136Debug output can be enabled or disabled with 
    119137 
    120     tmc.debug(level) 
     138    _tmc.debug(level) 
    121139 
    122140Level 0 turns debug output off. 
  • developers/werner/ahrt/host/tmc/demo/deglitch.py

    r4631 r4633  
    55 
    66from tmc.shape import arbitrary 
    7 from tmc.wave import digital_wave 
     7from tmc.wave import digital 
    88from math import floor 
    99 
     
    2020w = shape.wave(0, 100, 1e-3) 
    2121 
    22 # Crudely convert the analog waveform into a digital one. 
     22# Convert the analog waveform into a digital one. 
    2323 
    24 dw = digital_wave() 
    25 for p in w: 
    26     dw.append(p[0], p[1]) 
     24dw = w.digitize(0.5) 
    2725 
    2826# Dump the original waveform to a file 
    2927 
    30 dw.dump("before") 
     28dw.save("before") 
    3129 
    3230# Remove all transients with a duration of less than 0.1 units. 
     
    3937# Dump the deglitched waveform to a file 
    4038 
    41 dw.dump("after") 
     39dw.save("after") 
    4240 
    4341# 
  • developers/werner/ahrt/host/tmc/demo/fft.py

    r4631 r4633  
    1313 
    1414# Describe a 100Hz square wave with amplitude = 5 
     15 
    1516shape = tmc.shape.square(high = 5, freq = 100) 
    1617 
    1718# Generate a waveform over t = [0, 1) with 10kSa/s 
     19 
    1820wave = shape.wave(0, 1, 1e-4) 
    1921 
     
    2426 
    2527# convert the waveform into a NumPy array 
     28 
    2629a = map(lambda p: p[1], wave) 
    2730 
    2831# get the duration of the waveform 
     32 
    2933width = wave.end()-wave.start() 
    3034 
    3135# get the number of samples 
     36 
    3237n = len(wave) 
    3338 
    3439# do a discrete Fourier transform using the Hann(ing) window 
     40 
    3541fa = abs(fft(array(a)*hanning(n)))/n 
    3642 
    3743# obtain the frequency components and put the positive half of the spectrum 
    3844# into a file with one (frequency, amplitude) tuple per line 
     45 
    3946savetxt("fft.out", transpose((fftfreq(n, d = width/n)[0:n/2], fa[0:n/2]))) 
  • developers/werner/ahrt/host/tmc/demo/screen.py

    r4631 r4633  
    1212 
    1313# Open the Rigol DS1000 
     14 
    1415scope = tmc.scope.rigol_ds1000c() 
    1516 
    1617# Open a pipe to a process running "convert" from ImageMagick 
     18 
    1719pipe = popen("convert - "+argv[1], "w") 
    1820 
    1921# Retrieve the screendump as PPM file and send it to the converter 
     22 
    2023pipe.write(scope.screendump()) 
    2124 
    2225# Flush the pipe and terminate 
     26 
    2327pipe.close() 
  • developers/werner/ahrt/host/tmc/lib/instrument.py

    r4631 r4633  
    2626 
    2727class settings(object): 
    28     pass 
     28    def __init__(self, init, set = None, get = None): 
     29        self.value = init 
     30        self.__set = set 
     31        self.__get = get 
     32 
     33    def get(self): 
     34        if self.__get is None: 
     35            return self.value 
     36        else: 
     37            value = self.__get() 
     38            self.value = value 
     39            return value 
     40 
     41    def set(self, value): 
     42        if self.__set is not None: 
     43            self.__set(value) 
     44        self.value = value 
     45 
    2946 
    3047class setting(settings): 
  • developers/werner/ahrt/host/tmc/lib/scope.py

    r4631 r4633  
    2929 
    3030import time 
    31 from tmc.instrument import setting, settable, instrument 
    32 from tmc.wave import analog_wave, digital_wave 
     31from tmc.instrument import settings, setting, settable, instrument 
     32from tmc.wave import analog, digital 
     33from tmc.trigger import trigger 
    3334 
    3435 
     
    8788        self.scope = scope 
    8889        self.number = number 
     90        self.name = "CH"+str(number) 
    8991 
    9092        self.on = setting(scope, ":CHAN"+str(number)+":DISP", 
     
    120122    # --- wave download ------------------------------------------------------- 
    121123 
     124    # obsolete ! 
    122125 
    123126    def wave(self, start = None, end = None, step = None): 
     
    129132 
    130133 
     134class sweep: 
     135    Auto, Normal, Single = range(3) 
     136 
     137 
     138class state: 
     139    Run, Stop, Triggered, Wait, Scan, Auto = range(6) 
     140 
     141 
     142class horizontal_trigger_setting(settings): 
     143 
     144    def __init__(self, scope, set = None, get = None): 
     145        self.scope = scope 
     146        self.trigger_set = set 
     147        self.trigger_get = get 
     148        settings.__init__(self, None, set = self.pass_set, get = self.pass_get) 
     149 
     150    def pass_set(self, value): 
     151        if self.scope.trigger is not None and self.trigger_set is not None: 
     152            self.trigger_set(self.scope.trigger, value) 
     153 
     154    def pass_get(self): 
     155        if self.scope.trigger is not None and self.trigger_get is not None: 
     156            return self.trigger_get(self.scope.trigger) 
     157 
     158         
    131159class horizontal(settable): 
     160 
     161    state_map = ( "RUN", "STOP", "T'D", "WAIT", "SCAN", "AUTO" ) 
    132162 
    133163    def __init__(self, scope): 
     
    139169          lambda x: float(x), 
    140170          lambda x: "%.9f" % x) 
     171        self.sweep = horizontal_trigger_setting(scope, 
     172          trigger.set_sweep, trigger.get_sweep) 
    141173        self.forget() 
    142174 
     
    144176        self.pos = None 
    145177        self.scale = None 
    146  
    147  
    148 # === trigger ================================================================= 
    149  
    150  
    151 class trigger(settable): 
    152  
    153     def __init__(self, scope): 
    154         self.scope = scope 
    155         #self.forget() 
    156  
    157     def forget(self): 
    158         self.mode.forget() 
    159         self.source.forget() 
    160         self.type.forget() 
    161         self.level.forget() 
    162         self.holdoff.forget() 
    163  
    164     def mode(self, value): 
    165         if value != None: 
    166             if  value != self.mode: 
    167                 self.scope.send(":TRIG"+str(self.number)+":SCAL "+str(value)) 
    168                 self.scale = value 
    169         else: 
    170             pass 
    171         return self.scale 
    172  
    173     def source(self, value): 
    174         if value != None: 
    175             if  value != self.scale: 
    176                 self.scope.send(":CHAN"+str(self.number)+":SCAL "+str(value)) 
    177                 self.scale = value 
    178         else: 
    179             self.scale =  self.scope.query(":CHAN"+str(self.number)+":SCAL?") 
    180         return self.scale 
    181  
    182     def sweep(self, value): 
    183         if value != None: 
    184             if  value != self.sweep: 
    185                 self.scope.send(":CHAN"+self.mode+":SWE "+str(value)) 
    186                 self.sweep = value 
    187         else: 
    188             self.sweep = self.scope.query(":CHAN"+self.mode+":SWE?") 
    189         return self.sweep 
    190  
    191     def level(self, value): 
    192         if value != None: 
    193             if  value != self.level: 
    194                 self.scope.send(":TRIG:"+self.mode+":LEV "+str(value)) 
    195                 self.scale = level  
    196         else: 
    197             self.level =  self.scope.query(":TRIG:"+self.mode+":LEV?") 
    198         return self.leve 
    199  
    200     def holdoff(self, value): 
    201         if value != None: 
    202             if  value != self.scale: 
    203                 self.scope.send(":CHAN"+str(self.number)+":SCAL "+str(value)) 
    204                 self.scale = value 
    205         else: 
    206             self.scale =  self.scope.query(":CHAN"+str(self.number)+":SCAL?") 
    207         return self.scale 
     178        self.sweep = None 
    208179 
    209180    def force(self): 
     
    211182 
    212183    def state(self): 
    213         return self.scope.query(":TRIG:STAT?") 
    214  
    215  
    216 # ==== 
     184        return self.state_map.index(self.scope.query(":TRIG:STAT?")) 
     185 
     186 
     187# === Scope class ============================================================= 
     188 
    217189 
    218190class scope(instrument): 
     
    235207 
    236208def rigol_channel_data(s, t0, td, v0, vd): 
    237     res = analog_wave() 
     209    res = analog() 
    238210    i = 212 
    239211    while i != 812: 
     
    250222    res = [] 
    251223    for b in range(0, 16): 
    252         res.append(digital_wave()) 
     224        res.append(digital()) 
    253225    i = 424 
    254226    while i != 1624: 
     
    332304          "vendor=0x0400", "product=0x05dc") 
    333305        self.ch = [] 
     306        self.d = map(lambda x: x, range(0, 16)) 
    334307        for n in range(1, self.channels+1): 
    335308            self.ch.append(channel(self, n)) 
     309        self.trigger = None 
    336310        self.hor = horizontal(self) 
    337         self.trigger = trigger(self) 
    338311 
    339312    def forget(self): 
     
    341314            ch.forget() 
    342315        self.hor.forget() 
    343         self.trigger.forget() 
    344316 
    345317    def send(self, s): 
     
    361333 
    362334    def download_wave(self, channel, start, end, step): 
    363         c_num = None 
    364         for n in range(0, len(self.ch)): 
    365             if self.ch[n] == channel: 
    366                 c_num = n+1 
    367335        return rigol_wave(self, start, end, step, 
    368           ":WAV:DATA? CHAN"+str(c_num), 
     336          ":WAV:DATA? CHAN"+str(channel.number), 
    369337          lambda a, b: a.extend(b), 
    370338          rigol_channel_data, channel.pos, channel.scale) 
     
    383351        self.send(":HARDCOPY") 
    384352        return rigol_to_ppm(self.query(":LCD:DATA?")) 
     353 
     354    def wave(self, channels, start = None, end = None, step = None): 
     355        if not isinstance(channels, array): 
     356            return self.download([channels], start, end, step)[0] 
     357        la = None 
     358        res = [] 
     359        for ch in channels: 
     360            if isinstance(ch, channel): 
     361                res.append(download_wave, start, end, step) 
     362            else: 
     363                if la is None: 
     364                    la = self.download_la(start, end, step) 
     365                res.append(la[ch]) 
     366        return res 
  • developers/werner/ahrt/host/tmc/lib/shape.py

    r4631 r4633  
    2525from math import pi, sin, floor 
    2626from tmc.instrument import settable, settings 
    27 from tmc.wave import analog_wave 
    28  
    29  
    30 def set_low(shape, y): 
    31     if shape.high < y: 
    32         shape.high = y 
    33  
    34  
    35 def set_high(shape, y): 
    36     if shape.low > y: 
    37         shape.low = y 
    38  
    39  
    40 def set_offset(shape, y): 
    41     peak = shape.peak 
    42     shape.low = y-peak/2.0 
    43     shape.high = y+peak/2.0 
    44  
    45  
    46 def set_peak(shape, y): 
    47     if y < 0: 
    48         raise hell 
    49     offset = shape.offset 
    50     shape.low = offset-y/2.0 
    51     shape.high = offset+y/2.0 
    52  
    53  
    54 def set_freq(shape, f): 
    55     if f < 0: 
    56         raise hell 
    57  
    58  
    59 def set_period(shape, t): 
    60     if t < 0: 
    61         raise hell 
    62     shape.freq = 1.0/t 
    63  
    64  
    65 def set_duty(shape, n): 
    66     if n < 0 or n > 1: 
    67         raise hell 
    68  
    69  
    70 def set_width(shape, n): 
    71     if n <= 0 or 1.0/n > shape.freq: 
    72         raise hell 
    73     shape.duty = shape.freq*n 
     27from tmc.wave import analog 
    7428 
    7529 
    7630class shape_setting(settings): 
    7731 
    78     def __init__(self, shape, init, set_fn = None, get_fn = None): 
     32    def __init__(self, shape, init, set = None, get = None): 
     33        settings.__init__(self, init, set, get) 
    7934        self.shape = shape 
    8035        self.value = init 
    81         self.set_fn = set_fn 
    82         self.get_fn = get_fn 
    83  
    84     def get(self): 
    85         if self.get_fn is None: 
    86             return self.value 
    87         else: 
    88             return self.get_fn(self.shape) 
    8936 
    9037    def set(self, value): 
    9138        prev = self.value 
    92         if self.set_fn is not None: 
    93             self.set_fn(self.shape, value) 
    94         self.value = value 
     39        settings.set(self, value) 
    9540        if self.shape.notify is not None: 
    9641            self.shape.notify(shape, self, prev, self.value) 
     
    10247        self.notify = None 
    10348 
    104         self.low = shape_setting(self, 0, set_low) 
    105         self.high = shape_setting(self, 0, set_high) 
    106         self.offset = shape_setting(self, None, set_offset, 
    107           lambda shape: (shape.low+shape.high)/2.0) 
    108         self.peak = shape_setting(self, None, set_peak, 
    109           lambda shape: shape.high-shape.low) 
    110         self.freq = shape_setting(self, 0, set_freq) 
    111         self.period = shape_setting(self, None, set_period) 
     49        self.low = shape_setting(self, 0, self.__set_low) 
     50        self.high = shape_setting(self, 0, self.__set_high) 
     51        self.offset = shape_setting(self, None, 
     52          self.__set_offset, self.__get_offset) 
     53        self.peak = shape_setting(self, None, self.__set_peak, self.__get_peak) 
     54        self.freq = shape_setting(self, 0, self.__set_freq) 
     55        self.period = shape_setting(self, None, self.__set_period) 
    11256 
    11357        self.set(**list) 
    11458 
     59    def __set_low(self, y): 
     60        if self.high < y: 
     61            self.high = y 
     62 
     63    def __set_high(self, y): 
     64        if self.low > y: 
     65            self.low = y 
     66 
     67    def __set_offset(self, y): 
     68        peak = self.peak 
     69        self.low = y-peak/2.0 
     70        self.high = y+peak/2.0 
     71 
     72    def __get_offset(self): 
     73          return (self.low+self.high)/2.0 
     74 
     75    def __set_peak(self, y): 
     76        if y < 0: 
     77            raise hell 
     78        offset = self.offset 
     79        self.low = offset-y/2.0 
     80        self.high = offset+y/2.0 
     81 
     82    def __get_peak(self): 
     83        return self.high-self.low 
     84 
     85    def __set_freq(self, f): 
     86        if f < 0: 
     87            raise hell 
     88 
     89    def __set_period(self, t): 
     90        if t <= 0: 
     91            raise hell 
     92        self.freq = 1.0/t 
     93 
    11594    def wave(self, start, end, step): 
    116         wave = analog_wave() 
     95        wave = analog() 
    11796        t = start 
    11897        while t <= end: 
     
    128107 
    129108 
    130 class square(shape): 
     109class __shape_with_duty(shape): 
     110 
     111    def __init__(self, init, **list): 
     112        self.duty = shape_setting(self, init, self.__set_duty) 
     113        shape.__init__(self, **list) 
     114 
     115    def __set_duty(self, n): 
     116        if n < 0 or n > 1: 
     117            raise hell 
     118 
     119    def __set_width(self, n): 
     120        if n <= 0 or 1.0/n > self.freq: 
     121            raise hell 
     122        self.duty = self.freq*n 
     123 
     124 
     125class square(__shape_with_duty): 
    131126 
    132127    def __init__(self, **list): 
    133         self.duty = shape_setting(self, 0.5, set_duty) 
    134         shape.__init__(self, **list) 
     128        __shape_with_duty(self, 0.5, **list) 
    135129 
    136130    def get(self, t): 
     
    139133 
    140134 
    141 class ramp(shape): 
     135class ramp(__shape_with_duty): 
    142136 
    143137    def __init__(self, **list): 
    144         self.duty = shape_setting(self, 1, set_duty) 
    145         shape.__init__(self, **list) 
     138        __shape_with_duty(self, 1, **list) 
    146139 
    147140    def get(self, t): 
     
    165158    def __init__(self, **list): 
    166159        self.fn = shape_setting(self, lambda t: 0, None) 
    167 #       self.arg = shape_setting(self, None, None) 
    168160        shape.__init__(self, **list) 
    169161 
  • developers/werner/ahrt/host/tmc/lib/trigger.py

    r4631 r4633  
    1313 
    1414# 
    15 # @@@ old-style. needs rewriting. 
     15# @@@ The callback logic isn't quite right - we could have multiple users of 
     16# the same trigger settings, so just attaching it to a scope channel is wrong. 
    1617# 
    1718 
    18 class trigger(object): 
    1919 
    20     def __init__(self, scope): 
    21         self.scope = scope 
     20from tmc.instrument import settable, setting 
     21 
     22 
     23class coupling: 
     24    DC, AC, HF, LF = range(4) 
     25 
     26 
     27class trigger(settable): 
     28    coupling_map = [ "DC", "AC", "HF", "LF" ] 
     29    sweep_map = [ "AUTO", "NORMAL", "SINGLE" ] 
     30 
     31    def __init__(self): 
     32        self.channel = None 
     33        self.scope = None 
     34 
     35    def source(self, channel, coupling = None): 
     36        if self.scope is not None: 
     37            self.scope.trigger = None 
     38        self.channel = channel 
     39        if channel is None: 
     40            self.scope = None 
     41        else: 
     42            self.scope = channel.scope 
     43            self.scope.trigger = self 
     44            self.scope.send(":TRIG:MODE "+self.mode) 
     45            self.scope.send(":TRIG:"+self.mode+":SOUR "+channel.name) 
     46            if coupling is not None: 
     47                self.scope.send( 
     48                  ":TRIG:"+self.mode+":COUP "+self.coupling_map[coupling]) 
     49            self.scope.send(":TRIG:"+self.mode+":SWE "+scope.sweep) 
     50            self.update() 
     51 
     52    def send(self, *args): 
     53        if self.scope is not None: 
     54            self.scope.send(*args) 
     55 
     56    def query(self, *args): 
     57        if self.scope is None: 
     58            raise hell 
     59        return self.scope.query(*args) 
     60         
     61    def set_sweep(self, value): 
     62        self.scope.send(":TRIG:"+self.mode+":SWE "+self.sweep_map[value]) 
     63 
     64    def get_sweep(self): 
     65        return self.sweep_map.index(self.scope.query(":TRIG:"+self.mode+":SWE")) 
     66 
     67class slope: 
     68    Rising, Falling, Both = range(3) 
     69 
     70 
     71class edge(trigger): 
     72    mode = "EDGE" 
     73    slope_map = [ "POSITIVE", "NEGATIVE", "BOTH?" ] # @@@ 
     74 
     75    def __init__(self, **list): 
     76        trigger.__init__(self) 
     77        self.level = setting(self, ":TRIG:"+self.mode+":LEV", 
     78          lambda x: float(x), 
     79          lambda x: "%.9f" % x) 
     80        self.slope = setting(self, ":TRIG:"+self.mode+":SLOP", 
     81          lambda x: self.slope_map.index(x), 
     82          lambda x: self.slope_map[x]) 
    2283        self.forget() 
     84        self.set(**list) 
    2385 
    2486    def forget(self): 
    25         mode = None 
    26         source = None 
    27         type = None 
    28         level = None 
    29         holdoff = None 
     87        self.level = None 
     88        self.slope = None 
    3089 
    31     def mode(self, value): 
    32         if value != None: 
    33             if  value != self.mode: 
    34                 self.scope.send(":TRIG"+str(self.number)+":SCAL "+str(value)) 
    35                 self.scale = value 
    36         else: 
    37             self.scale =  self.scope.query(":CHAN"+str(self.number)+":SCAL?") 
    38         return self.scale 
     90    def update(self): 
     91        self.level = self.level 
     92        self.slope = self.slope 
    3993 
    40     def source(self, value): 
    41         if value != None: 
    42             if  value != self.scale: 
    43                 self.scope.send(":CHAN"+str(self.number)+":SCAL "+str(value)) 
    44                 self.scale = value 
    45         else: 
    46             self.scale =  self.scope.query(":CHAN"+str(self.number)+":SCAL?") 
    47         return self.scale 
    48  
    49     def sweep(self, value): 
    50         if value != None: 
    51             if  value != self.sweep: 
    52                 self.scope.send(":CHAN"+self.mode+":SWE "+str(value)) 
    53                 self.sweep = value 
    54         else: 
    55             self.sweep = self.scope.query(":CHAN"+self.mode+":SWE?") 
    56         return self.sweep 
    57  
    58     def level(self, value): 
    59         if value != None: 
    60             if  value != self.level: 
    61                 self.scope.send(":TRIG:"+self.mode+":LEV "+str(value)) 
    62                 self.scale = level  
    63         else: 
    64             self.level =  self.scope.query(":TRIG:"+self.mode+":LEV?") 
    65         return self.leve 
    66  
    67     def holdoff(self, value): 
    68         if value != None: 
    69             if  value != self.scale: 
    70                 self.scope.send(":CHAN"+str(self.number)+":SCAL "+str(value)) 
    71                 self.scale = value 
    72         else: 
    73             self.scale =  self.scope.query(":CHAN"+str(self.number)+":SCAL?") 
    74         return self.scale 
     94    def apply(self, wave): 
     95        # @@@ only digital waves for now 
     96        # @@@ need to include holdoff time as well 
     97        if self.level <= 0 or self.level >= 1: 
     98            return [] 
     99        if self.slope == slope.Both: 
     100            return wave.data[:] 
     101        res = [] 
     102        i = int(wave.initial ^ self.slope == slope.Falling)+1 
     103        while i < len(wave.data): 
     104            res.append(wave.data[i]) 
     105            i += 2 
     106        return res 
  • developers/werner/ahrt/host/tmc/lib/wave.py

    r4631 r4633  
    3636#     or  (s0, t*)  x  t*  ->  v* 
    3737 
     38# de-interpolation (analog): remove points that can be interpolated 
     39 
     40 
     41# 
     42# Known restrictions: 
     43# - waves.load has no way of knowing what kind of wave it is loading, so it 
     44#   just makes everything an analog wave. Should perhaps add a comment on top 
     45#   of the file with same hints. Or let provide a means for the user to tell 
     46#   waves.load what the waves are like. An even more radical approach may be 
     47#   to automatically convert waves consisting only of 0 and 1 and with only 
     48#   vertical transitions to digital. CPU time is cheap :-) 
     49# 
     50 
     51import re 
    3852 
    3953 
     
    4357class wave(object): 
    4458 
    45     def dump(self, name, append = False): 
     59    def save(self, name, append = False): 
    4660        f = open(name, ("w", "a")[append]) 
    4761        if append: 
     
    4963        for xy in self: 
    5064            print >>f, xy[0], xy[1] 
     65        f.close() 
     66 
     67    def load(self, name, step = None): 
     68        self.__init__() 
     69        empty = re.compile("^\s*(#.*)$") 
     70        if step is None: 
     71            numbers = re.compile("^\s*(\S+)\s*(\S+)\s*") 
     72        else: 
     73            numbers = re.compile("^\s*(\S+)?\s*(\S+)\s*") 
     74            t = 0 
     75        f = open(name, "r") 
     76        while True: 
     77            line = f.readline() 
     78            if line == "": 
     79                break 
     80            if empty.match(line): 
     81                continue 
     82            m = numbers.match(line) 
     83            if m is None: 
     84                raise hell 
     85            if step is None: 
     86                self.append(float(m.group(1)), float(m.group(2))) 
     87            else: 
     88                self.append(t, float(m.group(2))) 
     89                t += step 
    5190        f.close() 
    5291 
     
    6099            return self.get_one(t) 
    61100 
     101    def unary(self, op): 
     102        res = analog() 
     103        for p in self: 
     104            res.append(float(p[0]), float(op(p[1]))) 
     105        return res 
     106 
     107    def binary(self, other, op): 
     108        res = analog() 
     109        if isinstance(self, wave): 
     110            if isinstance(other, wave): 
     111                for v in waves(self, other).iterate(): 
     112                    res.append(v[0], op(float(v[1]), float(v[2]))) 
     113            else: 
     114                for p in self: 
     115                    res.append(p[0], op(float(p[1]), float(other))) 
     116        else: 
     117            for p in other: 
     118                res.append(p[0], op(float(self), float(p[1]))) 
     119        return res 
     120 
     121    def __add__(self, other): 
     122        return self.binary(other, float.__add__) 
     123 
     124    def __sub__(self, other): 
     125        return self.binary(other, float.__sub__) 
     126 
     127    def __mul__(self, other): 
     128        return self.binary(other, float.__mul__) 
     129 
     130    def __pow__(self, other): 
     131        return self.binary(other, float.__pow__) 
     132 
     133    def __div__(self, other): 
     134        return self.binary(other, float.__div__) 
     135 
     136    def __truediv__(self, other): 
     137        return self.binary(other, float.__truediv__) 
     138 
     139    def __radd__(self, other): 
     140        return self.binary(other, float.__radd__) 
     141 
     142    def __rsub__(self, other): 
     143        return self.binary(other, float.__rsub__) 
     144 
     145    def __rmul__(self, other): 
     146        return self.binary(other, float.__rmul__) 
     147 
     148    def __rpow__(self, other): 
     149        return self.binary(other, float.__rpow__) 
     150 
     151    def __rdiv__(self, other): 
     152        return self.binary(other, float.__rdiv__) 
     153 
     154    def __rtruediv__(self, other): 
     155        return self.binary(other, float.__rtruediv__) 
     156 
     157    def __neg__(self): 
     158        return self.unary(float.__neg__) 
     159 
     160    def __abs__(self): 
     161        return self.unary(float.__abs__) 
     162 
     163 
     164# === Wave groups ============================================================= 
     165 
     166 
     167def next_or_none(iter): 
     168    try: 
     169        return iter.next() 
     170    except StopIteration: 
     171        return None 
     172 
     173 
     174# The iteration is a bit tricky, because waves may have different sample 
     175# intervals, so we need to interpolate, and they may have multiple samples with 
     176# the same time (in the case of digital waves), so we need to make sure we stop 
     177# at a given time until all samples have been extraced. 
     178 
     179class waves_iter: 
     180 
     181    def __init__(self, waves): 
     182        self.iter = [] 
     183        self.cur = [] 
     184        self.nxt = [] 
     185        self.t = None 
     186        for w in waves: 
     187            i = w.__iter__() 
     188            v = next_or_none(i) 
     189            self.iter.append(i) 
     190            self.nxt.append(v) 
     191            self.cur.append(None) 
     192            if self.t is None: 
     193                if v is not None and (self.t is None or v[0] < self.t): 
     194                    self.t = v[0] 
     195 
     196    def __iter__(self): 
     197        return self 
     198 
     199    def next(self): 
     200        if self.t is None: 
     201            raise StopIteration 
     202        next_t = None 
     203        res = [ self.t ] 
     204        for i in range(0, len(self.cur)): 
     205            if self.nxt[i] is not None and self.nxt[i][0] == self.t: 
     206                self.cur[i] = self.nxt[i] 
     207                self.nxt[i] = next_or_none(self.iter[i]) 
     208            if self.nxt[i] is not None and \ 
     209              (next_t is None or self.nxt[i][0] < next_t): 
     210                next_t = self.nxt[i][0] 
     211            if self.cur[i] is None: 
     212                if self.nxt[i] is None: 
     213                    v = 0 
     214                else: 
     215                    v = self.nxt[i][1] 
     216            else: 
     217                if self.nxt[i] is None or self.cur[i][0] == self.t: 
     218                    v = self.cur[i][1] 
     219                else: 
     220                    v = self.cur[i][1]+(self.nxt[i][1]-self.cur[i][1])* \ 
     221                      (self.t-self.cur[i][0])/ \ 
     222                      float(self.nxt[i][0]-self.cur[i][0]) 
     223            res.append(v) 
     224        self.t = next_t 
     225        return res 
     226 
     227 
     228class waves(list): 
     229 
     230    def __init__(self, *args): 
     231        list.__init__(self) 
     232        self.extend(args) 
     233 
     234    def iterate(self): 
     235        return waves_iter(self) 
     236 
     237    def save(self, name, append = False): 
     238        f = open(name, ("w", "a")[append]) 
     239        if append: 
     240            print >>f 
     241        for v in self.iterate(): 
     242            for i in range(0, len(v)-1): 
     243                print >>f, v[i], 
     244            print >>f, v[-1] 
     245        f.close() 
     246 
     247    def load(self, name, step = None): 
     248        list.__init__(self) 
     249        empty = re.compile("^\s*(#.*)$") 
     250        space = re.compile("\s+") 
     251        first = True 
     252        skip = step is None 
     253        t = 0 
     254        f = open(name, "r") 
     255        while True: 
     256            line = f.readline() 
     257            if line == "": 
     258                break 
     259            if empty.match(line): 
     260                continue 
     261            m = space.split(line.lstrip().rstrip()) 
     262            if first: 
     263                for i in range(0, len(m)-skip): 
     264                    self.append(analog()) 
     265                first = False 
     266            else: 
     267                if len(m)-skip != len(self): 
     268                    raise hell 
     269            if step is None: 
     270                for i in range(0, len(m)-1): 
     271                    self[i].append(float(m[0]), float(m[i+1])) 
     272            else: 
     273                for i in range(0, len(m)): 
     274                    self[i].append(t, float(m[i])) 
     275                t += step 
     276        f.close() 
     277 
    62278 
    63279# === Analog waves ============================================================ 
    64280 
    65281 
    66 class analog_wave_iter: 
     282class analog_iter: 
    67283 
    68284    def __init__(self, wave): 
     
    100316 
    101317 
    102 class analog_wave(wave): 
     318class analog(wave): 
    103319 
    104320    def __init__(self): 
     
    116332 
    117333    def append(self, t, y): 
    118         if len(self.data) and t <= self.data[-1][0]: 
    119             raise hell 
     334        if len(self.data): 
     335            if t < self.data[-1][0]: 
     336                raise hell 
     337            if t == self.data[-1][0] and y == self.data[-1][1]: 
     338                return 
    120339        self.data.append((t, y)) 
    121340 
     
    132351 
    133352    def __iter__(self): 
    134         return analog_wave_iter(self) 
     353        return analog_iter(self) 
    135354 
    136355    def __len__(self): 
    137356        return len(self.data) 
    138357 
     358    def digitize(self, low, high = None): 
     359        if high is None: 
     360            high = low 
     361        res = digital() 
     362        state = None 
     363        for p in self.data: 
     364            if p[1] >= high: 
     365                if state is None or not state: 
     366                    res.append(p[0], 1) 
     367                    state = True 
     368            elif p[1] <= low: 
     369                if state is None or state: 
     370                    res.append(p[0], 0) 
     371                    state = False 
     372        return res 
     373 
    139374 
    140375# === Digital waves =========================================================== 
    141376 
    142377 
    143 class digital_wave_iter: 
     378class digital_iter: 
    144379 
    145380    def __init__(self, wave): 
     
    163398 
    164399 
    165 class digital_wave(wave): 
     400class digital(wave): 
    166401 
    167402    def __init__(self): 
     
    210445 
    211446    def __iter__(self): 
    212         return digital_wave_iter(self) 
    213  
    214     # @@@ experimental ! maybe use triggers instead ? 
    215  
    216     def edges(self, rising, falling = None): 
    217         if falling is None: 
    218             falling = not rising 
    219         if (not falling) and (not rising): 
    220             return [] 
    221         res = [] 
    222         if falling and rising: 
    223             i = 1 
    224             while i != len(self.data): 
    225                 res.append(self.data[i]) 
    226                 i += 1 
    227             return res 
    228         else: 
    229             i = int(self.initial ^ (not rising))+1 
    230             while i < len(self.data): 
    231                 res.append(self.data[i]) 
    232                 i += 2 
    233             return res 
    234  
    235     # @@@ experimental ! 
     447        return digital_iter(self) 
    236448 
    237449    def debounce(self, t): 
     
    246458    def __len__(self): 
    247459        return len(self.data)*2 
    248  
    249  
    250 # === @@@ Work in progress ==================================================== 
    251  
    252  
    253 def digitize(wave, vl, vh): 
    254     delta = [] 
    255     state = 0  # high: 2, rise: 1, unknown: 0, fall: -1, low: -2 
    256     s0 = None 
    257     t0 = None 
    258     for p in wave: 
    259         if p[1] >= vh: 
    260             pass 
    261         elif p1[x] <= vl: 
    262             pass 
    263         else: 
    264             pass 
    265         if state == 2:  # high 
    266             pass 
    267         elif state == -2:  # low 
    268             pass 
    269         elif state == 1:  # rise 
    270             pass 
    271         elif state == -1:  # fall 
    272             pass 
    273         else:  # unknown 
    274             pass 
    275     if s0 == None: 
    276         return None 
    277     return (s0, delta) 
  • developers/werner/ahrt/host/tmc/setup.py

    r4631 r4633  
    55    description = "Test and Measurement Control", 
    66    py_modules = [ "tmc.instrument", 
    7         "tmc.wave", "tmc.shape", 
     7        "tmc.wave", "tmc.trigger", "tmc.shape", 
    88        "tmc.meter", "tmc.scope", "tmc.power", "tmc.function" ], 
    99    package_dir = { "tmc": "lib" }, 
Note: See TracChangeset for help on using the changeset viewer.