Exemple #1
0
 class MyBlockB(Block):
     Block.input_is_variable()
     Block.output('only one')
Exemple #2
0
class MPlayer(Generator):
    ''' Decodes a video stream. '''

    Block.alias('mplayer')

    Block.config(
        'file', 'Input video file. This can be in any format that '
        '``mplayer`` understands.')
    Block.config('quiet',
                 'If true, suppress stderr messages from mplayer.',
                 default=True)
    Block.config('stats', 'If true, writes some statistics about the '
                 'remaining time.',
                 default=True)

    Block.config('max_duration', 'Maximum length, in seconds, of the output.'
                 'Useful to get a maximum duration.',
                 default=None)

    Block.output('video', 'RGB stream as numpy array.')

    TIMESTAMPS_SUFFIX = ".timestamps"

    def init(self):
        self.programs = check_programs_existence(programs=['mencoder'])

        for p in self.programs:
            self.info('Using %13s = %s' % (p, self.programs[p]))

        if not isinstance(self.config.file, str):
            raise BadConfig('This should be a string.', self, 'file')

        self.file = expand(self.config.file)

        if not os.path.exists(self.file):
            msg = 'File %r does not exist.' % self.file
            raise BadConfig(msg, self, 'file')

        timestamps_file = self.file + MPlayer.TIMESTAMPS_SUFFIX
        if os.path.exists(timestamps_file):
            self.info('Reading timestamps from %r.' % timestamps_file)
            self.timestamps = open(timestamps_file)
        else:
            self.timestamps = None
            #self.info('Will use fps for timestamps.')

        self.mencoder_started = False

        self.state.timestamp = None
        self.state.timestamp = self.get_next_timestamp()
        self.state.next_frame = None
        self.state.finished = False

        self.num_frames_read = 0

    def open_mencoder(self):
        self.mencoder_started = True

        info = pg_video_info(self.file, intolerant=True)

        self.width = info['width']
        self.height = info['height']

        self.fps = info['fps']
        self.length = info['length']

        check('float|int', self.length)
        check('float|int', self.fps)
        self.info('length: %r' % self.length)
        self.info('fps: %r' % self.fps)
        self.approx_frames = int(math.ceil(self.length * self.fps))

        # TODO: reading non-RGB streams not supported
        self.info('Reading %dx%d @ %.3f fps '
                  ' (length %ss, approx %d frames), from %s.' %
                  (self.width, self.height, self.fps, self.length,
                   self.approx_frames, friendly_path(self.config.file)))

        self.shape = (self.height, self.width, 3)
        self.dtype = 'uint8'

        pixel_format = "rgb24"

        self.temp_dir = tempfile.mkdtemp(prefix='procgraph_fifo_dir')
        self.fifo_name = os.path.join(self.temp_dir, 'mencoder_fifo')
        os.mkfifo(self.fifo_name)
        args = [
            self.programs['mencoder'], self.file, '-ovc', 'raw', '-rawvideo',
            'w=%d:h=%d:format=%s' % (self.width, self.height, pixel_format),
            '-of', 'rawvideo', '-vf', 'format=rgb24', '-nosound', '-o',
            self.fifo_name
        ]

        self.tmp_stdout = tempfile.TemporaryFile()
        self.tmp_stderr = tempfile.TemporaryFile()

        if self.config.quiet:
            self.process = subprocess.Popen(args,
                                            stdout=self.tmp_stdout.fileno(),
                                            stderr=self.tmp_stderr.fileno())
        else:
            self.process = subprocess.Popen(args)

        self.delta = 1.0 / self.fps

        if not self.timestamps:
            msg = ('No timestamps found; using delta = %.2f (%.2f fps).' %
                   (self.delta, self.fps))
            self.info(msg)

        self.stream = open(self.fifo_name, 'r')

    def get_next_timestamp(self):
        if self.timestamps:
            # If reading from files
            l = self.timestamps.readline()
            if not l:
                # XXX warn if not even one
                self.error_once(
                    'Timestamps were too short; now using incremental.')
                if self.state.timestamp is not None:
                    return self.state.timestamp + self.delta
                else:
                    # empty file, not even one
                    self.error_once('Empty timestamp file? Starting at 0.')
                    return 0.0
            else:
                return float(l)
        else:
            if self.state.timestamp is None:
                return 0.0
            else:
                return self.state.timestamp + self.delta

    def update(self):
        if not self.mencoder_started:
            self.open_mencoder()
            self.read_next_frame()

        self.set_output(0,
                        value=self.state.next_frame,
                        timestamp=self.state.timestamp)
        self.state.timestamp = self.get_next_timestamp()

        self.state.next_frame = None
        self.read_next_frame()

        if self.config.stats:
            self.num_frames_read += 1
            if self.num_frames_read % 500 == 0:
                self.print_stats()

    def print_stats(self):
        if self.approx_frames != 0:
            percentage = 100.0 * self.num_frames_read / self.approx_frames
        else:
            percentage = 0
        # this assumes constant fps
        seconds = self.num_frames_read * self.delta
        seconds_total = self.approx_frames * self.delta
        self.info('%6d/%d frames, %.1f/%.1f sec (%4.1f%%) of %s' %
                  (self.num_frames_read, self.approx_frames, seconds,
                   seconds_total, percentage, friendly_path(self.file)))

    def read_next_frame(self):
        if self.state.finished:
            return
        if self.state.next_frame is not None:
            return

        # TODO: add display of stderr after timeout

        dtype = numpy.dtype(('uint8', self.shape))
        rgbs = numpy.fromfile(self.stream, dtype=dtype, count=1)

        if len(rgbs) == 0:
            self.state.next_frame = None
            self.state.finished = True
        else:
            self.state.next_frame = rgbs[0, :].squeeze()

    def too_long_for_us(self):
        """ Returns true if max_duration is set and we passed it. """
        if self.config.max_duration is None:
            return False

        # We haven't started yet, so we don't have "delta"
        if not self.mencoder_started:
            return False

        passed = self.num_frames_read * self.delta

        if passed > self.config.max_duration:
            self.info_once(
                'Finishing because passed %d frames = %f seconds > duration %f'
                % (self.num_frames_read, passed, self.config.max_duration))
            return True
        else:
            return False

    def next_data_status(self):
        # The stream has finished
        if self.state.finished:
            return (False, None)
        # We passed the limit
        elif self.too_long_for_us():
            return (False, None)
        else:
            return (True, self.state.timestamp)

    def finish(self):
        # TODO: make sure process is closed?
        if os.path.exists(self.fifo_name):
            os.unlink(self.fifo_name)
        if os.path.exists(self.temp_dir):
            os.rmdir(self.temp_dir)
