Example #1
0
class HDFwrite(Block):
    ''' This block writes the incoming signals to a file in HDF_ format.
     
    The HDF format is organized as follows: ::
    
         /            (root)
         /procgraph             (group with name procgraph)
         /procgraph/signal1     (table)
         /procgraph/signal2     (table)
         ...
         
    Each table has the following fields:
    
         time         (float64 timestamp)
         value        (the datatype of the signal)
         
    If a signal changes datatype, then an exception is raised.
    
    '''

    Block.alias('hdfwrite')
    Block.input_is_variable('Signals to be written', min=1)
    Block.config('file', 'HDF file to write')
    Block.config('compress', 'Whether to compress the hdf table.', 1)
    Block.config('complib', 'Compression library (zlib, bzip2, blosc, lzo).',
                 default='zlib')
    Block.config('complevel', 'Compression level (0-9)', 9)

    def init(self):
        self.writer = PGHDFLogWriter(self.config.file,
                                     compress=self.config.compress,
                                     complevel=self.config.complevel,
                                     complib=self.config.complib)
        
    def update(self):
        signals = self.get_input_signals_names()
        for signal in signals:
            if self.input_update_available(signal):
                self.log_signal(signal)

    def log_signal(self, signal):
        timestamp = self.get_input_timestamp(signal)
        value = self.get_input(signal)
        # only do something if we have something 
        if value is None:
            return
        assert timestamp is not None

        if not isinstance(value, numpy.ndarray):
            # TODO: try converting
            try:
                value = numpy.array(value)
            except:
                msg = 'I can only log numpy arrays, not %r' % value.__class__
                raise BadInput(msg, self, signal)

        self.writer.log_signal(timestamp, signal, value)
        
    def finish(self):
        self.writer.finish()
Example #2
0
class Blend(Block):
    '''
        Blend two or more images.
        
        RGB images are interpreted as having full alpha (opaque). 
        All images must have the same width and height.
        
    '''
    Block.alias('blend')

    Block.input_is_variable('images to blend', min=2)

    Block.output('rgb', 'The output is a RGB image (no alpha)')

    def update(self):
        # TODO: check images
        result = None
        for signal in self.get_input_signals_names():
            image = self.get_input(signal)
            if result is None:
                result = image
                continue

            result = blend(result, image)

        self.output.rgb = to_rgb(result)
Example #3
0
class FPSDataLimit(Block):
    ''' This block limits the output update to a certain framerate. '''
    Block.alias('fps_data_limit')

    Block.config('fps', 'Maximum framerate.')

    Block.input_is_variable('Signals to decimate.', min=1)
    Block.output_is_variable('Decimated signals.')

    def init(self):
        self.state.last_timestamp = None

    def update(self):
        should_update = False

        last = self.state.last_timestamp
        current = max(self.get_input_signals_timestamps())

        if last is None:
            should_update = True
            self.state.last_timestamp = current
        else:
            fps = self.config.fps
            delta = 1.0 / fps
            difference = current - last
            if difference > delta:
                should_update = True
                self.state.last_timestamp = current

        if not should_update:
            return
        # Just copy the input to the output
        for i in range(self.num_input_signals()):
            self.set_output(i, self.get_input(i), self.get_input_timestamp(i))
Example #4
0
class ASync(Block):
    ''' 
      The first signal is the "master".
      Waits that all signals are perceived once.
      Then it creates one event every time the master arrives.
    '''
    Block.alias('async')

    Block.input_is_variable(
        'Signals to (a)synchronize. The first is the master.', min=2)
    Block.output_is_variable('Synchronized signals.')

    def init(self):
        self.state.last_sent_timestamp = None

    def update(self):
        self.state.last_timestamp = None

        # No signal until everybody is ready
        if not self.all_input_signals_ready():
            return

        # No signal unless the master is ready
        master_timestamp = self.get_input_timestamp(0)
        if self.state.last_timestamp == master_timestamp:
            return

        self.state.last_timestamp = master_timestamp

        # Write all signals using master's timestamp
        for s in self.get_input_signals_names():
            self.set_output(s, self.get_input(s), master_timestamp)
