Example #1
0
class LogSim(Generator):
    Block.alias('log_sim')

    Block.output('stream')

    def init(self):
        line = self.config.line

        self.queue = []
        for i, char in enumerate(line):
            t = i + 1
            if char == ' ':
                continue
            elif char == '*':
                self.queue.insert(0, (t, -1))
            elif char.isdigit():
                v = int(char)
                self.queue.insert(0, (t, v))
            else:
                raise Exception('Bad input "%s" at char %d of "%s".' % \
                                (char, t, line))

    def next_data_status(self):
        if self.queue:
            return (True, self.queue[-1][0])
        else:
            return (False, None)

    def update(self):
        assert self.queue
        timestamp, value = self.queue.pop()
        self.set_output(0, value, timestamp)
Example #2
0
class vel_from_SE2_seq(Block):
    ''' Computes velocity in se2 represented as vx,vy,omega
        from a sequence of poses in SE2. '''
    Block.alias('vel_from_SE2_seq')

    Block.input('pose', 'Pose as an element of SE2', dtype=SE2)
    Block.output('velocity',
                 'Velocity as vx,vy,omega.',
                 dtype=np.dtype(('float', 3)))

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

    def update(self):
        q2 = self.get_input(0)
        t2 = self.get_input_timestamp(0)

        if self.state.prev is not None:
            t1, q1 = self.state.prev
            vel = velocity_from_poses(t1, q1, t2, q2, S=SE2)

            # Convertion from se2 to R3
            v, omega = linear_angular_from_se2(vel)
            out = np.array([v[0], v[1], omega])

            self.set_output(0, out, timestamp=t2)

        self.state.prev = t2, q2
Example #3
0
class JitteryDisplay(Block):
    Block.alias('jittery_display')

    Block.input('clock')
    Block.output('rgb')
    
    def init(self):
        self.plot_generic = PlotGeneric(width=320,
                                        height=240,
                                        transparent=False,
                                        tight=False,
                                        keep=True)
        self.first_timestamp = None
        self.plot_anim = PlotAnim()
        self.nframes = 0
        
    def update(self):
        if self.first_timestamp is None:
            self.first_timestamp = self.get_input_timestamp(0)
        self.time_since_start = self.get_input_timestamp(0) - self.first_timestamp
        self.nframes += 1
        self.output.rgb = self.plot_generic.get_rgb(self.plot)
        
        
    def plot(self, pylab):
        self.plot_anim.set_pylab(pylab)
        self.plot_anim.text('clock', 0, 1, '%5.2f' % self.time_since_start)
        self.plot_anim.text('frames', 0, 0.5, '%d' % self.nframes)

        self.plot_anim.text('value', 0, 0.24, self.input.clock)
        pylab.axis((-0.2, 1.1, -0.1, 1.1))
Example #4
0
class ExpectationNorm(Block):
    Block.alias('expectation_norm')

    Block.input('x', 'Any numpy array.')
    Block.output('Ex', 'Expectation of input.')

    def init(self):
        self.value = None
        self.mass = None
        
    def update(self):
        x = self.input.x
        if self.value is None:
            self.value = x
            self.weight = np.zeros(x.shape)
        else:
            if False:
                w = np.abs(x - self.last_x)
            else:
                w = np.abs(x - self.last_x).sum()
            self.weight += w
            self.value += w * x
            
            self.weight[self.weight == 0] = np.inf
            res = self.value / self.weight 
            self.output.Ex = res
        self.last_x = x.copy()
Example #5
0
class AERTrackerLogReader(TextLog):
    Block.alias('aer_tracker_log')
    Block.config('file')
    Block.output('track')
        
    def init(self):
        self.last = -1
        self.buffer = []
        TextLog.init(self)
        
    def parse_format(self, line):
        # return none if we want more data
        x = aer_track_parse_line(line)
        if x is None:
            return None
        
        self.buffer.append(x)
        # If it was the last in the sequence:
        if x['peak'] == x['npeaks'] - 1:
            res = np.array(self.buffer, self.buffer[0].dtype)
            self.buffer = []
            
            t = res[0]['timestamp']
            if t <= self.last:
                t = self.last + 0.0000001
            self.last = t
            
            return t, [(0, res)]
Example #6
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 #7
0
class TimeSlice(Block):
    ''' 
    This block collects the history of a quantity for a given
    interval length, and it outputs a list of values when the 
    buffer is full. Then it resets the buffer.
    
    See also :ref:`block:historyt` and :ref:`block:last_n_samples`.
    '''
    Block.alias('time_slice')

    Block.config('interval', 'Length of the interval to record.', default=10)

    Block.input('values', 'Any signal.')

    Block.output('grouped', 'List of the values in a given interval.')
    
    def init(self):
        self.x = []
        self.t = []

    def update(self):
        sample = self.get_input(0)
        timestamp = self.get_input_timestamp(0)
        
        self.x.append(sample)
        self.t.append(timestamp)

        delta = self.t[-1] - self.t[0]