Exemple #3
0
 class MyBlock(Block):
     Block.output('x')
     Block.output('x')
Exemple #4
0
 class MyBlock(Block):
     Block.output_is_variable()
     Block.output('x')
class VehiclesCairoDisplay(Block):
    ''' Produces a top-down plot of a circular arena. '''

    Block.alias('vehicles_cairo_display')

    Block.config('format', 'pdf|png', default='pdf')
    Block.config('file', 'Output file (pdf)', default=None)
    Block.output('rgb', 'RGB data (png)')
    Block.config('transparent', 'Outputs RGB with transparent bg',
                 default=False)

    Block.config('width', 'Image width in points.', default=600)
    Block.config('height', 'Image height in points.', default=600)
    Block.config('sidebar_width', default=200)

    # Sidebar options
    Block.config('display_sidebar', default=True)
    Block.config('trace', 'Trace the path', default=False)
    Block.config('plotting_params',
                 'Configuration to pass to vehicles_cairo_display_all()',
                 default={})
    Block.config('sidebar_params',
                 'Configuration to pass to create_sidebar()',
                 default={})

    Block.config('swf', 'Converts PDF to SWF using pdf2swf', default=True)

    Block.input('boot_obs', '')

    def get_shape(self):
        w = self.config.width
        if self.config.display_sidebar:
            w += self.config.sidebar_width
        h = self.config.height
        
        return (w, h) 

    def init(self):
        self.format = self.config.format
        (w, h) = self.get_shape()
        self.total_width = w
        self.total_height = h
        self.frame = 0

        if self.format == 'pdf':
            self.init_pdf()
        elif self.format == 'png':
            self.init_png()
        else:
            raise BadConfig('Invalid format %r.' % self.format, self, 'format')
        self.count = 0
        self.fps = None
        self.t0 = None

        self.tmp_cr = None

    def init_pdf(self):
        self.filename = self.config.file
        self.tmp_filename = self.filename + '.active'
        make_sure_dir_exists(self.filename)
        self.info("Creating file %r." % self.filename)
        import cairo
        self.surf = cairo.PDFSurface(self.tmp_filename,  # @UndefinedVariable
                                     self.total_width,
                                     self.total_height)

    def init_png(self):
        import cairo
        w, h = self.total_width, self.total_height
        # note (w,h) here and (h,w,h*4) below; I'm not sure but it works
        self.argb_data = np.empty((h, w, 4), dtype=np.uint8)
        self.argb_data.fill(255)

        self.surf = cairo.ImageSurface.create_for_data(# @UndefinedVariable
                        self.argb_data,
                        cairo.FORMAT_ARGB32,  # @UndefinedVariable
                         w, h, w * 4)

    def update(self):
        # Estimate fps
        if self.count == 0:
            self.t0 = self.get_input_timestamp(0)
        if self.count >= 1:
            delta = self.get_input_timestamp(0) - self.t0
            self.fps = 1.0 * self.count / delta

        self.count += 1

        if self.format == 'pdf':
            self.update_pdf()
        elif self.format == 'png':
            self.update_png()
        else:
            assert False

    def update_png(self):
        import cairo

        cr = cairo.Context(self.surf)  # @UndefinedVariable

        self.draw_everything(cr)
        self.surf.flush()

        if not self.config.transparent:
            rgb = self.argb_data[:, :, :3].copy()
            # fix red/blue inversion
            rgb[:, :, 0] = self.argb_data[:, :, 2]
            rgb[:, :, 2] = self.argb_data[:, :, 0]
            assert rgb.shape[2] == 3
        else:
            rgb = self.argb_data.copy()
            # fix red/blue inversion
            rgb[:, :, 0] = self.argb_data[:, :, 2]
            rgb[:, :, 2] = self.argb_data[:, :, 0]
            assert rgb.shape[2] == 4

        self.output.rgb = rgb

    def update_pdf(self):
        import cairo
        # If I don't recreate it, it will crash
        cr = cairo.Context(self.surf)  # @UndefinedVariable
        if not self.config.transparent:
            # Set white background
            bg_color = [1, 1, 1]
            cr.rectangle(0, 0, self.total_width, self.total_height)
            cr.set_source_rgb(bg_color[0], bg_color[1], bg_color[2])
            cr.fill()
        else:
            # Green screen :-)
            cr.rectangle(0, 0, self.total_width, self.total_height)
            cr.set_source_rgba(0, 1, 0, 0)
            cr.fill()
        self.draw_everything(cr)
        self.surf.flush()
        self.surf.show_page()  # Free memory self.cr?

    def draw_everything(self, cr):

        boot_obs = self.input.boot_obs

        if 'id_episode' in boot_obs:
            id_episode = boot_obs['id_episode'].item()
        else:
            id_episode = ''
            
        id_vehicle = boot_obs['id_robot'].item()
        
        if 'extra' in boot_obs:
            extra = boot_obs['extra'].item()
        else:
            extra = {}

        def extra_draw_world(cr):
            if 'servonav' in extra:
                plot_servonave(cr, extra['servonav'])

            if 'servoing_poses' in extra:
                plot_servoing_poses(cr, extra['servoing_poses'])

        plotting_params = self.config.plotting_params

        plotting_params['extra_draw_world'] = extra_draw_world

        sidebar_params = self.config.sidebar_params

        # todo: check
        sim_state = extra['robot_state']

        observations_values = boot_obs['observations']

        commands = boot_obs['commands']

        commands_source = boot_obs['commands_source'].item()
        timestamp = boot_obs['time_from_episode_start'].item()

        with cairo_save(cr):
            if self.config.display_sidebar:
                padding = 0.03 * self.config.width
                map_width = self.config.width - 2 * padding
                map_height = self.config.height - 2 * padding
                cr.translate(padding, padding)
            else:
                map_width = self.config.width
                map_height = self.config.height

            with cairo_save(cr):
                cr.rectangle(0, 0, map_width, map_height)
                cr.clip()

                # TODO: implement trace

                vehicles_cairo_display_all(cr,
                                       map_width,
                                       map_height,
                                       sim_state,
                                       **plotting_params)

            if self.config.display_sidebar:
                cr.set_line_width(1)
                cr.set_source_rgb(0, 0, 0)
                cr.rectangle(0, 0, map_width, map_height)
                cr.stroke()

        if self.config.display_sidebar:
            with cairo_transform(cr, t=[self.config.width, 0]):
                create_sidebar(cr, width=self.config.sidebar_width,
                               height=self.config.height,
                               sim_state=sim_state,
                               id_vehicle=id_vehicle,
                               id_episode=id_episode,
                               timestamp=timestamp,
                               observations_values=observations_values,
                               commands_values=commands,
                               commands_source=commands_source,
                               **sidebar_params)

    def finish(self):
        if self.format == 'pdf':
            self.finish_pdf()

    def finish_pdf(self):
        self.surf.finish()
        if os.path.exists(self.filename):
            os.unlink(self.filename)
        if os.path.exists(self.tmp_filename):
            os.rename(self.tmp_filename, self.filename)

        if self.config.swf:
            if self.fps is None:
                self.error('Only one frame seen?')
            else:
                basename, _ = os.path.splitext(self.filename)
                swf = '%s.swf' % basename
                try:
                    command = ['pdf2swf',
                                           # "-b", # --defaultviewer
                                           # "-l", # --defaultloader
                                           '-G',  # flatten
                                           '-s', 'framerate=%d' % self.fps,
                                            self.filename,
                                            '-o', swf]
                    self.info(' '.join(command))
                    subprocess.check_call(command)
                except Exception as e:
                    self.error('Could not convert to swf: %s' % e)
                    if os.path.exists(swf):
                        os.unlink(swf)

        self.info("Completed %r." % self.filename)