Example #5
0
class Wait(Block):
    ''' 
        This block waits a given number of updates before transmitting the 
        output signal.
    '''
    Block.alias('wait')

    Block.config('n', 'Number of updates to wait at the beginning.')

    Block.input_is_variable('Arbitrary signals.')
    Block.output_is_variable('Copy of the signals, minus the first '
                             '*n* updates.')

    def init(self):
        self.state.count = 0

    def update(self):
        count = self.state.count

        # make something happen after we have waited enough
        if count >= self.config.n:
            # Just copy the input to the output
            for i in range(self.num_input_signals()):
                self.set_output(i, self.get_input(i),
                                self.get_input_timestamp(i))

        self.state.count = count + 1
Example #6
0
class Identity(Block):
    ''' 
        This block outputs the inputs, unchanged. 
    '''

    Block.alias('identity')
    Block.input_is_variable('Input signals.', min=1)
    Block.output_is_variable('Output signals, equal to input.')

    def update(self):
        # Just copy the input to the output
        for i in range(self.num_input_signals()):
            self.set_output(i, self.get_input(i), self.get_input_timestamp(i))
Example #7
0
class BagWrite(Block):
    ''' This block writes the incoming signals to a ROS bag file.
     
    By default, the signals are organized as follows: ::
    
         /            (root)
         /procgraph             (group with name procgraph)
         /procgraph/signal1     (table)
         /procgraph/signal2     (table)
         ...
   

    Note that the input should be ROS messages; no conversion is done.
    
    '''

    Block.alias('bagwrite')
    Block.input_is_variable('Signals to be written', min=1)
    Block.config('file', 'Bag file to write')

    def init(self):
        from ros import rosbag  # @UnresolvedImport

        self.info('Writing to bag %r.' % self.config.file)
        make_sure_dir_exists(self.config.file)

        if os.path.exists(self.config.file):
            os.unlink(self.config.file)
        self.tmp_file = self.config.file + '.active'
        self.bag = rosbag.Bag(self.tmp_file, 'w', compression='bz2')
        self.signal2last_timestamp = {}

    def finish(self):
        self.bag.close()
        os.rename(self.tmp_file, self.config.file)

    def update(self):
        import rospy
        signals = self.get_input_signals_names()

        for signal in signals:
            msg = self.get_input(signal)
            timestamp = self.get_input_timestamp(signal)
            if ((signal in self.signal2last_timestamp)
                    and (timestamp == self.signal2last_timestamp[signal])):
                continue
            self.signal2last_timestamp[signal] = timestamp

            ros_timestamp = rospy.Time.from_sec(timestamp)
            self.bag.write('/%s' % signal, msg, ros_timestamp)
Example #8
0
class Print(Block):
    ''' 
        Print a representation of the input values along with 
        their timestamp. 
    '''

    Block.alias('print')

    Block.input_is_variable('Signals to print.', min=1)

    def update(self):
        for i in range(self.num_input_signals()):
            print('P %s %s %s' %
                  (self.canonicalize_input(i), self.get_input_timestamp(i),
                   self.get_input(i)))
Example #9
0
class MakeTuple(Block):
    ''' Creates a tuple out of the input signals values. 
    
        Often used for plotting two signals as (x,y); see :ref:`block:plot`.
    '''

    Block.alias('make_tuple')

    Block.input_is_variable('Signals to unite in a tuple.')

    Block.output('tuple', 'Tuple containing signals.')

    def update(self):
        values = self.get_input_signals_values()
        self.output.tuple = tuple(values)
