77
88import copy
99import os .path
10- from collections import deque
11- from enum import Enum
12- from typing import Dict , Optional , Union , Tuple , Sequence
13- import yaml
14-
1510import matplotlib .cm as cmap
1611import numpy as np
1712import sys
13+ import yaml
14+ from collections import deque
15+ from enum import Enum
16+ from typing import Dict , Optional , Union , Tuple , Sequence
17+ from lxml import etree
1818from numpy .linalg import inv
1919from typing import Any , Dict
2020
4343from urdf_parser_py import urdf , xml_reflection
4444from vision_msgs .msg import Detection2DArray , Detection2D , ObjectHypothesisWithPose
4545
46- from .message_filter import DeserializedMessageFilter , DeserializedMessageFilterWithTF , Passthrough , RawMessageFilter , \
47- TopicSet
46+ from .message_filter import DeserializedMessageFilter , DeserializedMessageFilterWithTF , NoMessageFilter , Passthrough , \
47+ RawMessageFilter , TopicSet
4848
4949
5050def urdf_error (message ):
@@ -1248,7 +1248,7 @@ def _str_params(self):
12481248 return ", " .join (parts )
12491249
12501250
1251- class AddSubtitles (Passthrough ):
1251+ class AddSubtitles (NoMessageFilter ):
12521252 """Read subtitles from a text file and add them to the output as std_msgs/String messages."""
12531253
12541254 def __init__ (self , subtitles_file , topic , latch = False , time_offset = 0.0 ):
@@ -1265,7 +1265,14 @@ def __init__(self, subtitles_file, topic, latch=False, time_offset=0.0):
12651265 self ._latch = latch
12661266 self ._time_offset = rospy .Duration (time_offset )
12671267
1268+ self ._subtitles = None
1269+
1270+ def on_filtering_start (self ):
1271+ super (AddSubtitles , self ).on_filtering_start ()
1272+ self ._start_time = rospy .Time (self ._bag .get_start_time ()) + self ._time_offset
1273+ subtitles_file = self .resolve_file (self ._subtitles_file )
12681274 self ._subtitles = self ._parse_subtitles (subtitles_file )
1275+ print ("Read" , len (self ._subtitles ), "subtitles from file" , subtitles_file )
12691276
12701277 def _parse_subtitles (self , subtitles_file ):
12711278 if subtitles_file .endswith (".srt" ):
@@ -1317,10 +1324,6 @@ def parse_time_str(time_str):
13171324
13181325 return subtitles
13191326
1320- def set_bag (self , bag ):
1321- super (AddSubtitles , self ).set_bag (bag )
1322- self ._start_time = rospy .Time (bag .get_start_time ()) + self ._time_offset
1323-
13241327 def extra_initial_messages (self ):
13251328 connection_header = {
13261329 "callerid" : "/bag_filter" ,
@@ -1338,7 +1341,7 @@ def extra_initial_messages(self):
13381341 yield self ._topic , msg , abs_time , connection_header
13391342
13401343 def _str_params (self ):
1341- params = ["topic=" + self ._topic , "subtitles_file=" + self ._subtitles_file ]
1344+ params = ["topic=" + self ._topic , "subtitles_file=" + self .resolve_file ( self . _subtitles_file ) ]
13421345 if self ._time_offset != rospy .Duration (0 , 0 ):
13431346 params .append ("time_offset=%f" % (self ._time_offset .to_sec (),))
13441347 parent_params = super (AddSubtitles , self )._str_params ()
@@ -1347,6 +1350,97 @@ def _str_params(self):
13471350 return "," .join (params )
13481351
13491352
1353+ class DumpRobotModel (NoMessageFilter ):
1354+ """Read robot model URDF from ROS params and store it in a file."""
1355+
1356+ def __init__ (self , urdf_file , param = "robot_description" , remove_comments = False , pretty_print = False ,
1357+ run_on_start = True ):
1358+ """
1359+ :param urdf_file: Path to the URDF file. If relative, it will be resolved relative to the bag set by set_bag.
1360+ :param param: The parameter where robot model should be read.
1361+ :param remove_comments: Whether comments should be removed from the output URDF file.
1362+ :param pretty_print: Whether to pretty-print the URDF file (if False, the original layout is preserved).
1363+ :param run_on_start: If true, the model will be exported before messages are processed. If false, the model will
1364+ be exported after processing all messages.
1365+ """
1366+ super (DumpRobotModel , self ).__init__ ()
1367+ self ._urdf_file = urdf_file
1368+ self ._param = param
1369+ self ._remove_comments = remove_comments
1370+ self ._pretty_print = pretty_print
1371+ self ._run_on_start = run_on_start
1372+
1373+ def on_filtering_start (self ):
1374+ super (DumpRobotModel , self ).on_filtering_start ()
1375+
1376+ if self ._run_on_start :
1377+ self .dump_model ()
1378+
1379+ def on_filtering_end (self ):
1380+ super (DumpRobotModel , self ).on_filtering_end ()
1381+
1382+ if not self ._run_on_start :
1383+ self .dump_model ()
1384+
1385+ def dump_model (self ):
1386+ urdf = self ._get_param (self ._param )
1387+ if urdf is None :
1388+ print ('Robot model not found on parameter' , self ._param , file = sys .stderr )
1389+ return
1390+
1391+ if self ._remove_comments or self ._pretty_print :
1392+ parser = etree .XMLParser (remove_comments = self ._remove_comments , encoding = 'utf-8' )
1393+ tree = etree .fromstring (urdf .encode ('utf-8' ), parser = parser )
1394+ urdf = etree .tostring (tree , encoding = 'utf-8' , xml_declaration = True ,
1395+ pretty_print = self ._pretty_print ).decode ('utf-8' )
1396+
1397+ dest = self .resolve_file (self ._urdf_file )
1398+ with open (dest , 'w+' ) as f :
1399+ print (urdf , file = f )
1400+ print ("Robot model from parameter" , self ._param , "exported to" , dest )
1401+
1402+ def _str_params (self ):
1403+ params = ["param=" + self ._param , "urdf_file=" + self .resolve_file (self ._urdf_file )]
1404+ parent_params = super (DumpRobotModel , self )._str_params ()
1405+ if len (parent_params ) > 0 :
1406+ params .append (parent_params )
1407+ return "," .join (params )
1408+
1409+
1410+ class UpdateRobotModel (NoMessageFilter ):
1411+ """Read robot model from URDF file and update it in the ROS parameters."""
1412+
1413+ def __init__ (self , urdf_file , param = "robot_description" ):
1414+ """
1415+ :param urdf_file: Path to the URDF file. If relative, it will be resolved relative to the bag set by set_bag.
1416+ :param param: The parameter where robot model should be stored.
1417+ """
1418+ super (UpdateRobotModel , self ).__init__ ()
1419+ self ._urdf_file = urdf_file
1420+ self ._param = param
1421+
1422+ def on_filtering_start (self ):
1423+ super (UpdateRobotModel , self ).on_filtering_start ()
1424+
1425+ src = self .resolve_file (self ._urdf_file )
1426+ if not os .path .exists (src ):
1427+ print ('Cannot find robot URDF file' , src , file = sys .stderr )
1428+ return
1429+
1430+ with open (src , 'r' , encoding = 'utf-8' ) as f :
1431+ urdf = f .read ()
1432+
1433+ self ._set_param (self ._param , urdf )
1434+ print ('Robot model from %s set to parameter %s' % (src , self ._param ))
1435+
1436+ def _str_params (self ):
1437+ params = ["param=" + self ._param , "urdf_file=" + self .resolve_file (self ._urdf_file )]
1438+ parent_params = super (UpdateRobotModel , self )._str_params ()
1439+ if len (parent_params ) > 0 :
1440+ params .append (parent_params )
1441+ return "," .join (params )
1442+
1443+
13501444class FixSpotCams (RawMessageFilter ):
13511445 """Fix a problem with Spot robot cameras that publish a bit weird message header."""
13521446
0 commit comments