Exemple #6
0
class BlockSimpleExample(Block):
    ''' This is a documented example of the simplest block possible.
    
        This docstring will be included in the generated documentation.
    '''

    # Define a name for the block using `Block.alias`.
    # If not given, the class name ('BlockExample') will be used.
    Block.alias('block_example')

    # Define the inputs for the blocks using `Block.input`.
    # It takes two parameters: name of the signal, and a docstring.
    # ProcGraph will generate wonderful documentation for you, and lets
    # you write documentation for what you are doing at almost every step:
    # take every chance to document things.
    Block.input('baz', 'Measured baz in the particle accelerator.')

    # This block has one output as well.
    Block.output('baz_compensated',
                 'Compensated baz value according to calibration.')

    # Now define the configuration for the block.
    # Pass the optional 'default' argument to define a default for the
    # parameter.
    # Without the default, the user *must* pass the configuration parameter.
    Block.config('bias', 'Bias for the accelerator.', default=0)

    # Now let's write the logic for the block.

    # Very important! Do not use __init__ for initialization. Use init().
    # ProcGraph does a lot behind the scenes to let you have a streamlined
    # experiences, but sometimes there is a price to pay.
    #
    # def __init__(self):
    #     ...
    #
    # Initialize the block in the init() function.
    def init(self):
        # For this example, we have nothing to do here.
        pass

    # You will write the main logic in the update() function.
    # It is called whenever the input signals are updated.
    # In update() you do your computations, and update the value
    # of the output signals.
    def update(self):
        # You can access the value of the configuration parameters
        # using  `self.config.<parameter name>`
        bias = self.config.bias

        # You read the input signals using `self.input.<signal name>`:
        baz = self.input.baz

        baz_compensated = baz + bias

        # Finally, you set the output using `self.output.<signal name> = ...`.
        self.output.baz_compensated = baz_compensated
        # You're done: ProcGraph will propagate this value to the other blocks
        # connected...

        # Note that for stateless computations such as this, creating a Block
        # class is likely overkill -- use register_simple_block() with an
        # instantaneous function instead -- see next example.

    # Finish is called at the end of the execution.
    def finish(self):
        # For this example, we have nothing to do here.
        pass