Example #10
0
class Compose(Block):
    '''
    Compose several images in the same canvas.
    You should probably use :ref:`block:grid` in many situations.
    
    Example configuration: ::
    
        compose.positions = {y: [0,0], ys: [320,20]}
        
    '''

    Block.alias('compose')

    Block.config('width', 'Dimension in pixels.')
    Block.config('height', 'Dimension in pixels.')
    Block.config(
        'positions',
        'A structure giving the position of each signal in the canvas.')

    Block.input_is_variable('Images to compose.')

    Block.output('canvas', 'RGB image')

    def update(self):
        width = self.get_config('width')
        height = self.get_config('height')

        canvas = np.zeros((height, width, 3), dtype='uint8')

        positions = self.get_config('positions')

        if not isinstance(positions, dict):
            raise Exception('I expected a dict, not "%s"' % positions)

        for signal, position in positions.items():
            if not self.is_valid_input_name(signal):
                raise Exception('Unknown input "%s" in %s.' % (signal, self))
            rgb = self.get_input(signal)
            # TODO check
            if rgb is not None:
                assert_rgb_image(rgb, 'input %s to compose block' % signal)

                place_at(canvas, rgb, position[0], position[1])
                #print "Writing image %s" % signal
            else:
                print "Ignoring image %s because not ready.\n" % signal

        self.set_output(0, canvas)
Example #11
0
class AllReady(Block):
    ''' 
        This block outputs the inputs, unchanged. 
    '''

    Block.alias('all_ready')

    Block.input_is_variable('Input signals.', min=1)
    Block.output_is_variable('Output signals, equal to input.')

    def update(self):
        if not self.all_input_signals_ready():
            return

        for i in range(self.num_input_signals()):
            if self.input_update_available(i):
                t, value = self.get_input_ts_and_value(i)
                self.set_output(i, value, t)
Example #12
0
class Info(Block):
    ''' 
        Prints more compact information about the inputs 
        than :ref:`block:print`.
    
        For numpy arrays it prints their shape and dtype 
        instead of their values. 
        
    '''
    Block.alias('info')
    Block.input_is_variable('Signals to describe.', min=1)

    def init(self):
        self.first = {}
        self.counter = {}

    def update(self):
        # Just copy the input to the output 
        for i in range(self.num_input_signals()):
            name = self.canonicalize_input(i)
            val = self.get_input(i)
            ts = self.get_input_timestamp(i)
            
            if ts is None:
                continue
            
            if not i in self.first:
                self.first[i] = ts
                self.counter[i] = 0
            friendly = ts - self.first[i]
#             if isinstance(val, numpy.ndarray):
#                 s = "%s %s" % (str(val.shape), str(val.dtype))
#             else:
            s = str(val)
            if len(s) > 40:
                s = s[:40]
            s = s.replace('\n', '|')
            date = datetime.fromtimestamp(ts).isoformat(' ')[:-4]
            ts = "%.2f" % ts
            self.debug('%s (%8.2fs) %12s %5d  %s' % 
                       (date, friendly, name, self.counter[i], s))
            self.counter[i] += 1
Example #13
0
class Any(Block):
    ''' 
        Joins the stream of multiple signals onto one output signal.
    '''

    Block.alias('any')

    Block.input_is_variable('Signals to be put on the same stream.', min=1)
    Block.output('stream', 'Unified stream.')

    def init(self):
        self.last_ts = {}
        self.buffer = []

    def update(self):
        # DO NOT USE NAMES
        # TODO: check other blocks for this bug
        nsignals = self.num_input_signals()
        for i in range(nsignals):
            value = self.get_input(i)
            ts = self.get_input_timestamp(i)
            if value is None:
                continue
            if not i in self.last_ts or ts != self.last_ts[i]:
                self.buffer.append((ts, value))
            self.last_ts[i] = ts

#        t = [x[0] for x in self.buffer]
#        self.debug(' after1: %s' % t)
#
        self.buffer = sorted(self.buffer, key=lambda x: x[0])

        #        t = [x[0] for x in self.buffer]
        #        self.debug(' sort: %s' % t)

        # Make sure we saw every signal before outputing one
        #        if len(self.last_ts) == nsignals:
        # FIXME: bug we will send one sample of the last stream

        if self.buffer:
            ts, value = self.buffer.pop(0)
            self.set_output(0, value, ts)
Example #14
0
class FPSPrint(Block):
    ''' Prints the fps count for the input signals. '''
    Block.alias('fps_print')

    Block.input_is_variable('Any signal.', min=1)

    def init(self):
        self.state.last_timestamp = None

    def update(self):
        current = max(self.get_input_signals_timestamps())
        last = self.state.last_timestamp
        if last is not None:

            difference = current - last
            fps = 1.0 / difference

            self.info("FPS %s %.1f" % (self.canonicalize_input(0), fps))

        self.state.last_timestamp = current
