tools/Rerun: Add video logging features (#32810)
* working
* multiprocessing
* fix that
* print services
* all services + fix
* less verbose
* start readme
* segment range
* cleanup
* update readme + fix bug in 'all'
* cleanup + update readme
* update readme
* cleanup
* cleanup
* rm frame_iter
* cleanup
* staticmethod
* proc kill
* split files
* fix range with hevc vids
* update reamde + add prompt
* readme
* readme
* readme
old-commit-hash: 5e0aff92ae
pull/33302/head
parent
8a16d47e7e
commit
41ee057acd
3 changed files with 299 additions and 64 deletions
@ -0,0 +1,57 @@ |
|||||||
|
# Rerun |
||||||
|
Rerun is a tool to quickly visualize time series data. It supports all openpilot logs , both the `logMessages` and video logs. |
||||||
|
|
||||||
|
[Instructions](https://rerun.io/docs/reference/viewer/overview) for navigation within the Rerun Viewer. |
||||||
|
|
||||||
|
## Usage |
||||||
|
``` |
||||||
|
usage: run.py [-h] [--demo] [--qcam] [--fcam] [--ecam] [--dcam] [--print_services] [--services [SERVICES ...]] [route_or_segment_name] |
||||||
|
|
||||||
|
A helper to run rerun on openpilot routes |
||||||
|
|
||||||
|
options: |
||||||
|
-h, --help show this help message and exit |
||||||
|
--demo Use the demo route instead of providing one (default: False) |
||||||
|
--qcam Log decimated driving camera (default: False) |
||||||
|
--fcam Log driving camera (default: False) |
||||||
|
--ecam Log wide camera (default: False) |
||||||
|
--dcam Log driver monitoring camera (default: False) |
||||||
|
--print_services List out openpilot services (default: False) |
||||||
|
--services [SERVICES ...] Specify openpilot services that will be logged. No service will be logged if not specified. |
||||||
|
To log all services include 'all' as one of your services (default: []) |
||||||
|
--route [ROUTE] The route or segment name to plot (default: None) |
||||||
|
``` |
||||||
|
|
||||||
|
Examples using route name to observe accelerometer and qcamera: |
||||||
|
|
||||||
|
`./run.py --services accelerometer --qcam --route "a2a0ccea32023010/2023-07-27--13-01-19"` |
||||||
|
|
||||||
|
Examples using segment range (more on [SegmentRange](https://github.com/commaai/openpilot/tree/master/tools/lib)): |
||||||
|
|
||||||
|
`./run.py --qcam --route "a2a0ccea32023010/2023-07-27--13-01-19/2:4"` |
||||||
|
|
||||||
|
## Cautions: |
||||||
|
- You can specify `--services all` to visualize all `logMessage`, but it will draw a lot of memory usage and take a long time to log all messages. Rerun isn't ready for logging big number of data. |
||||||
|
|
||||||
|
- Logging hevc videos (`--fcam`, `--ecam`, and `--dcam`) are expensive, and it's recommended to use `--qcam` for optimized performance. If possible, limiting your route to a few segments using `SegmentRange` will speed up logging and reduce memory usage |
||||||
|
|
||||||
|
This example draws 13GB of memory: |
||||||
|
|
||||||
|
`./run.py --services accelerometer --qcam --route "a2a0ccea32023010/2023-07-27--13-01-19"` |
||||||
|
|
||||||
|
|
||||||
|
## Openpilot services |
||||||
|
To list all openpilot services: |
||||||
|
|
||||||
|
`./run.py --print_services` |
||||||
|
|
||||||
|
Examples including openpilot services: |
||||||
|
|
||||||
|
`./run.py --services accelerometer cameraodometry --route "a2a0ccea32023010/2023-07-27--13-01-19/0/q"` |
||||||
|
|
||||||
|
Examples including all services: |
||||||
|
|
||||||
|
`./run.py --services all --route "a2a0ccea32023010/2023-07-27--13-01-19/0/q"` |
||||||
|
|
||||||
|
## Demo |
||||||
|
`./run.py --services accelerometer carcontrol caroutput --qcam --demo` |
@ -0,0 +1,92 @@ |
|||||||
|
import tqdm |
||||||
|
import subprocess |
||||||
|
import multiprocessing |
||||||
|
from enum import StrEnum |
||||||
|
from functools import partial |
||||||
|
from collections import namedtuple |
||||||
|
|
||||||
|
from openpilot.tools.lib.framereader import ffprobe |
||||||
|
|
||||||
|
CameraConfig = namedtuple("CameraConfig", ["qcam", "fcam", "ecam", "dcam"]) |
||||||
|
|
||||||
|
class CameraType(StrEnum): |
||||||
|
qcam = "qcamera" |
||||||
|
fcam = "fcamera" |
||||||
|
ecam = "ecamera" |
||||||
|
dcam = "dcamera" |
||||||
|
|
||||||
|
|
||||||
|
def probe_packet_info(camera_path): |
||||||
|
args = ["ffprobe", "-v", "quiet", "-show_packets", "-probesize", "10M", camera_path] |
||||||
|
dat = subprocess.check_output(args) |
||||||
|
dat = dat.decode().split() |
||||||
|
return dat |
||||||
|
|
||||||
|
|
||||||
|
class _FrameReader: |
||||||
|
def __init__(self, camera_path, segment, h, w, start_time): |
||||||
|
self.camera_path = camera_path |
||||||
|
self.segment = segment |
||||||
|
self.h = h |
||||||
|
self.w = w |
||||||
|
self.start_time = start_time |
||||||
|
|
||||||
|
self.ts = self._get_ts() |
||||||
|
|
||||||
|
def _read_stream_nv12(self): |
||||||
|
frame_sz = self.w * self.h * 3 // 2 |
||||||
|
proc = subprocess.Popen( |
||||||
|
["ffmpeg", "-v", "quiet", "-i", self.camera_path, "-f", "rawvideo", "-pix_fmt", "nv12", "-"], |
||||||
|
stdout=subprocess.PIPE, |
||||||
|
stderr=subprocess.DEVNULL |
||||||
|
) |
||||||
|
try: |
||||||
|
while True: |
||||||
|
dat = proc.stdout.read(frame_sz) |
||||||
|
if len(dat) == 0: |
||||||
|
break |
||||||
|
yield dat |
||||||
|
finally: |
||||||
|
proc.kill() |
||||||
|
|
||||||
|
def _get_ts(self): |
||||||
|
dat = probe_packet_info(self.camera_path) |
||||||
|
try: |
||||||
|
ret = [float(d.split('=')[1]) for d in dat if d.startswith("pts_time=")] |
||||||
|
except ValueError: |
||||||
|
# pts_times aren't available. Infer timestamps from duration_times |
||||||
|
ret = [d for d in dat if d.startswith("duration_time")] |
||||||
|
ret = [float(d.split('=')[1])*(i+1)+(self.segment*60)+self.start_time for i, d in enumerate(ret)] |
||||||
|
return ret |
||||||
|
|
||||||
|
def __iter__(self): |
||||||
|
for i, frame in enumerate(self._read_stream_nv12()): |
||||||
|
yield self.ts[i], frame |
||||||
|
|
||||||
|
|
||||||
|
class CameraReader: |
||||||
|
def __init__(self, camera_paths, start_time, seg_idxs): |
||||||
|
self.seg_idxs = seg_idxs |
||||||
|
self.camera_paths = camera_paths |
||||||
|
self.start_time = start_time |
||||||
|
|
||||||
|
probe = ffprobe(camera_paths[0])["streams"][0] |
||||||
|
self.h = probe["height"] |
||||||
|
self.w = probe["width"] |
||||||
|
|
||||||
|
self.__frs = {} |
||||||
|
|
||||||
|
def _get_fr(self, i): |
||||||
|
if i not in self.__frs: |
||||||
|
self.__frs[i] = _FrameReader(self.camera_paths[i], segment=i, h=self.h, w=self.w, start_time=self.start_time) |
||||||
|
return self.__frs[i] |
||||||
|
|
||||||
|
def _run_on_segment(self, func, i): |
||||||
|
return func(self._get_fr(i)) |
||||||
|
|
||||||
|
def run_across_segments(self, num_processes, func): |
||||||
|
with multiprocessing.Pool(num_processes) as pool: |
||||||
|
num_segs = len(self.seg_idxs) |
||||||
|
for _ in tqdm.tqdm(pool.imap_unordered(partial(self._run_on_segment, func), self.seg_idxs), total=num_segs): |
||||||
|
continue |
||||||
|
|
Loading…
Reference in new issue