Exemple #7
0
class FilesFromDir(Generator):
    ''' 
        This block reads the filenames from a directory according
        to a given regexp. 
        
        For now the timestamp starts from 0 and it is fixed.
    '''
    Block.alias('files_from_dir')
    Block.config('dir', 'Directory containing the image files.')
    Block.config('regexp', 'Regular expression for images.',
#                  default='(\w+)(\d+)\.(\w+)')
                default='.+\.(png|jpg)')
    Block.config('fps', 'Fixed frame per second.', default=20.0)
    Block.output('filename', 'Image filename')

    def init(self):
        dirname = self.config.dir
        dirname = expand(dirname)

        if not os.path.exists(dirname):
            raise BadConfig('Not existent directory %r.' % dirname, self,
                            'dir')

        if not os.path.isdir(dirname):
            raise BadConfig('The file %r is not a directory.' % dirname, self,
                            'dir')

        regexp = self.config.regexp

        # TODO: use proper logging
        self.info("Reading %r from %r." % (regexp, dirname))
        all_files = os.listdir(dirname)

        selected = [os.path.join(dirname, f)
                    for f in all_files
                    if re.match(regexp, f)]

        def natural_key(string):
            """See http://www.codinghorror.com/blog/archives/001018.html"""
            return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string)]

        selected.sort(key=lambda x: natural_key(os.path.basename(x)))

        self.info("Selected %d/%d files." % (len(selected), len(all_files)))

        fps = float(self.config.fps)
        if fps <= 0:
            raise BadConfig(self, "Invalid fps value %s." % fps, 'fps')
        # tuples (timestamp, filename)
        frames = [(i / fps, f)
                    for i, f in enumerate(selected)]

        if not frames:
            raise Exception('No frames found in dir %r.' % dirname)

        self.state.frames = frames
        self.state.next_frame = 0

    def next_data_status(self):
        k = self.state.next_frame
        if k is None:
            return (False, None)
        else:
            frames = self.state.frames
            timestamp = frames[k][0]
            return (True, timestamp)

    def update(self):
        frames = self.state.frames
        k = self.state.next_frame

        assert k < len(frames)

        timestamp, filename = frames[k]

        self.set_output(0, value=filename, timestamp=timestamp)

        if k + 1 >= len(frames):
            self.state.next_frame = None
        else:
            self.state.next_frame = k + 1