Example #15
0
class ImageGrid(Block):
    '''
        A block that creates a larger image by arranging them in a grid. 
        
        The output is rgb, uint8.
        
        Inputs are passed through the "torgb" function.
    
    '''

    Block.alias('grid')

    Block.config('cols', 'Columns in the grid.', default=None)
    Block.config('bgcolor', 'Background color.', default=[0, 0, 0])

    Block.config('pad', 'Padding for each cell', default=0)
    Block.input_is_variable('Images to arrange in a grid.', min=1)
    Block.output('grid', 'Images arranged in a grid.')

    def update(self):
        if not self.all_input_signals_ready():
            return
        
        n = self.num_input_signals()
        for i in range(n):
            input_check_convertible_to_rgb(self, i)

        cols = self.config.cols

        if cols is not None and not isinstance(cols, int):
            raise BadConfig('Expected an integer.', self, 'cols')

        images = [self.get_input(i) for i in range(n)]
        
        images = map(torgb, images)
        canvas = make_images_grid(images,
                                  cols=self.config.cols,
                                  pad=self.config.pad,
                                  bgcolor=self.config.bgcolor)
        
        self.set_output(0, canvas)
Example #16
0
class AsJSON(Block):
    ''' Converts the input into a JSON string. 
    
        TODO: add example
    '''

    Block.alias('as_json')

    Block.input_is_variable('Inputs to transcribe as JSON.')

    Block.output('json', 'JSON string.')

    def update(self):
        data = {}
        data['timestamp'] = max(self.get_input_signals_timestamps())
        for i in range(self.num_input_signals()):
            name = self.canonicalize_input(i)
            value = self.input[name]
            data[name] = value

        self.output.json = json.dumps(data)
Example #17
0
class FPSLimit(Block):
    ''' This block limits the output update to a certain *realtime* framerate.
    
    Note that this uses realtime wall clock time -- not the data time!
    This is mean for real-time applications, such as visualization.'''

    Block.alias('fps_limit')

    Block.config('fps', 'Realtime fps limit.')

    Block.input_is_variable('Arbitrary signals.')
    Block.output_is_variable('Arbitrary signals with limited framerate.')

    def init(self):
        self.state.last_timestamp = None

    def update(self):
        should_update = False

        last = self.state.last_timestamp
        current = time.time()

        if last is None:
            should_update = True
            self.state.last_timestamp = current
        else:
            fps = self.config.fps
            delta = 1.0 / fps
            difference = current - last
            #print "difference: %s ~ %s" % (difference, delta)
            if difference > delta:
                should_update = True
                self.state.last_timestamp = current

        if not should_update:
            return

        # Just copy the input to the output
        for i in range(self.num_input_signals()):
            self.set_output(i, self.get_input(i), self.get_input_timestamp(i))
Example #18
0
class Join(Block):
    ''' 
    This block joins multiple signals into one.
    '''

    Block.alias('join')

    Block.input_is_variable('Signals to be joined together.')
    Block.output('joined', 'Joined signals.')

    def init(self):
        sizes = {}
        names = self.get_input_signals_names()
        for signal in names:
            sizes[signal] = None

        self.state.sizes = sizes

    def update(self):
        sizes = self.state.sizes
        result = []
        for name in self.get_input_signals_names():
            value = self.get_input(name)
            # workaround for scalar values
            value = np.reshape(value, np.size(value))
            size = len(value)
            if value is None:
                return
            if sizes[name] is None:
                sizes[name] = size
            else:
                if size != sizes[name]:
                    raise Exception('Signal %s changed size from %s to %s.' %
                                    (name, sizes[name], size))

            result.extend(value)

        self.output[0] = np.array(result)