#        self.info('Delta: %.10f sec  n = %6d' % (delta, len(self.t)))
        if np.abs(delta) >= self.config.interval:
            self.set_output(0, value=self.x, timestamp=self.t[0])
            self.x = []
            self.t = []
Example #8
0
class LowPass(Block):
    ''' Implements simple low-pass filtering. 
    
    Formula used: ::
    
        y[k] = alpha * u[k] + (1-alpha) * y[k-1]
    
    '''
    # TODO: make a serious low-pass block
    Block.alias('low_pass')

    Block.config('alpha', 'Innovation rate')

    Block.input('value', 'Any numpy array.')
    Block.output('lowpass', 'The lowpass version.')

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

    def update(self):
        u = self.input[0]
        alpha = self.config.alpha

        if self.state.y is None:
            self.state.y = u
        else:
            self.state.y = self.state.y * (1 - alpha) + alpha * u

        self.output[0] = self.state.y
Example #9
0
class DPDDSPredict(Block):
    Block.alias('dp_discdds_predict')

    Block.config('id_discdds')
    Block.config('plan')
    Block.config('config_dir', default=[])

    Block.input('rgb')
    Block.output('prediction')

    def init(self):
        id_discdds = self.config.id_discdds

        dp_config = get_dp_config()

        dp_config.load(self.config.config_dir)
        self.discdds = dp_config.discdds.instance(id_discdds)

        plan = self.config.plan
        self.action = self.discdds.plan2action(plan)

    def update(self):
        rgb0 = self.input.rgb
        H, W = self.discdds.get_shape()
        rgb = resize(rgb0, width=W, height=H)

        y0 = UncertainImage(rgb)
        y1 = self.action.predict(y0)
        pred = y1.get_rgba_fill()

        pred2 = resize(pred, height=rgb0.shape[0], width=rgb0.shape[1])
        self.output.prediction = pred2[:, :, :3]
Example #10
0
class AERAltPlotter(Block):
    Block.alias('aer_alt_plotter')
    Block.config('width', 'Image dimension', default=128)
    Block.input('alts')
    Block.output('rgb')

    def init(self):
        self.plot_generic = PlotGeneric(width=self.config.width,
                                        height=self.config.width,
                                        transparent=False,
                                        tight=False)
        self.max_q = 0

    def update(self):
        self.output.rgb = self.plot_generic.get_rgb(self.plot)

    def plot(self, pylab):
        alts = self.input.alts

        markers = ['s', 'o', 'x']
        quality = ['%f' % x.score for x in alts]
        for i, alt in enumerate(alts):
            subset = alt.subset
            # get the last ones
            tracks = get_last(subset)
            marker = markers[i % len(markers)]
            plot_tracks(pylab, tracks, base_markersize=10, marker=marker)
            pylab.text(10, 10, quality)
Example #11
0
class Border(Block):
    ''' Adds a block around the input image. '''
    Block.alias('border')

    Block.input('rgb', 'Input image.')
    Block.output('rgb', 'Image with borders added around.')
    Block.config('color', 'border color (0-1 rgb)', default=[0, 0, 0])
    Block.config('width', default=1)
    Block.config('left', 'pixel length for left border', default=None)
    Block.config('right', 'pixel length for right border', default=None)
    Block.config('top', 'pixel length for top border', default=None)
    Block.config('bottom', 'pixel length for bottom border', default=None)

    def update(self):
        check_rgb(self, 'rgb')

        def df(x):
            if x is None:
                return self.config.width
            else:
                return x

        # TODO: check color
        self.output.rgb = image_border(self.input.rgb,
                                       left=df(self.config.left),
                                       right=df(self.config.right),
                                       top=df(self.config.top),
                                       bottom=df(self.config.bottom),
                                       color=df(self.config.color))
Example #12
0
class Bounce(Block):

    Block.alias('bounce')

    Block.config('width', 'Image dimension', default=320)
    Block.config('height', 'Image dimension', default=240)
    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.input('tick')
    Block.output('rgb')

    def init(self):
        self.plot_generic = PlotGeneric(width=self.config.width,
                                        height=self.config.height,
                                        transparent=self.config.transparent,
                                        tight=self.config.tight)

    def update(self):
        self.output.rgb = self.plot_generic.get_rgb(self.plot)

    def plot(self, pylab):
        t = self.get_input_timestamp(0)
        t0 = t
        t1 = t + 2
        x = np.linspace(t0, t1, 1000)
        y = np.cos(x)
        pylab.plot(x, y)
        pylab.axis((t0, t1, -1.2, +1.2))
