rosbag_main.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2010, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 from __future__ import print_function
00034 
00035 import optparse
00036 import os
00037 import shutil
00038 import signal
00039 import subprocess
00040 import sys
00041 import time
00042 try:
00043     from UserDict import UserDict  # Python 2.x
00044 except ImportError:
00045     from collections import UserDict  # Python 3.x
00046 
00047 import roslib.message
00048 import roslib.packages
00049 
00050 from .bag import Bag, Compression, ROSBagException, ROSBagFormatException, ROSBagUnindexedException
00051 from .migration import MessageMigrator, fixbag2, checkbag
00052 
00053 def print_trans(old, new, indent):
00054     from_txt = '%s [%s]' % (old._type, old._md5sum)
00055     if new is not None:
00056         to_txt= '%s [%s]' % (new._type, new._md5sum)
00057     else:
00058         to_txt = 'Unknown'
00059     print('    ' * indent + ' * From: %s' % from_txt)
00060     print('    ' * indent + '   To:   %s' % to_txt)
00061 
00062 def handle_split(option, opt_str, value, parser):
00063     parser.values.split = True
00064     if len(parser.rargs) > 0 and parser.rargs[0].isdigit():
00065         print("Use of \"--split <MAX_SIZE>\" has been deprecated.  Please use --split --size <MAX_SIZE> or --split --duration <MAX_DURATION>", file=sys.stderr)
00066         parser.values.size = int(parser.rargs.pop(0))
00067 
00068 def record_cmd(argv):
00069     parser = optparse.OptionParser(usage="rosbag record TOPIC1 [TOPIC2 TOPIC3 ...]",
00070                                    description="Record a bag file with the contents of specified topics.",
00071                                    formatter=optparse.IndentedHelpFormatter())
00072 
00073     parser.add_option("-a", "--all",           dest="all",           default=False, action="store_true",          help="record all topics")
00074     parser.add_option("-e", "--regex",         dest="regex",         default=False, action="store_true",          help="match topics using regular expressions")
00075     parser.add_option("-x", "--exclude",       dest="exclude_regex", default="",    action="store",               help="exclude topics matching the follow regular expression (subtracts from -a or regex)")
00076     parser.add_option("-q", "--quiet",         dest="quiet",         default=False, action="store_true",          help="suppress console output")
00077     parser.add_option("-o", "--output-prefix", dest="prefix",        default=None,  action="store",               help="prepend PREFIX to beginning of bag name (name will always end with date stamp)")
00078     parser.add_option("-O", "--output-name",   dest="name",          default=None,  action="store",               help="record to bag with name NAME.bag")
00079     parser.add_option(      "--split",         dest="split",         default=False, callback=handle_split, action="callback",    help="split the bag when maximum size or duariton is reached")
00080     parser.add_option(      "--size",          dest="size",                         type='int',   action="store", help="record a bag of maximum size SIZE", metavar="SIZE")
00081     parser.add_option(      "--duration",      dest="duration",                     type='string',action="store", help="record a bag of maximum duration DURATION in seconds, unless 'm', or 'h' is appended.", metavar="DURATION")
00082     parser.add_option("-b", "--buffsize",      dest="buffsize",      default=256,   type='int',   action="store", help="use an internal buffer of SIZE MB (Default: %default, 0 = infinite)", metavar="SIZE")
00083     parser.add_option("--chunksize",           dest="chunksize",     default=768,   type='int',   action="store", help="Advanced. Record to chunks of SIZE KB (Default: %default)", metavar="SIZE")
00084     parser.add_option("-l", "--limit",         dest="num",           default=0,     type='int',   action="store", help="only record NUM messages on each topic")
00085     parser.add_option(      "--node",          dest="node",          default=None,  type='string',action="store", help="record all topics subscribed to by a specific node")
00086     parser.add_option("-j", "--bz2",           dest="bz2",           default=False, action="store_true",          help="use BZ2 compression")
00087 
00088     (options, args) = parser.parse_args(argv)
00089 
00090     if len(args) == 0 and not options.all and not options.node:
00091         parser.error("You must specify a topic name or else use the '-a' option.")
00092 
00093     if options.prefix is not None and options.name is not None:
00094         parser.error("Can't set both prefix and name.")
00095 
00096     recordpath = roslib.packages.find_node('rosbag', 'record')
00097     if not recordpath:
00098         parser.error("Cannot find rosbag/record executable")
00099     cmd = [recordpath[0]]
00100 
00101     cmd.extend(['--buffsize',  str(options.buffsize)])
00102     cmd.extend(['--chunksize', str(options.chunksize)])
00103 
00104     if options.num != 0:      cmd.extend(['--limit', str(options.num)])
00105     if options.quiet:         cmd.extend(["--quiet"])
00106     if options.prefix:        cmd.extend(["-o", options.prefix])
00107     if options.name:          cmd.extend(["-O", options.name])
00108     if options.exclude_regex: cmd.extend(["--exclude", options.exclude_regex])
00109     if options.all:           cmd.extend(["--all"])
00110     if options.regex:         cmd.extend(["--regex"])
00111     if options.bz2:           cmd.extend(["--bz2"])
00112     if options.split:
00113         if not options.duration and not options.size:
00114             parser.error("Split specified without giving a maximum duration or size")
00115         cmd.extend(["--split"])
00116     if options.duration:    cmd.extend(["--duration", options.duration])
00117     if options.size:        cmd.extend(["--size", str(options.size)])
00118     if options.node:
00119         cmd.extend(["--node", options.node])
00120 
00121     cmd.extend(args)
00122 
00123     # Better way of handling it than os.execv
00124     # This makes sure stdin handles are passed to the process.
00125     subprocess.call(cmd)
00126 
00127 def info_cmd(argv):
00128     parser = optparse.OptionParser(usage='rosbag info [options] BAGFILE1 [BAGFILE2 BAGFILE3 ...]',
00129                                    description='Summarize the contents of one or more bag files.')
00130     parser.add_option('-y', '--yaml', dest='yaml', default=False, action='store_true', help='print information in YAML format')
00131     parser.add_option('-k', '--key',  dest='key',  default=None,  action='store',      help='print information on the given key')
00132     parser.add_option(      '--freq', dest='freq', default=False, action='store_true', help='display topic message frequency statistics')
00133     (options, args) = parser.parse_args(argv)
00134 
00135     if len(args) == 0:
00136         parser.error('You must specify at least 1 bag file.')
00137     if options.key and not options.yaml:
00138         parser.error('You can only specify key when printing in YAML format.')
00139 
00140     for i, arg in enumerate(args):
00141         try:
00142             b = Bag(arg, 'r', skip_index=not options.freq)
00143             if options.yaml:
00144                 info = b._get_yaml_info(key=options.key)
00145                 if info is not None:
00146                     print(info)
00147             else:
00148                 print(b)
00149             b.close()
00150             if i < len(args) - 1:
00151                 print('---')
00152         
00153         except ROSBagUnindexedException as ex:
00154             print('ERROR bag unindexed: %s.  Run rosbag reindex.' % arg,
00155                   file=sys.stderr)
00156         except ROSBagException as ex:
00157             print('ERROR reading %s: %s' % (arg, str(ex)), file=sys.stderr)
00158         except IOError as ex:
00159             print('ERROR reading %s: %s' % (arg, str(ex)), file=sys.stderr)
00160 
00161 
00162 def handle_topics(option, opt_str, value, parser):
00163     topics = []
00164     for arg in parser.rargs:
00165         if arg[:2] == "--" and len(arg) > 2:
00166             break
00167         if arg[:1] == "-" and len(arg) > 1:
00168             break
00169         topics.append(arg)
00170     parser.values.topics.extend(topics)
00171     del parser.rargs[:len(topics)]
00172 
00173 
00174 def play_cmd(argv):
00175     parser = optparse.OptionParser(usage="rosbag play BAGFILE1 [BAGFILE2 BAGFILE3 ...]",
00176                                    description="Play back the contents of one or more bag files in a time-synchronized fashion.")
00177     parser.add_option("-q", "--quiet",        dest="quiet",      default=False, action="store_true", help="suppress console output")
00178     parser.add_option("-i", "--immediate",    dest="immediate",  default=False, action="store_true", help="play back all messages without waiting")
00179     parser.add_option("--pause",              dest="pause",      default=False, action="store_true", help="start in paused mode")
00180     parser.add_option("--queue",              dest="queue",      default=100,     type='int', action="store", help="use an outgoing queue of size SIZE (defaults to %default)", metavar="SIZE")
00181     parser.add_option("--clock",              dest="clock",      default=False, action="store_true", help="publish the clock time")
00182     parser.add_option("--hz",                 dest="freq",       default=100,   type='float', action="store", help="use a frequency of HZ when publishing clock time (default: %default)", metavar="HZ")
00183     parser.add_option("-d", "--delay",        dest="delay",      default=0.2,   type='float', action="store", help="sleep SEC seconds after every advertise call (to allow subscribers to connect)", metavar="SEC")
00184     parser.add_option("-r", "--rate",         dest="rate",       default=1.0,   type='float', action="store", help="multiply the publish rate by FACTOR", metavar="FACTOR")
00185     parser.add_option("-s", "--start",        dest="start",      default=0.0,   type='float', action="store", help="start SEC seconds into the bag files", metavar="SEC")
00186     parser.add_option("-u", "--duration",     dest="duration",   default=None,  type='float', action="store", help="play only SEC seconds from the bag files", metavar="SEC")
00187     parser.add_option("--skip-empty",         dest="skip_empty", default=None,  type='float', action="store", help="skip regions in the bag with no messages for more than SEC seconds", metavar="SEC")
00188     parser.add_option("-l", "--loop",         dest="loop",       default=False, action="store_true", help="loop playback")
00189     parser.add_option("-k", "--keep-alive",   dest="keep_alive", default=False, action="store_true", help="keep alive past end of bag (useful for publishing latched topics)")
00190     parser.add_option("--try-future-version", dest="try_future", default=False, action="store_true", help="still try to open a bag file, even if the version number is not known to the player")
00191     parser.add_option("--topics", dest="topics", default=[],  callback=handle_topics, action="callback", help="topics to play back")
00192     parser.add_option("--bags",  help="bags files to play back from")
00193 
00194     (options, args) = parser.parse_args(argv)
00195 
00196     if len(args) == 0:
00197         parser.error('You must specify at least 1 bag file to play back.')
00198 
00199     playpath = roslib.packages.find_node('rosbag', 'play')
00200     if not playpath:
00201         parser.error("Cannot find rosbag/play executable")
00202     cmd = [playpath[0]]
00203 
00204     if options.quiet:      cmd.extend(["--quiet"])
00205     if options.pause:      cmd.extend(["--pause"])
00206     if options.immediate:  cmd.extend(["--immediate"])
00207     if options.loop:       cmd.extend(["--loop"])
00208     if options.keep_alive: cmd.extend(["--keep-alive"])
00209     if options.try_future: cmd.extend(["--try-future-version"])
00210 
00211     if options.clock:
00212         cmd.extend(["--clock", "--hz", str(options.freq)])
00213 
00214     cmd.extend(['--queue', str(options.queue)])
00215     cmd.extend(['--rate', str(options.rate)])
00216     cmd.extend(['--delay', str(options.delay)])
00217     cmd.extend(['--start', str(options.start)])
00218     if options.duration:
00219         cmd.extend(['--duration', str(options.duration)])
00220     if options.skip_empty:
00221         cmd.extend(['--skip-empty', str(options.skip_empty)])
00222 
00223     if options.topics:
00224         cmd.extend(['--topics'] + options.topics + ['--bags'])
00225 
00226     cmd.extend(args)
00227     # Better way of handling it than os.execv
00228     # This makes sure stdin handles are passed to the process.
00229     subprocess.call(cmd)
00230 
00231 def filter_cmd(argv):
00232     def expr_eval(expr):
00233         def eval_fn(topic, m, t):
00234             return eval(expr)
00235         return eval_fn
00236 
00237     parser = optparse.OptionParser(usage="""rosbag filter [options] INBAG OUTBAG EXPRESSION
00238 
00239 EXPRESSION can be any Python-legal expression.
00240 
00241 The following variables are available:
00242  * topic: name of topic
00243  * m: message
00244  * t: time of message (t.secs, t.nsecs)""",
00245                                    description='Filter the contents of the bag.')
00246     parser.add_option('-p', '--print', action='store', dest='verbose_pattern', default=None, metavar='PRINT-EXPRESSION', help='Python expression to print for verbose debugging. Uses same variables as filter-expression')
00247 
00248     options, args = parser.parse_args(argv)
00249     if len(args) == 0:
00250         parser.error('You must specify an in bag, an out bag, and an expression.')
00251     if len(args) == 1:
00252         parser.error('You must specify an out bag and an expression.')
00253     if len(args) == 2:
00254         parser.error("You must specify an expression.")
00255     if len(args) > 3:
00256         parser.error("Too many arguments.")
00257 
00258     inbag_filename, outbag_filename, expr = args
00259 
00260     if not os.path.isfile(inbag_filename):
00261         print('Cannot locate input bag file [%s]' % inbag_filename, file=sys.stderr)
00262         sys.exit(2)
00263 
00264     if os.path.realpath(inbag_filename) == os.path.realpath(outbag_filename):
00265         print('Cannot use same file as input and output [%s]' % inbag_filename, file=sys.stderr)
00266         sys.exit(3)
00267 
00268     filter_fn = expr_eval(expr)
00269 
00270     outbag = Bag(outbag_filename, 'w')
00271     
00272     try:
00273         inbag = Bag(inbag_filename)
00274     except ROSBagUnindexedException as ex:
00275         print('ERROR bag unindexed: %s.  Run rosbag reindex.' % inbag_filename, file=sys.stderr)
00276         return
00277 
00278     try:
00279         meter = ProgressMeter(outbag_filename, inbag.size)
00280         total_bytes = 0
00281     
00282         if options.verbose_pattern:
00283             verbose_pattern = expr_eval(options.verbose_pattern)
00284     
00285             for topic, raw_msg, t in inbag.read_messages(raw=True):
00286                 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg
00287                 msg = pytype()
00288                 msg.deserialize(serialized_bytes)
00289 
00290                 if filter_fn(topic, msg, t):
00291                     print('MATCH', verbose_pattern(topic, msg, t))
00292                     outbag.write(topic, msg, t)
00293                 else:
00294                     print('NO MATCH', verbose_pattern(topic, msg, t))          
00295 
00296                 total_bytes += len(serialized_bytes) 
00297                 meter.step(total_bytes)
00298         else:
00299             for topic, raw_msg, t in inbag.read_messages(raw=True):
00300                 msg_type, serialized_bytes, md5sum, pos, pytype = raw_msg
00301                 msg = pytype()
00302                 msg.deserialize(serialized_bytes)
00303 
00304                 if filter_fn(topic, msg, t):
00305                     outbag.write(topic, msg, t)
00306 
00307                 total_bytes += len(serialized_bytes)
00308                 meter.step(total_bytes)
00309         
00310         meter.finish()
00311 
00312     finally:
00313         inbag.close()
00314         outbag.close()
00315 
00316 def fix_cmd(argv):
00317     parser = optparse.OptionParser(usage='rosbag fix INBAG OUTBAG [EXTRARULES1 EXTRARULES2 ...]', description='Repair the messages in a bag file so that it can be played in the current system.')
00318     parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins', help='do not load rulefiles via plugins')
00319     parser.add_option('--force', action='store_true', dest='force', help='proceed with migrations, even if not all rules defined')
00320 
00321     (options, args) = parser.parse_args(argv)
00322 
00323     if len(args) < 1:
00324         parser.error('You must pass input and output bag files.')
00325     if len(args) < 2:
00326         parser.error('You must pass an output bag file.')
00327 
00328     inbag_filename  = args[0]
00329     outbag_filename = args[1]
00330     rules           = args[2:]   
00331 
00332     ext = os.path.splitext(outbag_filename)[1]
00333     if ext == '.bmr':
00334         parser.error('Input file should be a bag file, not a rule file.')
00335     if ext != '.bag':
00336         parser.error('Output file must be a bag file.')
00337 
00338     outname = outbag_filename + '.tmp'
00339 
00340     if os.path.exists(outbag_filename):
00341         if not os.access(outbag_filename, os.W_OK):
00342             print('Don\'t have permissions to access %s' % outbag_filename, file=sys.stderr)
00343             sys.exit(1)
00344     else:
00345         try:
00346             file = open(outbag_filename, 'w')
00347             file.close()
00348         except IOError as e:
00349             print('Cannot open %s for writing' % outbag_filename, file=sys.stderr)
00350             sys.exit(1)
00351 
00352     if os.path.exists(outname):
00353         if not os.access(outname, os.W_OK):
00354             print('Don\'t have permissions to access %s' % outname, file=sys.stderr)
00355             sys.exit(1)
00356     else:
00357         try:
00358             file = open(outname, 'w')
00359             file.close()
00360         except IOError as e:
00361             print('Cannot open %s for writing' % outname, file=sys.stderr)
00362             sys.exit(1)
00363 
00364     if options.noplugins is None:
00365         options.noplugins = False
00366 
00367     migrator = MessageMigrator(rules, plugins=not options.noplugins)
00368     
00369     try:
00370         migrations = fixbag2(migrator, inbag_filename, outname, options.force)
00371     except ROSBagUnindexedException as ex:
00372         print('ERROR bag unindexed: %s.  Run rosbag reindex.' % inbag_filename,
00373               file=sys.stderr)
00374         return
00375 
00376     if len(migrations) == 0:
00377         os.rename(outname, outbag_filename)
00378         print('Bag migrated successfully.')
00379     else:
00380         print('Bag could not be migrated.  The following migrations could not be performed:')
00381         for m in migrations:
00382             print_trans(m[0][0].old_class, m[0][-1].new_class, 0)
00383             
00384             if len(m[1]) > 0:
00385                 print('    %d rules missing:' % len(m[1]))
00386                 for r in m[1]:
00387                     print_trans(r.old_class, r.new_class,1)
00388                     
00389         print('Try running \'rosbag check\' to create the necessary rule files or run \'rosbag fix\' with the \'--force\' option.')
00390         os.remove(outname)
00391 
00392 def check_cmd(argv):
00393     parser = optparse.OptionParser(usage='rosbag check BAG [-g RULEFILE] [EXTRARULES1 EXTRARULES2 ...]', description='Determine whether a bag is playable in the current system, or if it can be migrated.')
00394     parser.add_option('-g', '--genrules',  action='store',      dest='rulefile', default=None, help='generate a rulefile named RULEFILE')
00395     parser.add_option('-a', '--append',    action='store_true', dest='append',                 help='append to the end of an existing rulefile after loading it')
00396     parser.add_option('-n', '--noplugins', action='store_true', dest='noplugins',              help='do not load rulefiles via plugins')
00397     (options, args) = parser.parse_args(argv)
00398 
00399     if len(args) == 0:
00400         parser.error('You must specify a bag file to check.')
00401     if options.append and options.rulefile is None:
00402         parser.error('Cannot specify -a without also specifying -g.')
00403     if options.rulefile is not None:
00404         rulefile_exists = os.path.isfile(options.rulefile)
00405         if rulefile_exists and not options.append:
00406             parser.error('The file %s already exists.  Include -a if you intend to append.' % options.rulefile)
00407         if not rulefile_exists and options.append:
00408             parser.error('The file %s does not exist, and so -a is invalid.' % options.rulefile)
00409     
00410     if options.append:
00411         append_rule = [options.rulefile]
00412     else:
00413         append_rule = []
00414 
00415     # First check that the bag is not unindexed 
00416     try:
00417         Bag(args[0])
00418     except ROSBagUnindexedException as ex:
00419         print('ERROR bag unindexed: %s.  Run rosbag reindex.' % args[0], file=sys.stderr)
00420         return
00421 
00422     mm = MessageMigrator(args[1:] + append_rule, not options.noplugins)
00423 
00424     migrations = checkbag(mm, args[0])
00425        
00426     if len(migrations) == 0:
00427         print('Bag file is up to date.')
00428         exit(0)
00429         
00430     print('The following migrations need to occur:')
00431     all_rules = []
00432     for m in migrations:
00433         all_rules.extend(m[1])
00434 
00435         print_trans(m[0][0].old_class, m[0][-1].new_class, 0)
00436         if len(m[1]) > 0:
00437             print("    %d rules missing:" % len(m[1]))
00438             for r in m[1]:
00439                 print_trans(r.old_class, r.new_class, 1)
00440 
00441     if options.rulefile is None:
00442         if all_rules == []:
00443             print("\nAll rules defined.  Bag is ready to be migrated")
00444         else:
00445             print("\nTo generate rules, please run with -g <rulefile>")
00446         exit(0)
00447 
00448     output = ''
00449     rules_left = mm.filter_rules_unique(all_rules)
00450 
00451     if rules_left == []:
00452         print("\nNo additional rule files needed to be generated.  %s not created."%(options.rulefile))
00453         exit(0)
00454 
00455     while len(rules_left) > 0:
00456         extra_rules = []
00457         for r in rules_left:
00458             if r.new_class is None:
00459                 print('The message type %s appears to have moved.  Please enter the type to migrate it to.' % r.old_class._type)
00460                 new_type = raw_input('>')
00461                 new_class = roslib.message.get_message_class(new_type)
00462                 while new_class is None:
00463                     print("\'%s\' could not be found in your system.  Please make sure it is built." % new_type)
00464                     new_type = raw_input('>')
00465                     new_class = roslib.message.get_message_class(new_type)
00466                 new_rule = mm.make_update_rule(r.old_class, new_class)
00467                 R = new_rule(mm, 'GENERATED.' + new_rule.__name__)
00468                 R.find_sub_paths()
00469                 new_rules = [r for r in mm.expand_rules(R.sub_rules) if r.valid == False]
00470                 extra_rules.extend(new_rules)
00471                 print('Creating the migration rule for %s requires additional missing rules:' % new_type)
00472                 for nr in new_rules:
00473                     print_trans(nr.old_class, nr.new_class,1)
00474                 output += R.get_class_def()
00475             else:
00476                 output += r.get_class_def()
00477         rules_left = mm.filter_rules_unique(extra_rules)
00478     f = open(options.rulefile, 'a')
00479     f.write(output)
00480     f.close()
00481 
00482     print('\nThe necessary rule files have been written to: %s' % options.rulefile)
00483 
00484 def compress_cmd(argv):
00485     parser = optparse.OptionParser(usage='rosbag compress [options] BAGFILE1 [BAGFILE2 ...]',
00486                                    description='Compress one or more bag files.')
00487     parser.add_option(      '--output-dir', action='store',      dest='output_dir', help='write to directory DIR', metavar='DIR')
00488     parser.add_option('-f', '--force',      action='store_true', dest='force',      help='force overwriting of backup file if it exists')
00489     parser.add_option('-q', '--quiet',      action='store_true', dest='quiet',      help='suppress noncritical messages')
00490 
00491     (options, args) = parser.parse_args(argv)
00492 
00493     if len(args) < 1:
00494         parser.error('You must specify at least one bag file.')
00495 
00496     op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, Compression.BZ2, options.quiet)
00497 
00498     bag_op(args, False, lambda b: False, op, options.output_dir, options.force, options.quiet)
00499 
00500 def decompress_cmd(argv):
00501     parser = optparse.OptionParser(usage='rosbag decompress [options] BAGFILE1 [BAGFILE2 ...]',
00502                                    description='Decompress one or more bag files.')
00503     parser.add_option(      '--output-dir', action='store',      dest='output_dir', help='write to directory DIR', metavar='DIR')
00504     parser.add_option('-f', '--force',      action='store_true', dest='force',      help='force overwriting of backup file if it exists')
00505     parser.add_option('-q', '--quiet',      action='store_true', dest='quiet',      help='suppress noncritical messages')
00506 
00507     (options, args) = parser.parse_args(argv)
00508 
00509     if len(args) < 1:
00510         parser.error('You must specify at least one bag file.')
00511     
00512     op = lambda inbag, outbag, quiet: change_compression_op(inbag, outbag, Compression.NONE, options.quiet)
00513     
00514     bag_op(args, False, lambda b: False, op, options.output_dir, options.force, options.quiet)
00515 
00516 def reindex_cmd(argv):
00517     parser = optparse.OptionParser(usage='rosbag reindex [options] BAGFILE1 [BAGFILE2 ...]',
00518                                    description='Reindexes one or more bag files.')
00519     parser.add_option(      '--output-dir', action='store',      dest='output_dir', help='write to directory DIR', metavar='DIR')
00520     parser.add_option('-f', '--force',      action='store_true', dest='force',      help='force overwriting of backup file if it exists')
00521     parser.add_option('-q', '--quiet',      action='store_true', dest='quiet',      help='suppress noncritical messages')
00522 
00523     (options, args) = parser.parse_args(argv)
00524 
00525     if len(args) < 1:
00526         parser.error('You must specify at least one bag file.')
00527     
00528     op = lambda inbag, outbag, quiet: reindex_op(inbag, outbag, options.quiet)
00529 
00530     bag_op(args, True, lambda b: b.version > 102, op, options.output_dir, options.force, options.quiet)
00531 
00532 def bag_op(inbag_filenames, allow_unindexed, copy_fn, op, output_dir=None, force=False, quiet=False):
00533     for inbag_filename in inbag_filenames:
00534         # Check we can read the file
00535         try:
00536             inbag = Bag(inbag_filename, 'r', allow_unindexed=allow_unindexed)
00537         except ROSBagUnindexedException:
00538             print('ERROR bag unindexed: %s.  Run rosbag reindex.' % inbag_filename, file=sys.stderr)
00539             continue
00540         except (ROSBagException, IOError) as ex:
00541             print('ERROR reading %s: %s' % (inbag_filename, str(ex)), file=sys.stderr)
00542             continue
00543 
00544         # Determine whether we should copy the bag    
00545         copy = copy_fn(inbag)
00546         
00547         inbag.close()
00548 
00549         # Determine filename for output bag
00550         if output_dir is None:
00551             outbag_filename = inbag_filename
00552         else:
00553             outbag_filename = os.path.join(output_dir, os.path.split(inbag_filename)[1])
00554 
00555         backup_filename = None
00556         if outbag_filename == inbag_filename:
00557             # Rename the input bag to ###.orig.###, and open for reading
00558             backup_filename = '%s.orig%s' % os.path.splitext(inbag_filename)
00559             
00560             if not force and os.path.exists(backup_filename):
00561                 if not quiet:
00562                     print('Skipping %s. Backup path %s already exists.' % (inbag_filename, backup_filename), file=sys.stderr)
00563                 continue
00564             
00565             try:
00566                 if copy:
00567                     shutil.copy(inbag_filename, backup_filename)
00568                 else:
00569                     os.rename(inbag_filename, backup_filename)
00570             except OSError as ex:
00571                 print('ERROR %s %s to %s: %s' % ('copying' if copy else 'moving', inbag_filename, backup_filename, str(ex)), file=sys.stderr)
00572                 continue
00573             
00574             source_filename = backup_filename
00575         else:
00576             if copy:
00577                 shutil.copy(inbag_filename, outbag_filename)
00578                 source_filename = outbag_filename
00579             else:
00580                 source_filename = inbag_filename
00581 
00582         try:
00583             inbag = Bag(source_filename, 'r', allow_unindexed=allow_unindexed)
00584 
00585             # Open the output bag file for writing
00586             try:
00587                 if copy:
00588                     outbag = Bag(outbag_filename, 'a', allow_unindexed=allow_unindexed)
00589                 else:
00590                     outbag = Bag(outbag_filename, 'w')
00591             except (ROSBagException, IOError) as ex:
00592                 print('ERROR writing to %s: %s' % (outbag_filename, str(ex)), file=sys.stderr)
00593                 inbag.close()
00594                 continue
00595 
00596             # Perform the operation
00597             try:
00598                 op(inbag, outbag, quiet=quiet)
00599             except ROSBagException as ex:
00600                 print('\nERROR operating on %s: %s' % (source_filename, str(ex)), file=sys.stderr)
00601                 inbag.close()
00602                 outbag.close()
00603                 continue
00604                 
00605             outbag.close()
00606             inbag.close()
00607 
00608         except KeyboardInterrupt:
00609             if backup_filename is not None:
00610                 try:
00611                     if copy:
00612                         os.remove(backup_filename)
00613                     else:
00614                         os.rename(backup_filename, inbag_filename)
00615                 except OSError as ex:
00616                     print('ERROR %s %s to %s: %s', ('removing' if copy else 'moving', backup_filename, inbag_filename, str(ex)), file=sys.stderr)
00617                     break
00618     
00619         except (ROSBagException, IOError) as ex:
00620             print('ERROR operating on %s: %s' % (inbag_filename, str(ex)), file=sys.stderr)
00621 
00622 def change_compression_op(inbag, outbag, compression, quiet):
00623     outbag.compression = compression
00624 
00625     if quiet:
00626         for topic, msg, t in inbag.read_messages(raw=True):
00627             outbag.write(topic, msg, t, raw=True)
00628     else:
00629         meter = ProgressMeter(outbag.filename, inbag._uncompressed_size)
00630 
00631         total_bytes = 0
00632         for topic, msg, t in inbag.read_messages(raw=True):
00633             msg_type, serialized_bytes, md5sum, pos, pytype = msg
00634     
00635             outbag.write(topic, msg, t, raw=True)
00636             
00637             total_bytes += len(serialized_bytes) 
00638             meter.step(total_bytes)
00639         
00640         meter.finish()
00641 
00642 def reindex_op(inbag, outbag, quiet):
00643     if inbag.version == 102:
00644         if quiet:
00645             try:
00646                 for offset in inbag.reindex():
00647                     pass
00648             except:
00649                 pass
00650 
00651             for (topic, msg, t) in inbag.read_messages():
00652                 outbag.write(topic, msg, t)
00653         else:
00654             meter = ProgressMeter(outbag.filename, inbag.size)
00655             try:
00656                 for offset in inbag.reindex():
00657                     meter.step(offset)
00658             except:
00659                 pass
00660             meter.finish()
00661 
00662             meter = ProgressMeter(outbag.filename, inbag.size)
00663             for (topic, msg, t) in inbag.read_messages():
00664                 outbag.write(topic, msg, t)
00665                 meter.step(inbag._file.tell())
00666             meter.finish()
00667     else:
00668         if quiet:
00669             try:
00670                 for offset in outbag.reindex():
00671                     pass
00672             except:
00673                 pass
00674         else:
00675             meter = ProgressMeter(outbag.filename, outbag.size)
00676             try:
00677                 for offset in outbag.reindex():
00678                     meter.step(offset)
00679             except:
00680                 pass
00681             meter.finish()
00682 
00683 class RosbagCmds(UserDict):
00684     def __init__(self):
00685         UserDict.__init__(self)
00686         self._description = {}
00687         self['help'] = self.help_cmd
00688 
00689     def add_cmd(self, name, function, description):
00690         self[name] = function
00691         self._description[name] = description
00692         
00693     def get_valid_cmds(self):
00694         str = "Available subcommands:\n"
00695         for k in sorted(self.keys()):
00696             str += "   %s  " % k
00697             if k in self._description.keys():
00698                 str +="\t%s" % self._description[k]
00699             str += "\n"
00700         return str
00701 
00702     def help_cmd(self,argv):
00703         argv = [a for a in argv if a != '-h' and a != '--help']
00704 
00705         if len(argv) == 0:
00706             print('Usage: rosbag <subcommand> [options] [args]')
00707             print()
00708             print("A bag is a file format in ROS for storing ROS message data. The rosbag command can record, replay and manipulate bags.")
00709             print()
00710             print(self.get_valid_cmds())
00711             print('For additional information, see http://wiki.ros.org/rosbag')
00712             print()
00713             return
00714 
00715         cmd = argv[0]
00716         if cmd in self:
00717             self[cmd](['-h'])
00718         else:
00719             print("Unknown command: '%s'" % cmd, file=sys.stderr)
00720             print(self.get_valid_cmds(), file=sys.stderr)
00721 
00722 class ProgressMeter(object):
00723     def __init__(self, path, bytes_total, refresh_rate=1.0):
00724         self.path           = path
00725         self.bytes_total    = bytes_total
00726         self.refresh_rate   = refresh_rate
00727         
00728         self.elapsed        = 0.0
00729         self.update_elapsed = 0.0
00730         self.bytes_read     = 0.0
00731 
00732         self.start_time     = time.time()
00733 
00734         self._update_progress()
00735 
00736     def step(self, bytes_read, force_update=False):
00737         self.bytes_read = bytes_read
00738         self.elapsed    = time.time() - self.start_time
00739         
00740         if force_update or self.elapsed - self.update_elapsed > self.refresh_rate:
00741             self._update_progress()
00742             self.update_elapsed = self.elapsed
00743 
00744     def _update_progress(self):
00745         max_path_len = self.terminal_width() - 37
00746         path = self.path
00747         if len(path) > max_path_len:
00748             path = '...' + self.path[-max_path_len + 3:]
00749 
00750         bytes_read_str  = self._human_readable_size(float(self.bytes_read))
00751         bytes_total_str = self._human_readable_size(float(self.bytes_total))
00752         
00753         if self.bytes_read < self.bytes_total:
00754             complete_fraction = float(self.bytes_read) / self.bytes_total
00755             pct_complete      = int(100.0 * complete_fraction)
00756 
00757             if complete_fraction > 0.0:
00758                 eta = self.elapsed * (1.0 / complete_fraction - 1.0)
00759                 eta_min, eta_sec = eta / 60, eta % 60
00760                 if eta_min > 99:
00761                     eta_str = '--:--'
00762                 else:
00763                     eta_str = '%02d:%02d' % (eta_min, eta_sec)
00764             else:
00765                 eta_str = '--:--'
00766 
00767             progress = '%-*s %3d%% %8s / %8s %s ETA' % (max_path_len, path, pct_complete, bytes_read_str, bytes_total_str, eta_str)
00768         else:
00769             progress = '%-*s 100%% %19s %02d:%02d    ' % (max_path_len, path, bytes_total_str, self.elapsed / 60, self.elapsed % 60)
00770 
00771         print('\r', progress, end='')
00772         sys.stdout.flush()
00773         
00774     def _human_readable_size(self, size):
00775         multiple = 1024.0
00776         for suffix in ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']:
00777             size /= multiple
00778             if size < multiple:
00779                 return '%.1f %s' % (size, suffix)
00780     
00781         raise ValueError('number too large')
00782 
00783     def finish(self):
00784         self.step(self.bytes_total, force_update=True)
00785         print()
00786 
00787     @staticmethod
00788     def terminal_width():
00789         """Estimate the width of the terminal"""
00790         width = 0
00791         try:
00792             import struct, fcntl, termios
00793             s     = struct.pack('HHHH', 0, 0, 0, 0)
00794             x     = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
00795             width = struct.unpack('HHHH', x)[1]
00796         except (IOError, ImportError):
00797             pass
00798         if width <= 0:
00799             try:
00800                 width = int(os.environ['COLUMNS'])
00801             except:
00802                 pass
00803         if width <= 0:
00804             width = 80
00805     
00806         return width
00807 
00808 def rosbagmain(argv=None):
00809     cmds = RosbagCmds()
00810     cmds.add_cmd('record', record_cmd, "Record a bag file with the contents of specified topics.")
00811     cmds.add_cmd('info', info_cmd, 'Summarize the contents of one or more bag files.')
00812     cmds.add_cmd('play', play_cmd, "Play back the contents of one or more bag files in a time-synchronized fashion.")
00813     cmds.add_cmd('check', check_cmd, 'Determine whether a bag is playable in the current system, or if it can be migrated.')
00814     cmds.add_cmd('fix', fix_cmd, 'Repair the messages in a bag file so that it can be played in the current system.')
00815     cmds.add_cmd('filter', filter_cmd, 'Filter the contents of the bag.')
00816     cmds.add_cmd('compress', compress_cmd, 'Compress one or more bag files.')
00817     cmds.add_cmd('decompress', decompress_cmd, 'Decompress one or more bag files.')
00818     cmds.add_cmd('reindex', reindex_cmd, 'Reindexes one or more bag files.')
00819 
00820     if argv is None:
00821         argv = sys.argv
00822 
00823     if '-h' in argv or '--help' in argv:
00824         argv = [a for a in argv if a != '-h' and a != '--help']
00825         argv.insert(1, 'help')
00826 
00827     if len(argv) > 1:
00828         cmd = argv[1]
00829     else:
00830         cmd = 'help'
00831 
00832     try:
00833         if cmd in cmds:
00834             cmds[cmd](argv[2:])
00835         else:
00836             cmds['help']([cmd])
00837     except KeyboardInterrupt:
00838         pass


rosbag
Author(s): Tim Field, Jeremy Leibs, James Bowman
autogenerated on Fri Aug 28 2015 12:33:52