Example #19
0
class PickleGroup(Block):
    ''' Dumps the input as a :py:mod:`pickle` file, in the form
        of a dictionary  signal name -> value.    
    '''
    Block.alias('pickle_group')
    Block.config('file', 'File to write to.')
    Block.input_is_variable('Any number of pickable signals.')

    def write(self, x, filename):
        make_sure_dir_exists(filename)
        with open(filename, 'wb') as f:
            pickle.dump(x, f, protocol=pickle.HIGHEST_PROTOCOL)

    def get_dict(self):
        data = {}
        for signal in self.get_input_signals_names():
            data[signal] = self.get_input(signal)
        return data

    def update(self):
        self.write(self.get_dict(), self.config.file + '.part')

    def finish(self):
        self.write(self.get_dict(), self.config.file)
Example #20
0
 class MyBlockC(Block):
     Block.input_is_variable()
Example #21
0
class Sync(Generator):
    ''' 
    This block synchronizes a set of streams to the first stream (the master).
    
    The first signal is called the "master" signal.
    The other (N-1) are slaves.
    
    We guarantee that:
    
    - if the slaves are faster than the master,
      then we output exactly the same.
      
    Example diagrams: ::
      
        Master  *  *  *   *   *
        Slave   ++++++++++++++++
        
        Master  *  *  *   *   *
        output? v  v  x   v  
        Slave   +    +      +   
        output? v    v      v
    '''
    Block.alias('sync')

    Block.input_is_variable('Signals to synchronize. The first is the master.',
                            min=2)
    Block.output_is_variable('Synchronized signals.')

    def init(self):
        # output signals get the same name as the inputs
        names = self.get_input_signals_names()

        # create a state for each signal: it is an array
        # of tuples (timestamp, tuple)
        queues = {}
        for signal in names:
            queues[signal] = []

        # note in all queues,
        # [(t0,...), (t1,...), ... , (tn,...) ]
        # and we have t0 > tn
        # The chronologically first (oldest) is queue[-1]
        # You get one out using queue.pop()
        # You insert one with queue.insert(0,...)

        self.set_state('queues', queues)
        self.state.already_seen = {}

        self.set_state('master', names[0])
        self.set_state('slaves', names[1:])

        # The output is an array of tuple (timestamp, values)
        # [
        #    (timestamp1, [value1,value2,value3,...]),
        #    (timestamp1, [value1,value2,value3,...])
        # ]#
        self.set_state('output', [])

    def update(self):
        def debug(s):
            if False:  # XXX: use facilities
                print('sync %s %s' % (self.name, s))

        output = self.get_state('output')
        queues = self.get_state('queues')
        names = self.get_input_signals_names()
        # for each input signal, put its value in the queues
        # if it is not already present
        for i, name in enumerate(names):
            #            Commenting this; we clarified 0 = eternity; None = no signal yet
            if not self.input_signal_ready(i):
                debug('Ignoring signal %r because timestamp is None.' % name)
                continue

            current_timestamp = self.get_input_timestamp(i)
            current_value = self.get_input(i)

            if (name in self.state.already_seen
                    and self.state.already_seen[name] == current_timestamp):
                continue
            else:
                self.state.already_seen[name] = current_timestamp

            queue = queues[name]
            # if there is nothing in the queue
            # or this is a new sample
            if ((len(queue) == 0) or newest(queue).timestamp !=
                    current_timestamp):  # new sample
                debug("Inserting signal '%s'  ts %s (queue len: %d)" %
                      (name, current_timestamp, len(queue)))
                # debug('Before the queue is: %s' % queue)
                add_last(
                    queue,
                    Sample(timestamp=current_timestamp, value=current_value))

                # debug('Now the queue is: %s' % queue)

        master = self.get_state('master')
        master_queue = queues[master]
        slaves = self.get_state('slaves')

        # if there is more than one value in each slave
        if len(master_queue) > 1:
            val = master_queue.pop()
            debug('DROPPING master (%s) ts =%s' % (master, val.timestamp))

        # Now check whether all slaves signals are >= the master
        # If so, output a synchronized sample.
        if master_queue:
            all_ready = True
            master_timestamp = master_queue[-1].timestamp
            # print "Master timestamp: %s" % (master_timestamp)
            for slave in slaves:
                slave_queue = queues[slave]
                # remove oldest
                while (len(slave_queue) > 1
                       and oldest(slave_queue).timestamp < master_timestamp
                       and oldest(slave_queue).timestamp != ETERNITY):
                    debug("DROP one from %s" % slave)
                    slave_queue.pop()

                if not slave_queue:
                    # or (master_timestamp > slave_queue[-1].timestamp):
                    all_ready = False
                    # its_ts = map(lambda x:x.timestamp, slave_queue)
                    # print "Slave %s not ready: %s" %(slave, its_ts)
                    break

            if all_ready:
                debug("**** Ready: master timestamp %s " % (master_timestamp))
                master_value = master_queue.pop().value
                output_values = [master_value]
                for slave in slaves:
                    # get the freshest still after the master
                    slave_queue = queues[slave]
                    slave_timestamp, slave_value = slave_queue.pop()
                    # print('Slave, %s %s ' % (slave, slave_timestamp))
                    if slave_timestamp == ETERNITY:
                        slave_queue.append((slave_timestamp, slave_value))
                    # # not true anymore, assert slave_timestamp >=
                    # master_timestamp
                    # difference = slave_timestamp - master_timestamp
                    # debug(" - %s timestamp %s diff %s" %
                    #      (slave, slave_timestamp, difference))
                    output_values.append(slave_value)
                output.insert(0, (master_timestamp, output_values))

        # XXX XXX not really sure here
        # if master has more than one sample, then drop the first one

        # if we have something to output, do it
        if output:
            timestamp, values = output.pop()
            # FIXME: had to remove this for Vehicles simulation
            # assert timestamp > 0
            debug("---------------- @ %s" % timestamp)
            for i in range(self.num_output_signals()):
                self.set_output(i, values[i], timestamp)

    def next_data_status(self):
        output = self.get_state('output')
        if not output:  # no output ready
            return (False, None)
        else:
            timestamp = self.output[-1][0]
            return (True, timestamp)