Example #13
0
class ForwardDifference12(Block):
    ''' Computes ``x[t+1] - x[t]`` normalized with timestamp. '''

    Block.alias('two_step_difference')

    Block.input('x12', 'An array with the last 2 values of x.')
    Block.input('t12', 'An array with the last 2 values of the timestamp.')

    Block.output('x_dot', 'Derivative of x')

    def update(self):
        x = self.input.x12
        t = self.input.t12
        if not isiterable(x) or len(x) != 2:
            raise BadInput('Expected arrays of 2 elements', self, 'x')
        if not isiterable(t) or len(t) != 2:
            raise BadInput('Expected arrays of 2 elements', self, 't')

        delta = t[1] - t[0]

        if not delta > 0:
            raise BadInput('Bad timestamp sequence % s' % t, self, 't')

        # if this is a sequence of bytes, let's promove them to floats
        if x[0].dtype == numpy.dtype('uint8'):
            diff = x[1].astype('float32') - x[0].astype('float32')
        else:
            diff = x[1] - x[0]
        time = t[0]
        x_dot = diff / numpy.float32(delta)
        self.set_output('x_dot', x_dot, timestamp=time)
Example #14
0
class Sieve(Block):
    ''' 
        This block decimates the data in time by transmitting
        only one in ``n`` updates.
    '''

    Block.alias('sieve')

    Block.config('n',
                 'Decimation level; ``n = 3`` means transmit one in three.')

    Block.input('data', 'Arbitrary input signals.')
    Block.output('decimated', 'Decimated signals.')

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

    def update(self):
        # make something happen after we have waited enough
        if 0 == self.state.count % self.config.n:
            # Just copy the input to the output
            # XXX: using only one signal?
            for i in range(self.num_input_signals()):
                self.set_output(i, self.get_input(i),
                                self.get_input_timestamp(i))

        self.state.count += 1
Example #15
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 #16
0
class History(Block):
    ''' 
    This block collects the history of a quantity,
    and outputs two signals ``x`` and ``t``. 
    See also :ref:`block:historyt` and :ref:`block:last_n_samples`.
    '''
    Block.alias('history')

    Block.config('interval', 'Length of the interval to record.', default=10)

    Block.input('values', 'Any signal.')

    Block.output('x', 'Sequence of values.')
    Block.output('t', 'Sequence of timestamps.')

    def init(self):
        self.history = HistoryInterval(self.config.interval)

    def update(self):
        sample = self.get_input(0)
        timestamp = self.get_input_timestamp(0)

        self.history.push(timestamp, sample)
        ts, xs = self.history.get_ts_xs()
        self.output.x = xs
        self.output.t = ts
Example #17
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 #18
0
class Pose2velocity(Block):
    ''' Block used by :ref:`block:pose2commands`. '''
    Block.alias('pose2vel_')

    Block.input('q12', 'Last two poses.')
    Block.input('t12', 'Last two timestamps.')

    Block.output('commands', 'Estimated commands ``[vx,vy,omega]``.')

    def update(self):
        q = self.get_input('q12')
        t = self.get_input('t12')
        if not (len(q) == 2 and len(t) == 2):
            raise BadInput('Bad input received.', self)

        pose1 = g.SE2_from_xytheta(q[0])
        pose2 = g.SE2_from_xytheta(q[1])
        delta = t[1] - t[0]

        if not delta > 0:
            raise BadInput('Bad timestamp sequence %s' % t, self, 't')

        _, vel = g.SE2.velocity_from_points(pose1, pose2, delta)

        linear, angular = g.linear_angular_from_se2(vel)

        commands = [linear[0], linear[1], angular]
        self.set_output('commands', commands, timestamp=t[0])
Example #19
0
class AERTrackPlotter(Block):
    Block.alias('aer_track_plotter')
    Block.config('width', 'Image dimension', default=128)
    Block.input('tracks')
    Block.output('rgb')

    def init(self):
        self.plot_generic = PlotGeneric(width=self.config.width,
                                        height=self.config.width,
                                        transparent=False,
                                        tight=False)

    def update(self):
        self.output.rgb = self.plot_generic.get_rgb(self.plot)

    def plot(self, pylab):
        tracks = self.input.tracks

        plot_tracks(pylab, tracks, base_markersize=10, alpha=0.5)

        T = self.get_input_timestamp(0)
        pylab.title('Raw detections')
        time = 'T = %.1f ms' % (T * 1000)
        pylab.text(3, 3, time)
        set_viewport_style(pylab)
