Skip to content

Commit 11a5f0b

Browse files
authored
feat: Bag Writer Append (#136)
1 parent c97ef9e commit 11a5f0b

File tree

4 files changed

+520
-57
lines changed

4 files changed

+520
-57
lines changed

README.md

Lines changed: 198 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,13 @@ uv add pybag-sdk
1515
uv tool install pybag-sdk
1616
```
1717

18-
## Quick Start
18+
## Reading Files
1919

20-
### CLI
21-
22-
```bash
23-
# Get file information
24-
pybag info data.mcap
25-
26-
# Filter messages
27-
pybag filter data.mcap -o output.mcap --include-topic /camera
28-
29-
# Merge multiple files
30-
pybag merge input1.mcap input2.mcap -o output.mcap
31-
```
20+
There are a number of ways to read data with pybag.
3221

3322
### Unified Reader
3423

3524
The `Reader` class provides a common interface for reading both MCAP and ROS 1 bag files.
36-
3725
The file format is automatically detected from the file extension.
3826

3927
```python
@@ -56,30 +44,60 @@ with Reader.from_file("data.bag") as reader:
5644
from pybag.mcap_reader import McapFileReader
5745

5846
with McapFileReader.from_file("data.mcap") as reader:
47+
# Get available topics
48+
topics = reader.get_topics()
49+
50+
# Stream messages with filtering
51+
for msg in reader.messages(
52+
topic=["/camera/*", "/sensor/imu"],
53+
start_time=1_000_000_000, # nanoseconds
54+
end_time=2_000_000_000,
55+
in_log_time_order=True
56+
):
57+
print(msg.log_time, msg.data)
58+
59+
# Get attachments and metadata
60+
attachments = reader.get_attachments()
61+
metadata = reader.get_metadata()
62+
```
63+
64+
### Reading ROS 1 Bag Files
65+
66+
```python
67+
from pybag.bag_reader import BagFileReader
68+
69+
with BagFileReader.from_file("data.bag") as reader:
5970
for msg in reader.messages("/camera"):
6071
print(msg.log_time, msg.data)
6172
```
6273

74+
## Writing Files
75+
76+
There are a number of ways to write data with pybag.
77+
6378
### Writing MCAP Files
6479

6580
```python
6681
from pybag.mcap_writer import McapFileWriter
6782
from pybag.ros2.humble import std_msgs
6883

69-
with McapFileWriter.open("output.mcap") as writer:
70-
log_time_ns = 1000
84+
with McapFileWriter.open(
85+
"output.mcap",
86+
profile="ros2",
87+
chunk_compression="lz4"
88+
) as writer:
7189
msg = std_msgs.String(data="hello")
72-
writer.write_message("/status", log_time_ns, msg)
73-
```
90+
writer.write_message("/status", 1_000_000_000, msg)
7491

75-
### Reading ROS 1 Bag Files
76-
77-
```python
78-
from pybag.bag_reader import BagFileReader
92+
# Add attachments
93+
writer.write_attachment(
94+
name="calibration.yaml",
95+
data=b"camera: ...",
96+
media_type="application/yaml"
97+
)
7998

80-
with BagFileReader.from_file("data.bag") as reader:
81-
for msg in reader.messages("/camera"):
82-
print(msg.log_time, msg.data)
99+
# Add metadata
100+
writer.write_metadata("recording_info", {"robot": "husky_1"})
83101
```
84102

85103
### Writing ROS 1 Bag Files
@@ -88,24 +106,173 @@ with BagFileReader.from_file("data.bag") as reader:
88106
from pybag.bag_writer import BagFileWriter
89107
from pybag.ros1.noetic import std_msgs
90108

91-
with BagFileWriter.open("output.bag") as writer:
92-
log_time_ns = 1000
109+
with BagFileWriter.open("output.bag", compression="bz2") as writer:
93110
msg = std_msgs.String(data="hello")
94-
writer.write_message("/status", log_time_ns, msg)
111+
writer.write_message("/status", 1_000_000_000, msg)
112+
```
113+
114+
## Append Mode
115+
116+
Both MCAP and bag writers support append mode to add messages to existing files.
117+
118+
### Appending to MCAP Files
119+
120+
```python
121+
from pybag.mcap_writer import McapFileWriter
122+
123+
# Create a new file
124+
with McapFileWriter.open("recording.mcap") as writer:
125+
writer.write_message("/topic", 1_000_000_000, msg1)
126+
127+
# Append to existing file
128+
with McapFileWriter.open("recording.mcap", mode="a") as writer:
129+
writer.write_message("/topic", 2_000_000_000, msg2)
130+
writer.write_message("/new_topic", 3_000_000_000, msg3)
131+
```
132+
133+
### Appending to ROS 1 Bag Files
134+
135+
```python
136+
from pybag.bag_writer import BagFileWriter
137+
138+
# Create a new file
139+
with BagFileWriter.open("recording.bag") as writer:
140+
writer.write_message("/topic", 1_000_000_000, msg1)
141+
142+
# Append to existing file
143+
with BagFileWriter.open("recording.bag", mode="a") as writer:
144+
writer.write_message("/topic", 2_000_000_000, msg2)
145+
writer.write_message("/new_topic", 3_000_000_000, msg3)
146+
```
147+
148+
## TypeStore
149+
150+
The `TypeStore` provides unified access to ROS message schemas from built-in definitions
151+
and custom `.msg` files.
152+
153+
```python
154+
from pybag.typestore import TypeStore
155+
156+
# Create a type store for ROS 2 Humble
157+
type_store = TypeStore(encoding="ros2msg", distro="humble")
158+
159+
# Add custom message paths
160+
type_store.add_path("/path/to/custom_msgs")
161+
162+
# Find a message schema
163+
schema = type_store.find("std_msgs/msg/String")
164+
165+
# List user-provided messages
166+
messages = type_store.list_messages()
95167
```
96168

97-
### Custom Messages
169+
### Supported Distributions
170+
171+
**ROS 1**: noetic (and legacy: melodic, kinetic, indigo, etc.)
172+
173+
**ROS 2**: foxy, galactic, humble, iron, jazzy, kilted, rolling
174+
175+
## Custom Messages
176+
177+
Define custom message types using dataclasses:
98178

99179
```python
100180
from dataclasses import dataclass
101181
import pybag.types as t
182+
from pybag.mcap_writer import McapFileWriter
102183

103184
@dataclass
104185
class SensorData:
105-
__msg_name__ = 'custom/msg/SensorData'
186+
__msg_name__ = "custom/msg/SensorData"
106187
temperature: t.float32
107188
humidity: t.float32
108189

109190
with McapFileWriter.open("sensors.mcap") as writer:
110-
writer.write_message("/sensor", 1000, SensorData(25.5, 60.0))
191+
writer.write_message("/sensor", 1_000_000_000, SensorData(25.5, 60.0))
192+
```
193+
194+
Or provide schema text directly:
195+
196+
```python
197+
from pybag.types import SchemaText
198+
199+
schema = SchemaText(
200+
name="custom/msg/SensorData",
201+
text="float32 temperature\nfloat32 humidity"
202+
)
203+
204+
with McapFileWriter.open("sensors.mcap") as writer:
205+
writer.add_channel("/sensor", schema=schema)
206+
writer.write_message("/sensor", 1_000_000_000, {"temperature": 25.5, "humidity": 60.0})
207+
```
208+
209+
## CLI Reference
210+
211+
### info
212+
213+
Display file statistics and metadata:
214+
215+
```bash
216+
pybag info recording.mcap
217+
pybag info recording.bag
218+
```
219+
220+
### filter
221+
222+
Extract data based on topic patterns and time ranges:
223+
224+
```bash
225+
pybag filter input.mcap -o output.mcap \
226+
--include-topic "/sensor/*" \
227+
--exclude-topic "/sensor/debug" \
228+
--start-time 10.5 \
229+
--end-time 20.3
230+
```
231+
232+
### merge
233+
234+
Combine multiple files into one:
235+
236+
```bash
237+
pybag merge file1.mcap file2.mcap -o merged.mcap
238+
pybag merge bag1.bag bag2.bag -o merged.bag
239+
```
240+
241+
### convert
242+
243+
Convert between bag and MCAP formats:
244+
245+
```bash
246+
# Bag to MCAP
247+
pybag convert input.bag -o output.mcap --profile ros2 --mcap-compression lz4
248+
249+
# MCAP to Bag
250+
pybag convert input.mcap -o output.bag --bag-compression bz2
251+
```
252+
253+
### sort
254+
255+
Sort messages by time and/or topic:
256+
257+
```bash
258+
pybag sort input.mcap -o sorted.mcap --log-time --by-topic
259+
```
260+
261+
### recover
262+
263+
Recover data from corrupted files:
264+
265+
```bash
266+
pybag recover corrupted.mcap -o recovered.mcap --verbose
267+
```
268+
269+
### inspect
270+
271+
Examine specific record types:
272+
273+
```bash
274+
pybag inspect schemas recording.mcap
275+
pybag inspect channels recording.mcap --topic "/camera/*"
276+
pybag inspect metadata recording.mcap --name "calibration"
277+
pybag inspect attachments recording.mcap --name "calib.yaml" --data
111278
```

0 commit comments

Comments
 (0)