Example #22
0
class Plot(Block):
    ''' 
        Plots the inputs using matplotlib. 
    
        This block accepts an arbitrary number of signals. 
        Each signals is treated independently and plot separately. 
        
        Each signal can either be:
        
        1.  A tuple of length 2. It is interpreted as a tuple ``(x,y)``,
            and we plot ``x`` versus ``y`` (see also :ref:`block:make_tuple`).
            
        2.  A list of numbers, or a 1-dimensional numpy array of length N. 
            In this case, it is interpreted as the y values, 
            and we set  ``x = 1:N``. 
        
    '''

    Block.alias('plot')

    Block.config('width', 'Image dimension', default=320)
    Block.config('height', 'Image dimension', default=240)
    Block.config('xlabel', 'X label for the plot.', default=None)
    Block.config('ylabel', 'Y label for the plot.', default=None)
    Block.config('legend',
                 'List of strings to use as legend handles.',
                 default=None)
    Block.config('title',
                 'If None, use the signal name. Set to ``""`` to disable.',
                 default=None)
    Block.config('format', 'Line format ("-",".","x-",etc.)', default='-')
    Block.config('symmetric', 'An alternative to y_min, y_max.'
                 ' Makes sure the plot is symmetric for y. ',
                 default=False)
    Block.config('x_min',
                 'If set, force the X axis to have this minimum.',
                 default=None)
    Block.config('x_max',
                 'If set, force the X axis to have this maximum.',
                 default=None)
    Block.config('y_min',
                 'If set, force the Y axis to have this minimum.',
                 default=None)
    Block.config('y_max',
                 'If set, force the Y axis to have this maximum.',
                 default=None)
    Block.config('keep', 'If True, tries to reuse the figure, without closing.'
                 ' (buggy on some backends)',
                 default=False)
    Block.config('transparent',
                 'If true, outputs a RGBA image instead of RGB.',
                 default=False)

    Block.config(
        'tight',
        'Uses "tight" option for creating png (Matplotlib>=1.1).',
        default=False,
    )
    Block.config('fancy_styles',
                 'A list of fancy styles to apply (%s).' % fancy_styles.keys(),
                 default=[])

    Block.input_is_variable('Data to plot.')

    Block.output('rgb', 'Resulting image.')

    def init(self):
        self.line = None

        # figure gets initialized in update() on the first execution
        self.figure = None

        self.warned = False

    def init_figure(self):
        width = self.config.width
        height = self.config.height

        # TODO: remove from here
        pylab.rc('xtick', labelsize=8)
        pylab.rc('ytick', labelsize=8)
        ''' Creates figure object and axes '''
        self.figure = pylab.figure(frameon=False,
                                   figsize=(width / 100.0, height / 100.0))
        # TODO: is there a better way?
        # left, bottom, right, top
        # borders = [0.15, 0.15, 0.03, 0.05]
        # w = 1 - borders[0] - borders[2]
        # h = 1 - borders[1] - borders[3]
        # self.axes = pylab.axes([borders[0], borders[1], w, h])
        self.axes = pylab.axes()
        self.figure.add_axes(self.axes)

        pylab.draw_if_interactive = lambda: None

        pylab.figure(self.figure.number)
        if self.config.title is not None:
            if self.config.title != "":
                self.axes.set_title(self.config.title, fontsize=10)
        else:
            # We don't have a title ---
            t = ", ".join(self.get_input_signals_names())
            self.axes.set_title(t, fontsize=10)

        if self.config.xlabel:
            self.axes.set_xlabel(self.config.xlabel)
        if self.config.ylabel:
            self.axes.set_ylabel(self.config.ylabel)

        self.legend_handle = None

        self.lines = {}
        self.lengths = {}

    def apply_styles(self, pylab):
        # TODO: check type
        for style in self.config.fancy_styles:
            if not style in fancy_styles:
                error = ('Cannot find style %r in %s' %
                         (style, fancy_styles.keys()))
                raise BadConfig(error, self, 'style')
            function = fancy_styles[style]
            function(pylab)

    def plot_one(self, id, x, y, format):  # @ReservedAssignment
        assert isinstance(x, np.ndarray)
        assert isinstance(y, np.ndarray)
        assert len(x.shape) <= 1
        assert len(y.shape) <= 1
        assert len(x) == len(y)

        if id in self.lengths:
            if self.lengths[id] != len(x):
                redraw = True
                self.axes.lines.remove(self.lines[id])
            else:
                redraw = False
        else:
            redraw = True

        if redraw:
            res = self.axes.plot(x, y, format)
            line = res[0]

            self.lines[id] = line
        else:
            self.lines[id].set_ydata(y)
            self.lines[id].set_xdata(x)

        self.lengths[id] = len(x)

        if self.limits is None:
            self.limits = np.array([min(x), max(x), min(y), max(y)])
        else:
            self.limits[0] = min(self.limits[0], min(x))
            self.limits[1] = max(self.limits[1], max(x))
            self.limits[2] = min(self.limits[2], min(y))
            self.limits[3] = max(self.limits[3], max(y))

        # self.limits = map(float, self.limits)
    def update(self):
        self.limits = None

        start = time.clock()

        if self.figure is None:
            self.init_figure()

        pylab.figure(self.figure.number)

        self.apply_styles(pylab)

        for i in range(self.num_input_signals()):
            value = self.input[i]
            if value is None:
                raise BadInput('Input is None (did you forget a |sync|?)',
                               self, i)
            elif isinstance(value, tuple):
                if len(value) != 2:
                    raise BadInput(
                        'Expected tuple of length 2 instead of %d.' %
                        len(value), self, i)

                xo = value[0]
                yo = value[1]

                if xo is None or yo is None:
                    raise BadInput('Invalid members of tuple', self, i)

                x = np.array(xo)
                y = np.array(yo)

                # X must be one-dimensional
                if len(x.shape) > 1:
                    raise BadInput('Bad x vector w/shape %s.' % str(x.shape),
                                   self, i)

                # y should be of dimensions ...?
                if len(y.shape) > 2:
                    raise BadInput('Bad shape for y vector %s.' % str(y.shape),
                                   self, i)

                if len(x) != y.shape[0]:
                    raise BadInput('Incompatible dimensions x: %s, y: %s' %
                                   (str(x.shape), str(y.shape)))
                # TODO: check x unidimensional (T)
                # TODO: check y compatible dimensions (T x N)

            else:
                y = np.array(value)
                if len(y.shape) > 2:
                    raise BadInput('Bad shape for y vector %s.' % str(y.shape),
                                   self, i)

                if len(y.shape) == 1:
                    x = np.array(range(len(y)))
                else:
                    assert (len(y.shape) == 2)
                    x = np.array(range(y.shape[1]))

            if len(x) <= 1:
                continue

            if len(y.shape) == 2:
                y = y.transpose()

            if len(y.shape) == 1:
                pid = self.canonicalize_input(i)
                self.plot_one(pid, x, y, self.config.format)
            else:
                assert (len(y.shape) == 2)
                num_lines = y.shape[0]
                for k in range(num_lines):
                    pid = "%s-%d" % (self.canonicalize_input(i), k)
                    yk = y[k, :]
                    self.plot_one(pid, x, yk, self.config.format)
            # TODO: check that if one has time vector, also others have it

        if self.limits is not None:

            if self.config.x_min is not None:
                self.limits[0] = self.config.x_min
            if self.config.x_max is not None:
                self.limits[1] = self.config.x_max
            if self.config.y_min is not None:
                self.limits[2] = self.config.y_min
            if self.config.y_max is not None:
                self.limits[3] = self.config.y_max

            if self.config.symmetric:
                if self.config.y_min is not None or \
                   self.config.y_max is not None:
                    raise BadConfig(
                        'Cannot specify symmetric together with'
                        'y_min or y_max.', self, 'symmetric')

                M = max(abs(self.limits[2:4]))
                if 'dottedzero' in self.config.fancy_styles:
                    a = pylab.axis()
                    pylab.plot([a[0], a[1]], [0, 0], 'k--')

                self.limits[2] = -M
                self.limits[3] = M

            # leave some space above and below
            self.limits[2] = self.limits[2] * 1.1
            self.limits[3] = self.limits[3] * 1.1

            self.axes.axis(self.limits)

        if self.legend_handle is None:
            legend = self.config.legend
            if legend:
                self.legend_handle = self.axes.legend(*legend,
                                                      loc='upper right',
                                                      handlelength=1.5,
                                                      markerscale=2,
                                                      labelspacing=0.03,
                                                      borderpad=0,
                                                      handletextpad=0.03,
                                                      borderaxespad=1)

        # http://matplotlib.sourceforge.net/users/tight_layout_guide.html
        try:
            pylab.tight_layout()
        except Exception as e:
            msg = ('Could not call tight_layout(); available only on '
                   'Matplotlib >=1.1 (%s)' % e)
            if not self.warned:
                self.warning(msg)
                self.warned = True

        plotting = time.clock() - start

        start = time.clock()
        # There is a bug that makes the image smaller than desired
        # if tight is True
        pixel_data = pylab2rgb(transparent=self.config.transparent,
                               tight=self.config.tight)

        # So we check and compensate
        shape = pixel_data.shape[0:2]
        shape_expected = (self.config.height, self.config.width)
        from procgraph_images import image_pad  # need here, otherwise circular

        if shape != shape_expected:
            msg = ('pylab2rgb() returned size %s instead of %s.' %
                   (shape, shape_expected))
            msg += ' I will pad the image with white.'
            self.warning(msg)
            pixel_data = image_pad(pixel_data,
                                   shape_expected,
                                   bgcolor=[1, 1, 1])
            assert_equal(pixel_data.shape[0:2], shape_expected)

        reading = time.clock() - start

        if False:
            # 30 49 30 before
            print("plotting: %dms    reading: %dms" %
                  (plotting * 1000, reading * 1000))

        self.output.rgb = pixel_data

        if not self.config.keep:
            pylab.close(self.figure.number)
            self.figure = None
Example #23
0
 class MyBlock(Block):
     Block.input('x')
     Block.input_is_variable()
Example #24
0
 class MyBlockA(Block):
     Block.input_is_variable()
     Block.output_is_variable()
Example #25
0
 class MyBlockB(Block):
     Block.input_is_variable()
     Block.output('only one')