Bagys and Map Bagys
Contents
ROSH provides libraries for two types of YAML-based message storage: bagys and map bagys. Regular bagys, just like ROS bags, store a sequence of messages. Map bagys are like Python dictionaries: they store a mapping between a key and a message.
Bagys
Bagys are like ROS bags, except they are based on a human-readable YAML format. They also have some other key differences:
- The message type is not stored in the bagy
- (Corollary) They can only store a single message type
- The do not record message receipt time
The format for bagys is identical to the output of rostopic echo and rosservice call. This means that the bagy equivalent of rosbag record is simply:
rostopic echo /my_topic > file.bagy
Similarly, the bagy format is is identical to the input of rostopic pub and rosservice call. That means that it is possible to echo a single bagy message into either of these tools. In the future, this will be improved upon.
Writing to a bagy
bagys have an API similar to files, i.e. open().
Using with statement
m1 = msg.std_msgs.String('foo') m2 = msg.std_msgs.String('bar') with Bagy('foo.bagy', 'w') as b: b.write(m1) b.write(m2)
Explicit close()
m1 = msg.std_msgs.String('foo') m2 = msg.std_msgs.String('bar') b = Bagy('foo.bagy', 'w') b.write(m1) b.write(m2) b.close()
Recording a bagy
If you don't want to write messages individually to a bagy, you can setup a bagy to record a single topic. You can call stop() and start() to pause/resume the recording.
WARNING: bagy recording within ROSH is not high performance.
b = Bagy('record.bagy', 'w') b.record(topics.rosout_agg) b.stop() b.start() b.close()
Reading from a bagy
There are a variety of ways of reading from a bagy, depending on your needs.
Read all messages into a list
with Bagy('foo.bagy', 'r', msg.std_msgs.String) as b: msgs = b.read()
Iterate over messages
b = Bagy('foo.bagy', 'r', msg.std_msgs.String) for m in b: print m b.close()
Read messages one by one
b = Bagy('foo.bagy', 'r', msg.std_msgs.String) m = b.next() b.close()
Advanced: Homographs
It is possible to record a bagy with one message type and read it back as another provided that the type you are reading into has at least the fields present in the original type, and the fields are of compatible types. For example, geometry_msgs/Vector3 vs. geometry_msgs/Point have identical specifications.
There are several potential use cases for this:
- Service requests/responses that have identical fields
- Actions that have identical fields
Isomorphic messages with differing precision (e.g. geometry_msgs/Point32 vs. geometry_msgs/Point)
You can also manually create "sparse" bagys, where you only store only a subset of the message's fields. When you load from the bagy, the rest of the fields will get default value assignments.
Recipe: Bagy-based Service
This creates the get_outlets service. In this particular case, our bagy contains a list of poses that we want to return.
with Bagy(findros('foo', 'outlet_poses.yaml'), srv.pr2_plugs_msgs.GetOutletsResponse) as b: resp = b.read()[0] # assume only one message in file Service('get_outlets', srv.pr2_plugs_msgs.GetOutlets, lambda x: resp) serve_forever()
Map Bagys
Map bagys are bagys that are indexed by keys. Whereas normal bagys are useful for storing a sequence of messages, map bagys are useful for storing a dictionary of messages.
Creating a map bagy
Map bagys have a file-like API, though there are various ways you can choose to write data to them.
Dictionary style
with MapBagy('foo1.bagy', 'w') as b: b['key1'] = m1 b['key2'] = m2 b['key3'] = m3
Explicit write()
with MapBagy('foo2.bagy', 'w') as b: b.write('key1', m1) b.write('key2', m2) b.write('key3', m3)
Keyword style
with MapBagy('foo.bagy', 'w') as b: b.write(key1=m1, key2=m2, key3=m3)
Reading from a key bagy
Read entire bagy into dictionary
with MapBagy(b, 'r', msg.std_msgs.String) as b: d = b.read() print d['key1']
Iterator style (default iterator iterates over keys and msgs)
with MapBagy('foo1.bagy', 'r', msg.std_msgs.String) as b: for k,m in b: print k, m
Dictionary style
with MapBagy('foo.bagy', 'r', msg.std_msgs.String) as b: m = b['key1']
Recipe: Map-Bagy-based Service
This creates a fictional get_outlet service. In this particular case, our bagy contains a list of poses that we want to return, indexed by name. Our fictional query has a single name field that specifies which outlet we want.
with MapBagy(findros('foo', 'outlet_db.yaml'), srv.pr2_plugs_msgs.GetOutletResponse) as b: responses = b.read() Service('get_outlets', srv.pr2_plugs_msgs.GetOutlet, lambda x: responses[x.name]) serve_forever()