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