SegmentRangeReader: new format for reading multiple segments (#30940)
* segment range reader
* rename that
* revert that
* cleanup
* revert this for now
* revert this for now
* Fix + test
* rm that
* rm that
* use for auto_fingerprint
* simpler
* for notebook too
* match numpy indexing
* just use numpy directly
* remove that
* spacing
* spacing
* use qlog for auto fingerprint
* add 'read mode'
* pass in read mode
* add test for modes
* numpy indexing
* fix that case
* more examples
* fix the notebook
* cleanup the notebook
* cleaner
* fix those
old-commit-hash: 0d126e1e9e
chrysler-long2
parent
312e786542
commit
1f434b2714
6 changed files with 177 additions and 14 deletions
@ -0,0 +1,81 @@ |
|||||||
|
import enum |
||||||
|
import re |
||||||
|
import numpy as np |
||||||
|
from openpilot.selfdrive.test.openpilotci import get_url |
||||||
|
from openpilot.tools.lib.helpers import RE |
||||||
|
from openpilot.tools.lib.logreader import LogReader |
||||||
|
from openpilot.tools.lib.route import Route, SegmentRange |
||||||
|
|
||||||
|
class ReadMode(enum.Enum): |
||||||
|
RLOG = 0 # only read rlogs |
||||||
|
QLOG = 1 # only read qlogs |
||||||
|
#AUTO = 2 # default to rlogs, fallback to qlogs, not supported yet |
||||||
|
|
||||||
|
|
||||||
|
def create_slice_from_string(s: str): |
||||||
|
m = re.fullmatch(RE.SLICE, s) |
||||||
|
assert m is not None, f"Invalid slice: {s}" |
||||||
|
start, end, step = m.groups() |
||||||
|
start = int(start) if start is not None else None |
||||||
|
end = int(end) if end is not None else None |
||||||
|
step = int(step) if step is not None else None |
||||||
|
|
||||||
|
if start is not None and ":" not in s and end is None and step is None: |
||||||
|
return start |
||||||
|
return slice(start, end, step) |
||||||
|
|
||||||
|
|
||||||
|
def parse_slice(sr: SegmentRange): |
||||||
|
route = Route(sr.route_name) |
||||||
|
segs = np.arange(route.max_seg_number+1) |
||||||
|
s = create_slice_from_string(sr._slice) |
||||||
|
return segs[s] if isinstance(s, slice) else [segs[s]] |
||||||
|
|
||||||
|
def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG): |
||||||
|
segs = parse_slice(sr) |
||||||
|
route = Route(sr.route_name) |
||||||
|
|
||||||
|
log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() |
||||||
|
|
||||||
|
for seg in segs: |
||||||
|
yield LogReader(log_paths[seg]) |
||||||
|
|
||||||
|
def internal_source(sr: SegmentRange, mode=ReadMode.RLOG): |
||||||
|
segs = parse_slice(sr) |
||||||
|
|
||||||
|
for seg in segs: |
||||||
|
yield LogReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2") |
||||||
|
|
||||||
|
def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG): |
||||||
|
segs = parse_slice(sr) |
||||||
|
|
||||||
|
for seg in segs: |
||||||
|
yield LogReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog')) |
||||||
|
|
||||||
|
def auto_source(sr: SegmentRange, mode=ReadMode.RLOG): |
||||||
|
# Automatically determine viable source |
||||||
|
|
||||||
|
try: |
||||||
|
next(internal_source(sr, mode)) |
||||||
|
return internal_source(sr, mode) |
||||||
|
except Exception: |
||||||
|
pass |
||||||
|
|
||||||
|
try: |
||||||
|
next(openpilotci_source(sr, mode)) |
||||||
|
return openpilotci_source(sr, mode) |
||||||
|
except Exception: |
||||||
|
pass |
||||||
|
|
||||||
|
return comma_api_source(sr, mode) |
||||||
|
|
||||||
|
|
||||||
|
class SegmentRangeReader: |
||||||
|
def __init__(self, segment_range: str, mode=ReadMode.RLOG, source=auto_source): |
||||||
|
sr = SegmentRange(segment_range) |
||||||
|
self.lrs = source(sr, mode) |
||||||
|
|
||||||
|
def __iter__(self): |
||||||
|
for lr in self.lrs: |
||||||
|
for m in lr: |
||||||
|
yield m |
@ -0,0 +1,63 @@ |
|||||||
|
import numpy as np |
||||||
|
import unittest |
||||||
|
from parameterized import parameterized |
||||||
|
|
||||||
|
from openpilot.tools.lib.route import SegmentRange |
||||||
|
from openpilot.tools.lib.srreader import ReadMode, SegmentRangeReader, parse_slice |
||||||
|
|
||||||
|
NUM_SEGS = 17 # number of segments in the test route |
||||||
|
ALL_SEGS = list(np.arange(NUM_SEGS)) |
||||||
|
TEST_ROUTE = "344c5c15b34f2d8a/2024-01-03--09-37-12" |
||||||
|
|
||||||
|
class TestSegmentRangeReader(unittest.TestCase): |
||||||
|
@parameterized.expand([ |
||||||
|
(f"{TEST_ROUTE}", ALL_SEGS), |
||||||
|
(f"{TEST_ROUTE.replace('/', '|')}", ALL_SEGS), |
||||||
|
(f"{TEST_ROUTE}--0", [0]), |
||||||
|
(f"{TEST_ROUTE}--5", [5]), |
||||||
|
(f"{TEST_ROUTE}/0", [0]), |
||||||
|
(f"{TEST_ROUTE}/5", [5]), |
||||||
|
(f"{TEST_ROUTE}/0:10", ALL_SEGS[0:10]), |
||||||
|
(f"{TEST_ROUTE}/0:0", []), |
||||||
|
(f"{TEST_ROUTE}/4:6", ALL_SEGS[4:6]), |
||||||
|
(f"{TEST_ROUTE}/0:-1", ALL_SEGS[0:-1]), |
||||||
|
(f"{TEST_ROUTE}/:5", ALL_SEGS[:5]), |
||||||
|
(f"{TEST_ROUTE}/2:", ALL_SEGS[2:]), |
||||||
|
(f"{TEST_ROUTE}/2:-1", ALL_SEGS[2:-1]), |
||||||
|
(f"{TEST_ROUTE}/-1", [ALL_SEGS[-1]]), |
||||||
|
(f"{TEST_ROUTE}/-2", [ALL_SEGS[-2]]), |
||||||
|
(f"{TEST_ROUTE}/-2:-1", ALL_SEGS[-2:-1]), |
||||||
|
(f"{TEST_ROUTE}/-4:-2", ALL_SEGS[-4:-2]), |
||||||
|
(f"{TEST_ROUTE}/:10:2", ALL_SEGS[:10:2]), |
||||||
|
(f"{TEST_ROUTE}/5::2", ALL_SEGS[5::2]), |
||||||
|
]) |
||||||
|
def test_parse_slice(self, segment_range, expected): |
||||||
|
sr = SegmentRange(segment_range) |
||||||
|
segs = parse_slice(sr) |
||||||
|
self.assertListEqual(list(segs), expected) |
||||||
|
|
||||||
|
@parameterized.expand([ |
||||||
|
(f"{TEST_ROUTE}//",), |
||||||
|
(f"{TEST_ROUTE}---",), |
||||||
|
(f"{TEST_ROUTE}/-4:--2",), |
||||||
|
(f"{TEST_ROUTE}/-a",), |
||||||
|
(f"{TEST_ROUTE}/0:1:2:3",), |
||||||
|
(f"{TEST_ROUTE}/:::3",), |
||||||
|
]) |
||||||
|
def test_bad_ranges(self, segment_range): |
||||||
|
with self.assertRaises(AssertionError): |
||||||
|
sr = SegmentRange(segment_range) |
||||||
|
parse_slice(sr) |
||||||
|
|
||||||
|
@parameterized.expand([ |
||||||
|
(ReadMode.QLOG, 11643), |
||||||
|
(ReadMode.RLOG, 70577), |
||||||
|
]) |
||||||
|
def test_modes(self, mode, expected): |
||||||
|
lr = SegmentRangeReader(TEST_ROUTE+"/0", mode) |
||||||
|
|
||||||
|
self.assertEqual(len(list(lr)), expected) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
unittest.main() |
Loading…
Reference in new issue