Example #20
0
class AERPF(Block):
    """ Simple particle filter """

    Block.alias('aer_pf')

    Block.input('track_log')
    Block.output('particles', 'All particles')
    Block.output('hps', 'A list of coherent hypotheses')

    Block.config('min_track_dist', 'Minimum distance between tracks')
    Block.config('max_vel', 'Maximum velocity')
    Block.config('max_bound', 'Largest size of the uncertainty')
    Block.config('max_hp', 'Maximum number of hypotheses to produce.')

    def init(self):
        params = dict(max_vel=self.config.max_vel,
                      min_track_dist=self.config.min_track_dist,
                      max_bound=self.config.max_bound)
        self.pdm = ParticleTrackerMultiple(**params)

    def update(self):
        tracks = self.input.track_log

        self.pdm.add_observations(tracks)

        particles = self.pdm.get_all_particles()
        if len(particles) > 0:
            self.output.particles = particles

            max_hp = self.config.max_hp
            hps = self.pdm.get_coherent_hypotheses(max_hp)
            self.output.hps = hps
Example #21
0
class CVCapture(Generator):

    Block.alias('cv_capture')
    Block.config('cam', default=0)
    Block.config('width', default=160)
    Block.config('height', default=120)
    Block.config('fps', default=10)
    
    Block.output('rgb')
    
    def init(self):
        cam = self.config.cam
        width = self.config.width
        height = self.config.height
        fps = self.config.fps
        
        self.info('Capturing cam=%d %dx%d @%.1f fps' % (cam, width, height, fps))
        self.capture = cv.CaptureFromCAM(cam)
        cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_FRAME_WIDTH, width)
        cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_FRAME_HEIGHT, height)
        cv.SetCaptureProperty(self.capture, cv.CV_CAP_PROP_FPS, fps)
    
    def update(self):
        t = time.time()
        img = cv.QueryFrame(self.capture)
        rgb = cv_to_numpy(img)
        self.set_output('rgb', value=rgb, timestamp=t)
    
    def next_data_status(self):
        return (True, None)
Example #22
0
class ForwardDifference(Block):
    ''' Computes ``x[t+1] - x[t-1]`` normalized with timestamp. 
    
        You want to attach this to :ref:`block:last_n_samples`.
    '''
    Block.alias('forward_difference')
    
    Block.input('x123', 'An array with the last 3 values of x.')
    Block.input('t123', 'An array with the last 3 values of the timestamp.')
    
    Block.output('x_dot', 'Derivative of x')
        
    def update(self):
        x = self.input.x123
        t = self.input.t123
        if not isiterable(x) or len(x) != 3:
            raise BadInput('Expected arrays of 3 elements', self, 'x')
        if not isiterable(t) or len(t) != 3:
            raise BadInput('Expected arrays of 3 elements', self, 't')
       
        delta = t[2] - t[0]
        
        if not delta > 0:
            raise BadInput('Bad timestamp sequence % s' % t, self, 't')

        # if this is a sequence of bytes, let's promove them to floats
        if x[0].dtype == numpy.dtype('uint8'):
            diff = x[2].astype('float32') - x[0].astype('float32')
        else:
            diff = x[2] - x[0]
        time = t[1]
        x_dot = diff / numpy.float32(delta)
        self.set_output('x_dot', x_dot, timestamp=time)   
Example #23
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 #24
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 #25
0
class PickleLoad(Block):
    ''' Dumps the input as a :py:mod:`pickle` file. '''
    Block.alias('pickle_load')
    Block.config('file', 'File to read from.')
    Block.output('x', 'Object read from file')

    def update(self):
        with open(self.config.file, 'rb') as f:
            x = pickle.load(f)
            self.set_output(0, x, timestamp=ETERNITY)
Example #26
0
class Gain(Block):
    ''' A simple example of a gain block. '''

    Block.alias('gain')

    Block.config('k', 'Multiplicative gain')

    Block.input('in', 'Input value')
    Block.output('out', 'Output multiplied by k.')

    def update(self):
        self.output[0] = self.input[0] * self.config.k
Example #27
0
class TransparentImageAverageBlock(Block):
    Block.alias('trans_avg')
    Block.input('rgba')
#     Block.output('rgb')
    Block.output('rgba')
    
    def init(self):
        self.tia = TransparentImageAverage()
        
    def update(self):
        self.tia.update(self.input.rgba)
        # self.output.rgb = self.tia.get_rgb()
        self.output.rgba = self.tia.get_rgba()
Example #28
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 #29
0
class AERSmoother(Block):
    Block.alias('aer_smoother')
    Block.config('ntracks')
    Block.input('track_log')
    Block.output('tracks')

    def init(self):
        self.smoother = Smoother(self.config.ntracks)

    def update(self):
        self.smoother.push(self.input.track_log)
        if self.smoother.is_complete():
            self.set_output('tracks', self.smoother.get_values())
Example #30
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)