@@ -22,6 +22,7 @@ from shutil import copyfile
2222import genpy
2323import rosbag
2424from cras import pretty_file_size
25+ from cras .string_utils import STRING_TYPE
2526
2627from cras_bag_tools .bag_filter import filter_bag
2728from cras_bag_tools .message_filter import FilterChain , MessageFilter , Passthrough , get_filters
@@ -94,17 +95,21 @@ def copy_params_if_any(param_file, out_bag_path):
9495
9596
9697def filter_bags (bags , out_format , compression , copy_params , filter , default_params_file = None ,
97- start_time = None , end_time = None , time_ranges = None , limit_to_first_bag = False ):
98+ start_time = None , end_time = None , time_ranges = None , limit_to_first_bag = False , extra_bags = None ):
9899 """Filter all given bags using the given filter.
99100
100101 :param list bags: The bags to filter. If multiple bags should be read at once, add them as a single list item with
101102 paths separated by colon. One of the files can also be be a YAML file with ROS parameters.
102- :param str out_format: Output path template.
103+ All sub-bags except the first one can have their name formatted similar to `out_format`.
104+ :param str out_format: Output path template in the format :meth:`str.format()` accepts. Available variables are:
105+ `dirname`, `basename`, `name`, `ext`, `ext_no_dot`, `bag_prefix`, `bag_stamp`, `bag_suffix`,
106+ `bag_base`. See :meth:`resolve_path()` for details.
103107 :param str compression: Output bag compression. One of 'rosbag.Compression.*' constants.
104108 :param bool copy_params: If True, copy parameters file along with the bag file if it exists.
105109 :param MessageFilter filter: The filter to apply.
106110 :param str default_params_file: If nonempty, specifies the YAML file with ROS parameters that is used if no param
107- file is specified for the particular bag.
111+ file is specified for the particular bag. The path will be resolved similar to
112+ out_format with the processed bag as reference file.
108113 :param genpy.Time start_time: Time from which the bag filtering should be started.
109114 :param genpy.Time end_time: Time to which the bag filtering should be stopped.
110115 :param TimeRanges time_ranges: Time ranges of the bag files to process. If start_time and end_time are specified,
@@ -113,27 +118,40 @@ def filter_bags(bags, out_format, compression, copy_params, filter, default_para
113118 :param bool limit_to_first_bag: If True, each multibag will report its start and end to be equal to the
114119 first open bag. If False, the start and end correspond to the earliest and latest
115120 stamp in all bags.
121+ :param extra_bags: List of bag files that will be added to each of the processed bags. Names of these bags
122+ will be resolved similar to out_format with the processed bag as reference file.
123+ :type extra_bags: list or str
116124 :return: The number of bags that failed to be processed.
117125 :rtype: int
118126 """
119127 num_failed = 0
120128 i = 0
121129 for bags_path in bags :
122130 i += 1
123- bag_path = bags_path
131+ bag_path = os . path . abspath ( os . path . expanduser ( bags_path ))
124132 bags_paths = [bag_path ]
125133 if os .path .pathsep in bags_path :
126134 bags_paths = bags_path .split (os .path .pathsep )
127- bag_path = bags_paths [0 ]
135+ bag_path = os . path . abspath ( os . path . expanduser ( bags_paths [0 ]))
128136
129- params_file = default_params_file
137+ for b in range (1 , len (bags_paths )):
138+ bags_paths [b ] = resolve_path (bags_paths [b ], bag_path )
139+ required_bags = set (bags_paths )
140+
141+ if extra_bags is not None :
142+ if isinstance (extra_bags , STRING_TYPE ):
143+ extra = extra_bags .split (os .path .pathsep )
144+ else :
145+ extra = list (extra_bags )
146+ bags_paths += [resolve_path (b , bag_path ) for b in extra ]
147+
148+ params_file = resolve_path (default_params_file , bag_path ) if default_params_file is not None else None
130149 if params_file is None :
131150 for b in bags_paths :
132151 ext = os .path .splitext (b )[- 1 ]
133152 if ext in ('.params' , '.yaml' , '.yml' ):
134153 params_file = b
135154 bags_paths .remove (b )
136- bags_path = os .path .pathsep .join (bags_paths )
137155 break
138156
139157 if params_file is None :
@@ -146,18 +164,24 @@ def filter_bags(bags, out_format, compression, copy_params, filter, default_para
146164 print ()
147165 print ("[{}/{}] Bag {}" .format (i , len (bags ), bag_path ))
148166
149- print ('Source: %s' % ("," .join ([os .path .abspath (b ) for b in bags_paths ]),))
150167 bags_ok = True
168+ bags_to_remove = []
151169 for b in bags_paths :
152170 if not os .path .exists (b ):
153- print ('Source bag %s does not exist' % (b ,), file = sys .stderr )
154- bags_ok = False
171+ if b in required_bags :
172+ print ('Source bag %s does not exist' % (b ,), file = sys .stderr )
173+ bags_ok = False
174+ bags_to_remove .append (b )
155175 if not bags_ok :
156176 num_failed += 1
157177 continue
178+ for b in bags_to_remove :
179+ bags_paths .remove (b )
180+
181+ print ('Source: %s' % ("\n " .join ([os .path .abspath (b ) for b in bags_paths ]),))
158182
159183 try :
160- with TqdmMultiBag (bags_path , skip_index = True , limit_to_first_bag = limit_to_first_bag ) as bag :
184+ with TqdmMultiBag (bags_paths , skip_index = True , limit_to_first_bag = limit_to_first_bag ) as bag :
161185 print ('- Size: %s' % (pretty_file_size (bag .size ),))
162186
163187 out_bag_path = resolve_path (out_format , bag_path )
@@ -208,7 +232,7 @@ def filter_bags(bags, out_format, compression, copy_params, filter, default_para
208232 file = sys .stderr )
209233
210234 except Exception as e :
211- print ('Error processing bag file %s: %s' % (bags_path , str (e )), file = sys .stderr )
235+ print ('Error processing bag file %s: %s' % (bag_path , str (e )), file = sys .stderr )
212236 num_failed += 1
213237 import traceback
214238 traceback .print_exc ()
@@ -228,10 +252,19 @@ class TimeRangesAction(argparse.Action):
228252
229253def main ():
230254 parser = ArgumentParser ()
231- parser .add_argument ('bags' , nargs = '*' , help = "The (multi)bag files to filter." )
255+ parser .add_argument ('bags' , nargs = '*' , help = "The (multi)bag files to filter. A multibag is a colon-separated list "
256+ "of bag files that will be processed together (basically merged). "
257+ "The multibag can also specify at most one YAML file with parameters."
258+ "Except the first item, the names of all sub-bags in a multibag are "
259+ "treated as templates for str.format()." )
260+ parser .add_argument ('--extra-bags' , dest = 'extra_bags' , nargs = '+' , default = None ,
261+ help = "Bag files added to every (multi)bag from `bags`. If an extra bag doesn't exist, "
262+ "it will be ignored. The names of all extra bags are treated as templates for "
263+ "str.format()." )
232264 parser .add_argument ('-c' , '--config' , nargs = '+' , help = "YAML configs of filters" )
233265 parser .add_argument ('-o' , '--out-format' , default = argparse .SUPPRESS ,
234- help = 'Template for naming the output bag. Defaults to "{name}.proc{ext}"' )
266+ help = 'File name of the output bag. It is treated as a template for str.format(). '
267+ 'Defaults to "{name}.proc{ext}".' )
235268 parser .add_argument ('--lz4' , dest = 'compression' , action = 'store_const' , const = rosbag .Compression .LZ4 ,
236269 help = "Compress the bag via LZ4" )
237270 parser .add_argument ('--bz2' , dest = 'compression' , action = 'store_const' , const = rosbag .Compression .BZ2 ,
@@ -241,7 +274,8 @@ def main():
241274 help = "If set, no .params file will be copied." )
242275 parser .add_argument ('--default-params-file' , dest = 'default_params_file' , type = str , default = None ,
243276 help = "If nonempty, specifies the YAML file with ROS parameters that is used if no param file "
244- "is specified for the particular bag." )
277+ "is specified for the particular bag. The file name is treated as a template for "
278+ "str.format()." )
245279 parser .add_argument ("--list-yaml-keys" , dest = "list_yaml_keys" , action = "store_true" ,
246280 help = "Print a list of all available YAML top-level keys provided by filters." )
247281 parser .add_argument ("--list-filters" , dest = "list_filters" , action = "store_true" ,
@@ -262,7 +296,8 @@ def main():
262296 getattr (f , 'add_cli_args' )(parser )
263297
264298 default_yaml_keys = [
265- 'bags' , 'out_format' , 'compression' , 'filters' , 'copy_params' , 'start_time' , 'end_time' , 'time_ranges' ,
299+ 'bags' , 'extra_bags' , 'out_format' , 'compression' , 'filters' , 'copy_params' , 'start_time' , 'end_time' ,
300+ 'time_ranges' ,
266301 ]
267302
268303 def default_process_cli_args (filters , args ):
@@ -349,7 +384,7 @@ def main():
349384 args .bags , args .out_format , args .compression , args .copy_params , filter_chain , args .default_params_file ,
350385 genpy .Time (args .start_time ) if args .start_time is not None else None ,
351386 genpy .Time (args .end_time ) if args .end_time is not None else None , args .time_ranges ,
352- args .limit_to_first_bag )
387+ args .limit_to_first_bag , args . extra_bags )
353388
354389 sys .exit (num_failed )
355390
0 commit comments