Exemple #8
0
class LaserDotDisplay(Block):
    ''' Produces a plot of a range-finder scan variation (derivative). 
    
    It is a variation of :ref:`block:laser_display`; look there for 
    the documentation.
     
    '''

    Block.alias('laser_dot_display')

    Block.config('width', 'Width of the resulting image.', default=320)
    Block.config('height', 'Height of the resulting image.', default=320)

    Block.config('groups', 'How to group and draw the readings. '
                            ' (see :ref:`block:laser_display`) ')
    Block.config('title', 'By default it displays the signal name.'
                        ' Set the empty string to disable.', default=None)
    Block.config('transparent', 'Gives transparent RGBA rather than RGB.',
                  default=False)
    Block.config('R0', 'Radius of the readings circle.', default=1)
    Block.config('amp', 'Amplitude of the readings crown.', default=0.5)

    Block.input('readings_dot', 'Array of float representing array readings.')

    Block.output('image', 'A fancy visualization of the laser derivative')

    def update(self):
        y = array(self.input.readings_dot)
        from procgraph_mpl import pylab2rgb, pylab

        if max(abs(y)) > 1:
            raise BadInput('I expect an input normalized in the [-1,1] range;'
                           'min,max: %s,%s ' % (min(y), max(y)),
                           self, 'readings_dot')

        f = pylab.figure(frameon=False,
                        figsize=(self.config.width / 100.0,
                                 self.config.height / 100.0))

        R0 = self.config.R0
        amp = self.config.amp

        theta = linspace(0, 2 * math.pi, 300)
        pylab.plot(R0 * cos(theta), R0 * sin(theta), 'k--')

        for group in self.config.groups:
            indices = group['indices']
            indices = range(indices[0], indices[-1] + 1)
            theta_spec = group['theta']
            origin = group.get('origin', [0, 0, 0])
            color = group.get('color', 'b.')

            N = len(indices)

            theta = linspace(theta_spec[0], theta_spec[-1], N)

            group_y = y[indices]

            r = R0 + amp * group_y

            px = cos(theta + origin[2]) * r
            py = sin(theta + origin[2]) * r

            pylab.plot(-py, px, color)

        M = R0 + amp * 1.2
        pylab.axis([-M, M, -M, M])

        # turn off ticks labels, they don't have meaning
        pylab.setp(f.axes[0].get_xticklabels(), visible=False)
        pylab.setp(f.axes[0].get_yticklabels(), visible=False)

        if self.config.title is not None:
            if self.config.title != "":
                pylab.title(self.config.title, fontsize=10)
        else:
            # We don't have a title ---
            t = self.get_input_signals_names()[0]
            pylab.title(t, fontsize=10)

        self.output.image = pylab2rgb(transparent=self.config.transparent)

        pylab.close(f.number)
