mcap-bag-parser 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcap_bag_parser-0.1.0/.gitattributes +1 -0
- mcap_bag_parser-0.1.0/.gitignore +3 -0
- mcap_bag_parser-0.1.0/.gitlab-ci.yml +22 -0
- mcap_bag_parser-0.1.0/LICENSE +21 -0
- mcap_bag_parser-0.1.0/PKG-INFO +17 -0
- mcap_bag_parser-0.1.0/README.md +3 -0
- mcap_bag_parser-0.1.0/pyproject.toml +26 -0
- mcap_bag_parser-0.1.0/requirements.txt +5 -0
- mcap_bag_parser-0.1.0/src/__init__.py +0 -0
- mcap_bag_parser-0.1.0/src/mcap_bag_parser/__init__.py +0 -0
- mcap_bag_parser-0.1.0/src/mcap_bag_parser/mcap_bag_parser.py +325 -0
- mcap_bag_parser-0.1.0/src/mcap_bag_parser/test/bagfile/bagfile_0.mcap +0 -0
- mcap_bag_parser-0.1.0/src/mcap_bag_parser/test/bagfile/metadata.yaml +272 -0
- mcap_bag_parser-0.1.0/src/mcap_bag_parser/test/test_mcap_bag_parser.py +113 -0
- mcap_bag_parser-0.1.0/src/pytest.ini +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.mcap filter=lfs diff=lfs merge=lfs -text
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
- linting
|
|
3
|
+
- testing
|
|
4
|
+
|
|
5
|
+
flake8:
|
|
6
|
+
stage: linting
|
|
7
|
+
image: python:3.10
|
|
8
|
+
|
|
9
|
+
script:
|
|
10
|
+
- pip install flake8 pep8-naming
|
|
11
|
+
- flake8 . --count --max-complexity=20 --max-line-length=127 --statistics
|
|
12
|
+
only:
|
|
13
|
+
- merge_requests
|
|
14
|
+
|
|
15
|
+
pytest:
|
|
16
|
+
stage: testing
|
|
17
|
+
image: python:3.10
|
|
18
|
+
script:
|
|
19
|
+
- pip install -r requirements.txt
|
|
20
|
+
- python -m pytest
|
|
21
|
+
only:
|
|
22
|
+
- merge_requests
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Neal Tanner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: mcap_bag_parser
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Parse mcap bagfiles into pandas dataframes
|
|
5
|
+
Project-URL: Homepage, https://gitlab.com/nealtanner/mcap-bag-parser
|
|
6
|
+
Project-URL: Issues, https://gitlab.com/nealtanner/mcap-bag-parser/-/issues
|
|
7
|
+
Author: Neal Tanner, Teresa Gadda
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# MCAP Bag Parser
|
|
16
|
+
|
|
17
|
+
Parse MCAP rosbags into pandas dataframes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcap_bag_parser"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Neal Tanner"},
|
|
10
|
+
{ name="Teresa Gadda"},
|
|
11
|
+
]
|
|
12
|
+
description = "Parse mcap bagfiles into pandas dataframes"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.8"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://gitlab.com/nealtanner/mcap-bag-parser"
|
|
23
|
+
Issues = "https://gitlab.com/nealtanner/mcap-bag-parser/-/issues"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/mcap_bag_parser"]
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import pathlib
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
import pickle
|
|
5
|
+
from typing import Iterable, Optional
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from mcap.reader import make_reader
|
|
9
|
+
from mcap_ros2.decoder import DecoderFactory
|
|
10
|
+
|
|
11
|
+
MAX_ARRAY_SIZE_TO_SPLIT = 10
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def read_messages(bag_file: str,
|
|
15
|
+
topics: Optional[Iterable[str]] = None,
|
|
16
|
+
start_time: Optional[int] = None,
|
|
17
|
+
end_time: Optional[int] = None,
|
|
18
|
+
log_time_order: bool = True,
|
|
19
|
+
reverse: bool = False):
|
|
20
|
+
""" iterates through the messages in an MCAP.
|
|
21
|
+
|
|
22
|
+
Parameters:
|
|
23
|
+
bag_file: path/name of bag file
|
|
24
|
+
topics: if not None, only messages from these topics will be returned.
|
|
25
|
+
start_time: an integer nanosecond timestamp. messages logged before this timestamp are not included.
|
|
26
|
+
end_time: an integer nanosecond timestamp. messages logged after this timestamp are not included.
|
|
27
|
+
log_time_order: if True, messages will be yielded in ascending log time order.
|
|
28
|
+
if False, messages will be yielded in the order they appear in the MCAP file.
|
|
29
|
+
reverse: if both `log_time_order` and `reverse` are True, messages will be
|
|
30
|
+
yielded in descending log time order.
|
|
31
|
+
"""
|
|
32
|
+
with open(bag_file, "rb") as fobj:
|
|
33
|
+
reader = make_reader(fobj, decoder_factories=[DecoderFactory()])
|
|
34
|
+
for schema, channel, message, ros_msg in reader.iter_decoded_messages(topics=topics,
|
|
35
|
+
start_time=start_time,
|
|
36
|
+
end_time=end_time,
|
|
37
|
+
log_time_order=log_time_order,
|
|
38
|
+
reverse=reverse):
|
|
39
|
+
yield channel.topic, ros_msg, message.log_time
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def members(msg):
|
|
43
|
+
""" Return the public members of a message """
|
|
44
|
+
return [attr for attr in dir(msg) if
|
|
45
|
+
not callable(getattr(msg, attr)) and
|
|
46
|
+
not attr.startswith("_") and
|
|
47
|
+
attr not in ['real', 'imag', 'numerator', 'denominator']]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# https://stackoverflow.com/a/31174427
|
|
51
|
+
def rgetattr(obj, attr, *args):
|
|
52
|
+
def _getattr(obj, attr):
|
|
53
|
+
return getattr(obj, attr, *args)
|
|
54
|
+
return functools.reduce(_getattr, [obj] + attr.split('.'))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# https://docs.python.org/3/library/operator.html#operator.attrgetter
|
|
58
|
+
def resolve_attr(obj, attr):
|
|
59
|
+
for name in attr.split("."):
|
|
60
|
+
if name in [f'{idx}' for idx in range(100)]:
|
|
61
|
+
# interpret field names of simple numbers as array indices
|
|
62
|
+
try:
|
|
63
|
+
idx = int(name)
|
|
64
|
+
obj = obj[idx]
|
|
65
|
+
except IndexError:
|
|
66
|
+
obj = None
|
|
67
|
+
else:
|
|
68
|
+
obj = getattr(obj, name)
|
|
69
|
+
return obj
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class MessageSchema:
|
|
73
|
+
""" Describes how we decompose a message into a DataFrame entry """
|
|
74
|
+
|
|
75
|
+
def __init__(self, topic, msg, prefix_with_topic=False):
|
|
76
|
+
""" Generate the schema from an example message """
|
|
77
|
+
self._topic = topic
|
|
78
|
+
self._name_indexed_fields = []
|
|
79
|
+
self._names = None
|
|
80
|
+
self._field_map = {} # alternative approach
|
|
81
|
+
|
|
82
|
+
if 'name' in members(msg) and isinstance(getattr(msg, 'name'), Iterable):
|
|
83
|
+
# Lots of ROS messages use parallel arrays that are indexed by a `name` array
|
|
84
|
+
# We want to unwrap these for the user
|
|
85
|
+
# TODO: figure out how to handle multiple messages on same topic with different name lists
|
|
86
|
+
# TODO: Push parsing of name-indexed arrays down into recursive approach below so that
|
|
87
|
+
# it works at more than just the top level
|
|
88
|
+
self._names = getattr(msg, 'name')
|
|
89
|
+
for member_name in members(msg):
|
|
90
|
+
member_var = getattr(msg, member_name)
|
|
91
|
+
if member_name == 'name':
|
|
92
|
+
pass
|
|
93
|
+
elif isinstance(member_var, Iterable) \
|
|
94
|
+
and len(member_var) == len(self._names) \
|
|
95
|
+
and not isinstance(member_var, str):
|
|
96
|
+
# This field is the same length as the name field, so we will assume they are paired
|
|
97
|
+
self._name_indexed_fields.append(member_name)
|
|
98
|
+
for idx, name in enumerate(self._names):
|
|
99
|
+
self._parse_field(key_name=f'{member_name}.{name}',
|
|
100
|
+
data_name=f'{member_name}.{idx}',
|
|
101
|
+
var=member_var[idx])
|
|
102
|
+
else:
|
|
103
|
+
# not name-index, so we'll deal with it separately
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
for member_name in members(msg):
|
|
107
|
+
member_var = getattr(msg, member_name)
|
|
108
|
+
if member_name in self._name_indexed_fields or member_name == 'name':
|
|
109
|
+
# already dealt with these
|
|
110
|
+
pass
|
|
111
|
+
else:
|
|
112
|
+
self._parse_field(key_name=member_name, data_name=member_name, var=member_var)
|
|
113
|
+
|
|
114
|
+
if prefix_with_topic:
|
|
115
|
+
prefixed_field_map = {}
|
|
116
|
+
for column, member_name in self._field_map.items():
|
|
117
|
+
if column in ['msg_timestamp', 'header', 'stamp']:
|
|
118
|
+
# there are a few special fields that we do NOT want to be unique between topics
|
|
119
|
+
prefixed_field_map[column] = member_name
|
|
120
|
+
else:
|
|
121
|
+
prefixed_field_map[f'{self._topic}.{column}'] = member_name
|
|
122
|
+
self._field_map = prefixed_field_map
|
|
123
|
+
|
|
124
|
+
# print(f'Header for {self._topic} is {self.header}')
|
|
125
|
+
|
|
126
|
+
def _parse_field(self, key_name, data_name, var):
|
|
127
|
+
""" Parse an individual field in the data structure, likely to recurse! """
|
|
128
|
+
# print(f'parse_field({key_name}, {data_name}, {var}')
|
|
129
|
+
# print(f'members = {members(var)}')
|
|
130
|
+
# If this looks like a struct, recurse down into each named member
|
|
131
|
+
if len(members(var)):
|
|
132
|
+
for member in members(var):
|
|
133
|
+
member_var = getattr(var, member)
|
|
134
|
+
self._parse_field(key_name=f'{key_name}.{member}',
|
|
135
|
+
data_name=f'{data_name}.{member}',
|
|
136
|
+
var=member_var)
|
|
137
|
+
|
|
138
|
+
# If this looks like an array, recurse down into each numbered element
|
|
139
|
+
elif isinstance(var, Iterable) \
|
|
140
|
+
and len(var) <= MAX_ARRAY_SIZE_TO_SPLIT \
|
|
141
|
+
and not isinstance(var, str) \
|
|
142
|
+
and key_name not in ['arg', 'changed_parameters', 'deleted_parameters']:
|
|
143
|
+
# TODO: figure out how to handle arrays (such as `arg` field) with variable lengths message to message
|
|
144
|
+
for idx in range(len(var)):
|
|
145
|
+
# recurse down into each sub_member
|
|
146
|
+
self._parse_field(key_name=f'{key_name}.{idx}',
|
|
147
|
+
data_name=f'{data_name}.{idx}',
|
|
148
|
+
var=var[idx])
|
|
149
|
+
else: # The thing in the array looks like a scalar, so go ahead and add it to the field map
|
|
150
|
+
self._field_map.update(
|
|
151
|
+
{f'{key_name}.{idx}': f'{data_name}.{idx}' for idx in range(len(var))})
|
|
152
|
+
|
|
153
|
+
# Otherwise, presume that we have reached the end of the tree and add this field to the map
|
|
154
|
+
elif key_name != 'name':
|
|
155
|
+
# No special situations found, so just use each member as its own column
|
|
156
|
+
self._field_map[f'{key_name}'] = f'{data_name}'
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def header(self):
|
|
160
|
+
return tuple(['msg_timestamp'] + list(self._field_map.keys()))
|
|
161
|
+
|
|
162
|
+
def generate_row(self, timestamp, msg):
|
|
163
|
+
""" Use the pre-determined schema to generate one row in the dataframe """
|
|
164
|
+
row_data = [timestamp]
|
|
165
|
+
|
|
166
|
+
for header, member_name in self._field_map.items():
|
|
167
|
+
row_data.append(resolve_attr(msg, member_name))
|
|
168
|
+
|
|
169
|
+
return row_data
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class BagFileParser:
|
|
173
|
+
""" Class-based parser to mimic existing sql-based parser interface """
|
|
174
|
+
|
|
175
|
+
def __init__(self, bag_file, use_pickle_if_exists=False):
|
|
176
|
+
self._bag_file: pathlib.Path = bag_file
|
|
177
|
+
self._pickle_file = pathlib.Path(str(self._bag_file).replace('.mcap', '.pickle'))
|
|
178
|
+
|
|
179
|
+
# Write to/read from a locally stored dataframe for faster post-processing
|
|
180
|
+
self._use_pickle_if_exists: bool = use_pickle_if_exists
|
|
181
|
+
|
|
182
|
+
def _get_summary(self):
|
|
183
|
+
with open(self._bag_file, "rb") as fobj:
|
|
184
|
+
reader = make_reader(fobj, decoder_factories=[DecoderFactory()])
|
|
185
|
+
summary = reader.get_summary()
|
|
186
|
+
return summary
|
|
187
|
+
|
|
188
|
+
def set_pickle_path(self, path):
|
|
189
|
+
self._pickle_file = path
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def message_start_time(self):
|
|
193
|
+
return self._get_summary().statistics.message_start_time
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def message_end_time(self):
|
|
197
|
+
return self._get_summary().statistics.message_end_time
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def topics(self):
|
|
201
|
+
summary = self._get_summary()
|
|
202
|
+
topics = [channel.topic for idx, channel in summary.channels.items()]
|
|
203
|
+
return tuple(topics)
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def message_counts(self):
|
|
207
|
+
summary = self._get_summary()
|
|
208
|
+
message_counts = {}
|
|
209
|
+
for idx, count in summary.statistics.channel_message_counts.items():
|
|
210
|
+
message_counts[summary.channels[idx].topic] = count
|
|
211
|
+
return message_counts
|
|
212
|
+
|
|
213
|
+
def read_messages(self, *args, **kwargs):
|
|
214
|
+
return read_messages(self._bag_file, *args, **kwargs)
|
|
215
|
+
|
|
216
|
+
def topic_to_dataframe(self, topic: str,
|
|
217
|
+
start_time: Optional[int] = None, # [ns]
|
|
218
|
+
end_time: Optional[int] = None, # [ns]
|
|
219
|
+
rel_start_time: Optional[float] = None, # [s]
|
|
220
|
+
rel_end_time: Optional[float] = None,): # [s]
|
|
221
|
+
""" Generate a pandas.DataFrame from a single topic """
|
|
222
|
+
if rel_start_time is not None:
|
|
223
|
+
start_time = self.message_start_time + int(1E9*rel_start_time)
|
|
224
|
+
if rel_end_time is not None:
|
|
225
|
+
end_time = self.message_start_time + int(1E9*rel_end_time)
|
|
226
|
+
|
|
227
|
+
# TODO: fix message counts for loading a partial bagfile
|
|
228
|
+
print(f'Expect {self.message_counts[topic]} messages for {topic}')
|
|
229
|
+
schema = None # have to wait until we get the first message
|
|
230
|
+
data = []
|
|
231
|
+
msg_count = 0
|
|
232
|
+
for topic, msg, time in self.read_messages(topics=[topic], start_time=start_time, end_time=end_time):
|
|
233
|
+
if schema is None:
|
|
234
|
+
# figure out how we are going to represent this type of message in the dataframe
|
|
235
|
+
schema = MessageSchema(topic=topic, msg=msg)
|
|
236
|
+
data.append(schema.generate_row(timestamp=time, msg=msg))
|
|
237
|
+
msg_count += 1
|
|
238
|
+
if msg_count % 10000 == 0:
|
|
239
|
+
print(f'Processed {msg_count} messages')
|
|
240
|
+
print('Generating data frame')
|
|
241
|
+
df = pd.DataFrame(data, columns=schema.header)
|
|
242
|
+
df.set_index('msg_timestamp', inplace=True)
|
|
243
|
+
return df
|
|
244
|
+
|
|
245
|
+
def has_messages_for_topic(self, topic: str) -> bool:
|
|
246
|
+
return topic in self.message_counts
|
|
247
|
+
|
|
248
|
+
def check_for_existing_data(self) -> bool:
|
|
249
|
+
if pathlib.Path.exists(self._pickle_file):
|
|
250
|
+
return True
|
|
251
|
+
return False
|
|
252
|
+
|
|
253
|
+
def to_dataframe(self, topics: Optional[Iterable[str]] = None,
|
|
254
|
+
start_time: Optional[int] = None, # [ns]
|
|
255
|
+
end_time: Optional[int] = None, # [ns]
|
|
256
|
+
rel_start_time: Optional[float] = None, # [s]
|
|
257
|
+
rel_end_time: Optional[float] = None, # [s]
|
|
258
|
+
fillna=False,
|
|
259
|
+
generate_relative_timestamps=True):
|
|
260
|
+
""" Generate a pandas.DataFrame from a list of topics
|
|
261
|
+
|
|
262
|
+
DataFrame field names will be prepended with the topic name
|
|
263
|
+
"""
|
|
264
|
+
if rel_start_time is not None:
|
|
265
|
+
start_time = self.message_start_time + int(1E9*rel_start_time)
|
|
266
|
+
if rel_end_time is not None:
|
|
267
|
+
end_time = self.message_start_time + int(1E9*rel_end_time)
|
|
268
|
+
|
|
269
|
+
if self._use_pickle_if_exists:
|
|
270
|
+
# Check if we already have the dataframe downloaded. If so, just use that, instead of re-parsing the MCAP.
|
|
271
|
+
if self.check_for_existing_data():
|
|
272
|
+
with open(self._pickle_file, 'rb') as f:
|
|
273
|
+
# The protocol version used is detected automatically, so we do not
|
|
274
|
+
# have to specify it.
|
|
275
|
+
mega_data_frame = pickle.load(f)
|
|
276
|
+
return mega_data_frame
|
|
277
|
+
else:
|
|
278
|
+
print(f'Data not found at {self._pickle_file}. Proceeding to MCAP parsing...')
|
|
279
|
+
|
|
280
|
+
if topics is None:
|
|
281
|
+
print(f'Expected message counts are {self.message_counts}')
|
|
282
|
+
else:
|
|
283
|
+
for topic in topics:
|
|
284
|
+
print(f'Expect {self.message_counts[topic]} messages for {topic}')
|
|
285
|
+
|
|
286
|
+
schemas = {}
|
|
287
|
+
data_tables = defaultdict(list)
|
|
288
|
+
msg_count = 0
|
|
289
|
+
for topic, msg, time in self.read_messages(topics=topics, start_time=start_time, end_time=end_time):
|
|
290
|
+
if topic not in schemas:
|
|
291
|
+
print(f'Trying to infer schema for {topic}')
|
|
292
|
+
# figure out how we are going to represent this type of message in the dataframe
|
|
293
|
+
schemas[topic] = MessageSchema(topic=topic, msg=msg, prefix_with_topic=True)
|
|
294
|
+
data_tables[topic].append(schemas[topic].generate_row(timestamp=time, msg=msg))
|
|
295
|
+
msg_count += 1
|
|
296
|
+
if msg_count % 10000 == 0:
|
|
297
|
+
print(f'Processed {msg_count} messages')
|
|
298
|
+
|
|
299
|
+
# create a dataframe for each topic, and then concatenate
|
|
300
|
+
print('Generating data frames')
|
|
301
|
+
dataframes = []
|
|
302
|
+
for topic in schemas:
|
|
303
|
+
dataframes.append(pd.DataFrame(data_tables[topic], columns=schemas[topic].header))
|
|
304
|
+
dataframes[-1].set_index('msg_timestamp', drop=False, inplace=True)
|
|
305
|
+
print('Concatenating data frames')
|
|
306
|
+
mega_data_frame = pd.concat(dataframes)
|
|
307
|
+
print('Sorting resultant data frame')
|
|
308
|
+
mega_data_frame.sort_index(inplace=True)
|
|
309
|
+
|
|
310
|
+
if generate_relative_timestamps:
|
|
311
|
+
print('Creating relative timestamps')
|
|
312
|
+
mega_data_frame['rel_timestamp'] = (mega_data_frame.index - self.message_start_time) / 1E9
|
|
313
|
+
mega_data_frame.set_index('rel_timestamp', drop=False, inplace=True)
|
|
314
|
+
if fillna:
|
|
315
|
+
print('Filling NaNs')
|
|
316
|
+
mega_data_frame.fillna(method='ffill', inplace=True)
|
|
317
|
+
|
|
318
|
+
if self._use_pickle_if_exists:
|
|
319
|
+
# If we've gotten here, it's because we didn't find an existing pickle, so let's create one for this data
|
|
320
|
+
with open(self._pickle_file, 'wb') as f:
|
|
321
|
+
pickle.dump(mega_data_frame, f, pickle.HIGHEST_PROTOCOL)
|
|
322
|
+
print(f'Stored data frame at: {self._pickle_file}')
|
|
323
|
+
|
|
324
|
+
print('Done!')
|
|
325
|
+
return mega_data_frame
|
|
Binary file
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
rosbag2_bagfile_information:
|
|
2
|
+
version: 5
|
|
3
|
+
storage_identifier: mcap
|
|
4
|
+
duration:
|
|
5
|
+
nanoseconds: 42730368608
|
|
6
|
+
starting_time:
|
|
7
|
+
nanoseconds_since_epoch: 1694016529830946819
|
|
8
|
+
message_count: 25387
|
|
9
|
+
topics_with_message_count:
|
|
10
|
+
- topic_metadata:
|
|
11
|
+
name: /visualizer/secondary_camera_pose
|
|
12
|
+
type: geometry_msgs/msg/Pose
|
|
13
|
+
serialization_format: cdr
|
|
14
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
15
|
+
message_count: 13
|
|
16
|
+
- topic_metadata:
|
|
17
|
+
name: /visualizer/primary_camera_pose
|
|
18
|
+
type: geometry_msgs/msg/Pose
|
|
19
|
+
serialization_format: cdr
|
|
20
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
21
|
+
message_count: 13
|
|
22
|
+
- topic_metadata:
|
|
23
|
+
name: /robot/sensor_status
|
|
24
|
+
type: capstan/msg/RobotSensorStatus
|
|
25
|
+
serialization_format: cdr
|
|
26
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
27
|
+
message_count: 76
|
|
28
|
+
- topic_metadata:
|
|
29
|
+
name: /device/state
|
|
30
|
+
type: capstan/msg/DeviceState
|
|
31
|
+
serialization_format: cdr
|
|
32
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
33
|
+
message_count: 3999
|
|
34
|
+
- topic_metadata:
|
|
35
|
+
name: /operator_interface/input_event
|
|
36
|
+
type: capstan/msg/StateMachineEvent
|
|
37
|
+
serialization_format: cdr
|
|
38
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
39
|
+
message_count: 0
|
|
40
|
+
- topic_metadata:
|
|
41
|
+
name: /reference_hardstops/_action/status
|
|
42
|
+
type: action_msgs/msg/GoalStatusArray
|
|
43
|
+
serialization_format: cdr
|
|
44
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
45
|
+
message_count: 0
|
|
46
|
+
- topic_metadata:
|
|
47
|
+
name: /robot/simulate_device_installed
|
|
48
|
+
type: std_msgs/msg/Bool
|
|
49
|
+
serialization_format: cdr
|
|
50
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
51
|
+
message_count: 0
|
|
52
|
+
- topic_metadata:
|
|
53
|
+
name: /system/state
|
|
54
|
+
type: std_msgs/msg/String
|
|
55
|
+
serialization_format: cdr
|
|
56
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
57
|
+
message_count: 189
|
|
58
|
+
- topic_metadata:
|
|
59
|
+
name: /fault/action
|
|
60
|
+
type: capstan/msg/FaultAction
|
|
61
|
+
serialization_format: cdr
|
|
62
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
63
|
+
message_count: 0
|
|
64
|
+
- topic_metadata:
|
|
65
|
+
name: /robot/event
|
|
66
|
+
type: std_msgs/msg/String
|
|
67
|
+
serialization_format: cdr
|
|
68
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
69
|
+
message_count: 0
|
|
70
|
+
- topic_metadata:
|
|
71
|
+
name: /reference_hardstops/_action/feedback
|
|
72
|
+
type: capstan/action/ReferenceHardstops_FeedbackMessage
|
|
73
|
+
serialization_format: cdr
|
|
74
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
75
|
+
message_count: 0
|
|
76
|
+
- topic_metadata:
|
|
77
|
+
name: /device/commanded_pose
|
|
78
|
+
type: capstan/msg/DevicePose
|
|
79
|
+
serialization_format: cdr
|
|
80
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
81
|
+
message_count: 3998
|
|
82
|
+
- topic_metadata:
|
|
83
|
+
name: /device/command
|
|
84
|
+
type: capstan/msg/StateMachineEvent
|
|
85
|
+
serialization_format: cdr
|
|
86
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
87
|
+
message_count: 6
|
|
88
|
+
- topic_metadata:
|
|
89
|
+
name: /device/status
|
|
90
|
+
type: capstan/msg/DeviceStatus
|
|
91
|
+
serialization_format: cdr
|
|
92
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
93
|
+
message_count: 3999
|
|
94
|
+
- topic_metadata:
|
|
95
|
+
name: /robot/joint_command
|
|
96
|
+
type: capstan/msg/RobotJointCommand
|
|
97
|
+
serialization_format: cdr
|
|
98
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
99
|
+
message_count: 3998
|
|
100
|
+
- topic_metadata:
|
|
101
|
+
name: /user_input/implant
|
|
102
|
+
type: capstan/msg/Implant
|
|
103
|
+
serialization_format: cdr
|
|
104
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
105
|
+
message_count: 0
|
|
106
|
+
- topic_metadata:
|
|
107
|
+
name: /presence/state
|
|
108
|
+
type: std_msgs/msg/String
|
|
109
|
+
serialization_format: cdr
|
|
110
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
111
|
+
message_count: 176
|
|
112
|
+
- topic_metadata:
|
|
113
|
+
name: /device/event
|
|
114
|
+
type: capstan/msg/StateMachineEvent
|
|
115
|
+
serialization_format: cdr
|
|
116
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
117
|
+
message_count: 2
|
|
118
|
+
- topic_metadata:
|
|
119
|
+
name: /user_input/implant_twist
|
|
120
|
+
type: geometry_msgs/msg/Twist
|
|
121
|
+
serialization_format: cdr
|
|
122
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
123
|
+
message_count: 0
|
|
124
|
+
- topic_metadata:
|
|
125
|
+
name: /rosout
|
|
126
|
+
type: rcl_interfaces/msg/Log
|
|
127
|
+
serialization_format: cdr
|
|
128
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 10\n nsec: 0\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
129
|
+
message_count: 166
|
|
130
|
+
- topic_metadata:
|
|
131
|
+
name: /user_input/pose_absolute
|
|
132
|
+
type: capstan/msg/DevicePose
|
|
133
|
+
serialization_format: cdr
|
|
134
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
135
|
+
message_count: 0
|
|
136
|
+
- topic_metadata:
|
|
137
|
+
name: /robot/joint_override
|
|
138
|
+
type: capstan/msg/RobotJointState
|
|
139
|
+
serialization_format: cdr
|
|
140
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
141
|
+
message_count: 0
|
|
142
|
+
- topic_metadata:
|
|
143
|
+
name: /user_input/lever
|
|
144
|
+
type: capstan/msg/Lever
|
|
145
|
+
serialization_format: cdr
|
|
146
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
147
|
+
message_count: 0
|
|
148
|
+
- topic_metadata:
|
|
149
|
+
name: /robot/joint_state
|
|
150
|
+
type: capstan/msg/RobotJointState
|
|
151
|
+
serialization_format: cdr
|
|
152
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
153
|
+
message_count: 4217
|
|
154
|
+
- topic_metadata:
|
|
155
|
+
name: /device/estimated_pose
|
|
156
|
+
type: capstan/msg/DevicePose
|
|
157
|
+
serialization_format: cdr
|
|
158
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
159
|
+
message_count: 3998
|
|
160
|
+
- topic_metadata:
|
|
161
|
+
name: /fault/detail
|
|
162
|
+
type: capstan/msg/FaultDetail
|
|
163
|
+
serialization_format: cdr
|
|
164
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
165
|
+
message_count: 85
|
|
166
|
+
- topic_metadata:
|
|
167
|
+
name: /joy
|
|
168
|
+
type: sensor_msgs/msg/Joy
|
|
169
|
+
serialization_format: cdr
|
|
170
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
171
|
+
message_count: 0
|
|
172
|
+
- topic_metadata:
|
|
173
|
+
name: /carriage_clutch/state
|
|
174
|
+
type: std_msgs/msg/String
|
|
175
|
+
serialization_format: cdr
|
|
176
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
177
|
+
message_count: 171
|
|
178
|
+
- topic_metadata:
|
|
179
|
+
name: /fault/config
|
|
180
|
+
type: capstan/msg/FaultConfig
|
|
181
|
+
serialization_format: cdr
|
|
182
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
183
|
+
message_count: 0
|
|
184
|
+
- topic_metadata:
|
|
185
|
+
name: /imu/camera_orientation
|
|
186
|
+
type: capstan/msg/CameraOrientation
|
|
187
|
+
serialization_format: cdr
|
|
188
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
189
|
+
message_count: 0
|
|
190
|
+
- topic_metadata:
|
|
191
|
+
name: /fault/status
|
|
192
|
+
type: capstan/msg/FaultStatus
|
|
193
|
+
serialization_format: cdr
|
|
194
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
195
|
+
message_count: 85
|
|
196
|
+
- topic_metadata:
|
|
197
|
+
name: /motionless_self_test/_action/feedback
|
|
198
|
+
type: capstan/action/MotionlessSelfTest_FeedbackMessage
|
|
199
|
+
serialization_format: cdr
|
|
200
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
201
|
+
message_count: 0
|
|
202
|
+
- topic_metadata:
|
|
203
|
+
name: /user/event
|
|
204
|
+
type: capstan/msg/StateMachineEvent
|
|
205
|
+
serialization_format: cdr
|
|
206
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
207
|
+
message_count: 1
|
|
208
|
+
- topic_metadata:
|
|
209
|
+
name: /joy/set_feebback
|
|
210
|
+
type: sensor_msgs/msg/JoyFeedback
|
|
211
|
+
serialization_format: cdr
|
|
212
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
213
|
+
message_count: 0
|
|
214
|
+
- topic_metadata:
|
|
215
|
+
name: /events/write_split
|
|
216
|
+
type: rosbag2_interfaces/msg/WriteSplitEvent
|
|
217
|
+
serialization_format: cdr
|
|
218
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
219
|
+
message_count: 0
|
|
220
|
+
- topic_metadata:
|
|
221
|
+
name: /user_input/angle_plane
|
|
222
|
+
type: capstan/msg/AnglePlane
|
|
223
|
+
serialization_format: cdr
|
|
224
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
225
|
+
message_count: 0
|
|
226
|
+
- topic_metadata:
|
|
227
|
+
name: /motionless_self_test/_action/status
|
|
228
|
+
type: action_msgs/msg/GoalStatusArray
|
|
229
|
+
serialization_format: cdr
|
|
230
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 1\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
231
|
+
message_count: 2
|
|
232
|
+
- topic_metadata:
|
|
233
|
+
name: /visualizer/device_pose
|
|
234
|
+
type: geometry_msgs/msg/Pose
|
|
235
|
+
serialization_format: cdr
|
|
236
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
237
|
+
message_count: 12
|
|
238
|
+
- topic_metadata:
|
|
239
|
+
name: /user_input/auto_relax
|
|
240
|
+
type: capstan/msg/AutoRelax
|
|
241
|
+
serialization_format: cdr
|
|
242
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
243
|
+
message_count: 0
|
|
244
|
+
- topic_metadata:
|
|
245
|
+
name: /operator_interface/state
|
|
246
|
+
type: std_msgs/msg/String
|
|
247
|
+
serialization_format: cdr
|
|
248
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
249
|
+
message_count: 171
|
|
250
|
+
- topic_metadata:
|
|
251
|
+
name: /parameter_events
|
|
252
|
+
type: rcl_interfaces/msg/ParameterEvent
|
|
253
|
+
serialization_format: cdr
|
|
254
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false\n- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
255
|
+
message_count: 9
|
|
256
|
+
- topic_metadata:
|
|
257
|
+
name: /operator_interface/event
|
|
258
|
+
type: capstan/msg/StateMachineEvent
|
|
259
|
+
serialization_format: cdr
|
|
260
|
+
offered_qos_profiles: "- history: 3\n depth: 0\n reliability: 1\n durability: 2\n deadline:\n sec: 9223372036\n nsec: 854775807\n lifespan:\n sec: 9223372036\n nsec: 854775807\n liveliness: 1\n liveliness_lease_duration:\n sec: 9223372036\n nsec: 854775807\n avoid_ros_namespace_conventions: false"
|
|
261
|
+
message_count: 1
|
|
262
|
+
compression_format: ""
|
|
263
|
+
compression_mode: ""
|
|
264
|
+
relative_file_paths:
|
|
265
|
+
- bagfile_0.mcap
|
|
266
|
+
files:
|
|
267
|
+
- path: bagfile_0.mcap
|
|
268
|
+
starting_time:
|
|
269
|
+
nanoseconds_since_epoch: 1694016529830946819
|
|
270
|
+
duration:
|
|
271
|
+
nanoseconds: 42730368608
|
|
272
|
+
message_count: 25387
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
|
7
|
+
import mcap_bag_parser # noqa: E402
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_always_passes():
|
|
14
|
+
assert True
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def parser():
|
|
19
|
+
return mcap_bag_parser.BagFileParser(os.path.join(SCRIPT_DIR, 'bagfile', 'bagfile_0.mcap'))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_read_messages():
|
|
23
|
+
num_msgs = defaultdict(lambda: 0)
|
|
24
|
+
for topic, msg, timestamp in mcap_bag_parser.read_messages(
|
|
25
|
+
os.path.join(SCRIPT_DIR, 'bagfile', 'bagfile_0.mcap')):
|
|
26
|
+
num_msgs[topic] = num_msgs[topic] + 1
|
|
27
|
+
# if topic in '/device/status':
|
|
28
|
+
# print(f"{topic} [{timestamp}]: '{msg}'")
|
|
29
|
+
|
|
30
|
+
print(f'Found {num_msgs}')
|
|
31
|
+
assert num_msgs['/rosout'] == 166
|
|
32
|
+
assert num_msgs['/parameter_events'] == 9
|
|
33
|
+
assert num_msgs['/robot/joint_command'] == 3998
|
|
34
|
+
assert num_msgs['/device/estimated_pose'] == 3998
|
|
35
|
+
assert num_msgs['/device/status'] == 3999
|
|
36
|
+
assert num_msgs['/device/state'] == 3999
|
|
37
|
+
assert num_msgs['/device/command'] == 6
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_read_messages_through_class(parser):
|
|
41
|
+
num_msgs = defaultdict(lambda: 0)
|
|
42
|
+
for topic, msg, timestamp in parser.read_messages():
|
|
43
|
+
num_msgs[topic] = num_msgs[topic] + 1
|
|
44
|
+
# if topic in '/device/status':
|
|
45
|
+
# print(f"{topic} [{timestamp}]: '{msg}'")
|
|
46
|
+
|
|
47
|
+
print(f'Found {num_msgs}')
|
|
48
|
+
assert num_msgs['/rosout'] == 166
|
|
49
|
+
assert num_msgs['/parameter_events'] == 9
|
|
50
|
+
assert num_msgs['/robot/joint_command'] == 3998
|
|
51
|
+
assert num_msgs['/device/estimated_pose'] == 3998
|
|
52
|
+
assert num_msgs['/device/status'] == 3999
|
|
53
|
+
assert num_msgs['/device/state'] == 3999
|
|
54
|
+
assert num_msgs['/device/command'] == 6
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_topic_to_dataframe(parser):
|
|
58
|
+
df = parser.topic_to_dataframe(topic='/device/command')
|
|
59
|
+
print(f'\n/device/command = \n{df}')
|
|
60
|
+
assert isinstance(df, pd.DataFrame)
|
|
61
|
+
assert len(df) == 6
|
|
62
|
+
assert list(df.columns) == ['arg', 'event']
|
|
63
|
+
|
|
64
|
+
df = parser.topic_to_dataframe(topic='/device/status')
|
|
65
|
+
print(f'\n/device/status = \n{df}')
|
|
66
|
+
assert isinstance(df, pd.DataFrame)
|
|
67
|
+
assert len(df) == 3999
|
|
68
|
+
assert 'active_driving_view' in df.columns
|
|
69
|
+
assert 'state' in df.columns
|
|
70
|
+
|
|
71
|
+
df = parser.topic_to_dataframe(topic='/robot/joint_command')
|
|
72
|
+
print(f'\n/robot/joint_command = \n{df}')
|
|
73
|
+
assert isinstance(df, pd.DataFrame)
|
|
74
|
+
assert len(df) == 3998
|
|
75
|
+
print(df.columns)
|
|
76
|
+
assert 'enable.C1_E' in df.columns
|
|
77
|
+
assert 'position.C3_ROLL' in df.columns
|
|
78
|
+
|
|
79
|
+
df = parser.topic_to_dataframe(topic='/device/estimated_pose')
|
|
80
|
+
print(f'\n/device/estimated_pose = \n{df}')
|
|
81
|
+
assert isinstance(df, pd.DataFrame)
|
|
82
|
+
assert len(df) == 3998
|
|
83
|
+
assert 'section.DISTAL.insertion' in df.columns
|
|
84
|
+
assert 'section.PROXIMAL.roll' in df.columns
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_to_dataframe(parser):
|
|
88
|
+
df = parser.to_dataframe(topics=['/device/command', '/device/status', '/robot/joint_command'])
|
|
89
|
+
print(f'\ncombined dataframe = \n{df}')
|
|
90
|
+
assert '/device/command.event' in df.columns
|
|
91
|
+
assert '/device/status.state' in df.columns
|
|
92
|
+
assert '/robot/joint_command.enable.C1_E' in df.columns
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_topics(parser):
|
|
96
|
+
topics = parser.topics
|
|
97
|
+
print(topics)
|
|
98
|
+
assert '/rosout' in topics
|
|
99
|
+
assert '/parameter_events' in topics
|
|
100
|
+
assert '/robot/joint_command' in topics
|
|
101
|
+
assert '/device/estimated_pose' in topics
|
|
102
|
+
assert '/device/status' in topics
|
|
103
|
+
assert '/device/state' in topics
|
|
104
|
+
assert '/device/estimated_pose' in topics
|
|
105
|
+
assert '/device/command' in topics
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_message_counts(parser):
|
|
109
|
+
message_counts = parser.message_counts
|
|
110
|
+
print(message_counts)
|
|
111
|
+
assert message_counts['/device/command'] == 6
|
|
112
|
+
assert message_counts['/device/estimated_pose'] == 3998
|
|
113
|
+
assert message_counts['/device/status'] == 3999
|