Exemple #9
0
class Text(Block):
    ''' This block provides text overlays over an image. 
    
    This block is very powerful, but the configuration is a bit complicated.
    
    You should provide a list of dictionary in the configuration variable 
    ``texts``. Each dictionary in the list describes how and where to write
    one piece of text.
     
    An example of valid configuration is the following: ::
    
        text.texts = [{string: "raw image", position: [10,30], halign: left, 
                      color: black, bg: white }]
    
    The meaning of the fields is as follow:
    
    ``string``
      Text to display. See the section below about keyword expansion.
      
    ``position``
      Array of two integers giving the position of the text in the image
      
    ``color``
      Text color. It can be a keyword color or an hexadecimal string 
      (``white`` or ``#ffffff``).
      
    ``bg``
      background color
    
    ``halign``
      Horizontal alignment. 
      Choose between ``left`` (default), ``center``, ``right``.
      
    ``valign``
      Vertical alignment. 
      Choose between ``top`` (default), ``middle``, ``center``.
    
    ``size``
      Font size in pixels
      
    ``font``
      Font family. Must be a ttf file (``Arial.ttf``)

    **Expansion**: Also we expand macros in the text using ``format()``. 
    The available keywords are:
    
    ``frame``
      number of frames since the beginning
       
    ``time``
      seconds since the beginning of the log

    ``timestamp``
      absolute timestamp
    
    '''

    Block.alias('text')

    Block.config('texts', 'Text specification (see block description).')

    Block.input('rgb', 'Input image.')
    Block.output('rgb', 'Output image with overlaid text.')

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

    def update(self):

        # TODO: add check
        if self.state.first_timestamp is None:
            self.state.first_timestamp = self.get_input_timestamp(0)
            self.state.frame = 0
        else:
            self.state.frame += 1
        # Add stats
        macros = {}
        macros['timestamp'] = self.get_input_timestamp(0)
        if self.state.first_timestamp == ETERNITY:
            macros['time'] = -1
        else:
            macros['time'] = \
                self.get_input_timestamp(0) - self.state.first_timestamp
        macros['frame'] = self.state.frame

        rgb = self.input.rgb
        im = Image_from_array(rgb)
        from . import ImageDraw
        draw = ImageDraw.Draw(im)

        # {string: "raw image", position: [10,30], halign: left, 
        # color: black, bg: white  }
        if not isinstance(self.config.texts, list):
            raise BadConfig('Expected list', self, 'texts')

        for text in self.config.texts:
            text = text.copy()
            if not 'string' in text:
                raise BadConfig('Missing field "string" in text spec %s.' % 
                                text.__repr__(), self, 'texts')

            try:
                text['string'] = Text.replace(text['string'], macros)
            except KeyError as e:
                msg = str(e) + '\nAvailable: %s.' % macros.keys()
                raise BadConfig(msg, self, 'texts')

            p = text['position']
            
            try:
                p[0] = get_ver_pos_value(p[0], height=rgb.shape[0])
                p[1] = get_hor_pos_value(p[1], width=rgb.shape[1])
            except ValueError as e:
                raise BadConfig(str(e), text.__repr__(), self, 'texts')
            
            process_text(draw, text)

        out = im.convert("RGB")
        pixel_data = numpy.asarray(out)

        self.output.rgb = pixel_data

    @staticmethod
    def replace(s, macros):
        ''' Expand macros in the text. '''
        return s.format(**macros)