cabana: improve timeline (#28801)

old-commit-hash: 059529cae2
beeps
Dean Lee 2 years ago committed by GitHub
parent 6b5ca3e86b
commit 841f8bfb3f
  1. 2
      tools/cabana/chart/chart.cc
  2. 2
      tools/cabana/mainwin.cc
  3. 2
      tools/cabana/streams/abstractstream.h
  4. 2
      tools/cabana/streams/replaystream.h
  5. 112
      tools/cabana/videowidget.cc
  6. 14
      tools/cabana/videowidget.h
  7. 4
      tools/replay/consoleui.cc
  8. 6
      tools/replay/replay.h

@ -484,7 +484,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
if (rubber->width() <= 0) {
// no rubber dragged, seek to mouse position
can->seekTo(min);
} else if (rubber->width() > 10) {
} else if (rubber->width() > 10 && (max - min) > 0.01) { // Minimum range is 10 milliseconds.
charts_widget->zoom_undo_stack->push(new ZoomCommand(charts_widget, {min, max}));
} else {
viewport()->update();

@ -190,7 +190,7 @@ void MainWindow::createDockWidgets() {
video_splitter = new QSplitter(Qt::Vertical, this);
video_widget = new VideoWidget(this);
video_splitter->addWidget(video_widget);
QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged);
QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::updateTimeRange);
video_splitter->addWidget(charts_container);
video_splitter->setStretchFactor(1, 1);

@ -57,7 +57,7 @@ public:
virtual void pause(bool pause) {}
const std::vector<const CanEvent *> &allEvents() const { return all_events_; }
const std::vector<const CanEvent *> &events(const MessageId &id) const;
virtual const std::vector<std::tuple<int, int, TimelineType>> getTimeline() { return {}; }
virtual const std::vector<std::tuple<double, double, TimelineType>> getTimeline() { return {}; }
signals:
void paused();

@ -23,7 +23,7 @@ public:
inline float getSpeed() const { return replay->getSpeed(); }
inline bool isPaused() const override { return replay->isPaused(); }
void pause(bool pause) override;
inline const std::vector<std::tuple<int, int, TimelineType>> getTimeline() override { return replay->getTimeline(); }
inline const std::vector<std::tuple<double, double, TimelineType>> getTimeline() override { return replay->getTimeline(); }
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
private:

@ -40,7 +40,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QFrame(parent) {
for (float speed : {0.1, 0.5, 1., 2.}) {
QPushButton *btn = new QPushButton(QString("%1x").arg(speed), this);
btn->setCheckable(true);
QObject::connect(btn, &QPushButton::clicked, [=]() { can->setSpeed(speed); });
QObject::connect(btn, &QPushButton::clicked, [speed]() { can->setSpeed(speed); });
control_layout->addWidget(btn);
group->addButton(btn);
if (speed == 1.0) btn->setChecked(true);
@ -91,21 +91,19 @@ QWidget *VideoWidget::createCameraWidget() {
// slider controls
auto slider_layout = new QHBoxLayout();
time_label = new QLabel("00:00");
slider_layout->addWidget(time_label);
slider_layout->addWidget(time_label = new QLabel("00:00"));
slider = new Slider(this);
slider->setSingleStep(0);
slider_layout->addWidget(slider);
end_time_label = new QLabel(this);
slider_layout->addWidget(end_time_label);
slider_layout->addWidget(end_time_label = new QLabel(this));
l->addLayout(slider_layout);
setMaximumTime(can->totalSeconds());
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); });
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(value / 1000)); });
QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime);
QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->currentSecond()); });
QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(utils::formatSeconds(slider->currentSecond())); });
QObject::connect(slider, &Slider::updateMaximumTime, this, &VideoWidget::setMaximumTime, Qt::QueuedConnection);
QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); });
QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState);
return w;
@ -114,10 +112,10 @@ QWidget *VideoWidget::createCameraWidget() {
void VideoWidget::setMaximumTime(double sec) {
maximum_time = sec;
end_time_label->setText(utils::formatSeconds(sec));
slider->setRange(0, sec * 1000);
slider->setTimeRange(0, sec);
}
void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) {
void VideoWidget::updateTimeRange(double min, double max, bool is_zoomed) {
if (can->liveStreaming()) return;
if (!is_zoomed) {
@ -125,21 +123,14 @@ void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) {
max = maximum_time;
}
end_time_label->setText(utils::formatSeconds(max));
slider->setRange(min * 1000, max * 1000);
slider->setTimeRange(min, max);
}
void VideoWidget::updateState() {
if (!slider->isSliderDown()) {
slider->setValue(can->currentSec() * 1000);
}
std::lock_guard lk(slider->thumbnail_lock);
uint64_t mono_time = (can->currentSec() + can->routeStartTime()) * 1e9;
auto it = slider->alerts.lower_bound(mono_time);
if (it != slider->alerts.end() && (it->first - mono_time) < 1e9) {
alert_label->showAlert(it->second);
} else {
alert_label->showAlert({});
slider->setCurrentSecond(can->currentSec());
}
alert_label->showAlert(slider->alertInfo(can->currentSec()));
}
void VideoWidget::updatePlayBtnState() {
@ -148,6 +139,7 @@ void VideoWidget::updatePlayBtnState() {
}
// Slider
Slider::Slider(QWidget *parent) : thumbnail_label(parent), QSlider(Qt::Horizontal, parent) {
setMouseTracking(true);
auto timer = new QTimer(this);
@ -171,6 +163,26 @@ Slider::~Slider() {
}
}
AlertInfo Slider::alertInfo(double seconds) {
std::lock_guard lk(thumbnail_lock);
uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9;
auto alert_it = alerts.lower_bound(mono_time);
bool has_alert = (alert_it != alerts.end()) && ((alert_it->first - mono_time) <= 1e8);
return has_alert ? alert_it->second : AlertInfo{};
}
QPixmap Slider::thumbnail(double seconds) {
std::lock_guard lk(thumbnail_lock);
uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9;
auto it = thumbnails.lowerBound(mono_time);
return it != thumbnails.end() ? it.value() : QPixmap();
}
void Slider::setTimeRange(double min, double max) {
assert(min < max);
setRange(min * factor, max * factor);
}
void Slider::parseQLog() {
const auto &segments = can->route()->segments();
for (auto it = segments.rbegin(); it != segments.rend() && !abort_parse_qlog; ++it) {
@ -178,7 +190,7 @@ void Slider::parseQLog() {
std::string qlog = it->second.qlog.toStdString();
if (!qlog.empty() && log.load(qlog, &abort_parse_qlog, {cereal::Event::Which::THUMBNAIL, cereal::Event::Which::CONTROLS_STATE}, true, 0, 3)) {
if (it == segments.rbegin() && !log.events.empty()) {
double max_time = (*(log.events.rbegin()))->mono_time / 1e9 - can->routeStartTime();
double max_time = log.events.back()->mono_time / 1e9 - can->routeStartTime();
emit updateMaximumTime(max_time);
}
for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_parse_qlog; ++ev) {
@ -192,7 +204,8 @@ void Slider::parseQLog() {
}
} else if ((*ev)->which == cereal::Event::Which::CONTROLS_STATE) {
auto cs = (*ev)->event.getControlsState();
if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0) {
if (cs.getAlertType().size() > 0 && cs.getAlertText1().size() > 0 &&
cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE) {
std::lock_guard lk(thumbnail_lock);
alerts.emplace((*ev)->mono_time, AlertInfo{cs.getAlertStatus(), cs.getAlertText1().cStr(), cs.getAlertText2().cStr()});
}
@ -202,30 +215,18 @@ void Slider::parseQLog() {
}
}
void Slider::sliderChange(QAbstractSlider::SliderChange change) {
if (change == QAbstractSlider::SliderValueChange) {
int x = width() * ((value() - minimum()) / double(maximum() - minimum()));
if (x != slider_x) {
slider_x = x;
update();
}
} else {
QAbstractSlider::sliderChange(change);
}
}
void Slider::paintEvent(QPaintEvent *ev) {
QPainter p(this);
QRect r = rect().adjusted(0, 4, 0, -4);
p.fillRect(r, timeline_colors[(int)TimelineType::None]);
double min = minimum() / 1000.0;
double max = maximum() / 1000.0;
double min = minimum() / factor;
double max = maximum() / factor;
for (auto [begin, end, type] : timeline) {
if (begin > max || end < min)
continue;
r.setLeft(((std::max(min, (double)begin) - min) / (max - min)) * width());
r.setRight(((std::min(max, (double)end) - min) / (max - min)) * width());
r.setLeft(((std::max(min, begin) - min) / (max - min)) * width());
r.setRight(((std::min(max, end) - min) / (max - min)) * width());
p.fillRect(r, timeline_colors[(int)type]);
}
@ -248,23 +249,16 @@ void Slider::mousePressEvent(QMouseEvent *e) {
}
void Slider::mouseMoveEvent(QMouseEvent *e) {
QPixmap thumb;
AlertInfo alert;
int pos = std::clamp(e->pos().x(), 0, width());
double seconds = (minimum() + pos * ((maximum() - minimum()) / (double)width())) / 1000.0;
{
std::lock_guard lk(thumbnail_lock);
uint64_t mono_time = (seconds + can->routeStartTime()) * 1e9;
auto it = thumbnails.lowerBound(mono_time);
if (it != thumbnails.end()) thumb = it.value();
auto alert_it = alerts.lower_bound(mono_time);
if (alert_it != alerts.end() && (alert_it->first - mono_time) < 1e9) {
alert = alert_it->second;
}
double seconds = (minimum() + pos * ((maximum() - minimum()) / (double)width())) / factor;
QPixmap thumb = thumbnail(seconds);
if (!thumb.isNull()) {
int x = std::clamp(pos - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN);
int y = -thumb.height();
thumbnail_label.showPixmap(mapToParent({x, y}), utils::formatSeconds(seconds), thumb, alertInfo(seconds));
} else {
thumbnail_label.hide();
}
int x = std::clamp(pos - thumb.width() / 2, THUMBNAIL_MARGIN, rect().right() - thumb.width() - THUMBNAIL_MARGIN);
int y = -thumb.height();
thumbnail_label.showPixmap(mapToParent({x, y}), utils::formatSeconds(seconds), thumb, alert);
QSlider::mouseMoveEvent(e);
}
@ -291,15 +285,13 @@ InfoLabel::InfoLabel(QWidget *parent) : QWidget(parent, Qt::WindowStaysOnTopHint
}
void InfoLabel::showPixmap(const QPoint &pt, const QString &sec, const QPixmap &pm, const AlertInfo &alert) {
pixmap = pm;
second = sec;
pixmap = pm;
alert_info = alert;
setVisible(!pm.isNull());
if (isVisible()) {
resize(pm.size());
move(pt);
update();
}
resize(pm.size());
move(pt);
setVisible(true);
update();
}
void InfoLabel::showAlert(const AlertInfo &alert) {

@ -34,6 +34,11 @@ class Slider : public QSlider {
public:
Slider(QWidget *parent);
~Slider();
double currentSecond() const { return value() / factor; }
void setCurrentSecond(double sec) { setValue(sec * factor); }
void setTimeRange(double min, double max);
AlertInfo alertInfo(double sec);
QPixmap thumbnail(double sec);
signals:
void updateMaximumTime(double);
@ -42,20 +47,17 @@ private:
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
bool event(QEvent *event) override;
void sliderChange(QAbstractSlider::SliderChange change) override;
void paintEvent(QPaintEvent *ev) override;
void parseQLog();
double max_sec = 0;
int slider_x = -1;
std::vector<std::tuple<int, int, TimelineType>> timeline;
const double factor = 1000.0;
std::vector<std::tuple<double, double, TimelineType>> timeline;
std::mutex thumbnail_lock;
std::atomic<bool> abort_parse_qlog = false;
QMap<uint64_t, QPixmap> thumbnails;
std::map<uint64_t, AlertInfo> alerts;
std::unique_ptr<QFuture<void>> qlog_future;
InfoLabel thumbnail_label;
friend class VideoWidget;
};
class VideoWidget : public QFrame {
@ -63,7 +65,7 @@ class VideoWidget : public QFrame {
public:
VideoWidget(QWidget *parnet = nullptr);
void rangeChanged(double min, double max, bool is_zommed);
void updateTimeRange(double min, double max, bool is_zommed);
void setMaximumTime(double sec);
protected:

@ -259,8 +259,8 @@ void ConsoleUI::updateTimeline() {
const int total_sec = replay->totalSeconds();
for (auto [begin, end, type] : replay->getTimeline()) {
int start_pos = ((double)begin / total_sec) * width;
int end_pos = ((double)end / total_sec) * width;
int start_pos = (begin / total_sec) * width;
int end_pos = (end / total_sec) * width;
if (type == TimelineType::Engaged) {
mvwchgat(win, 1, start_pos, end_pos - start_pos + 1, A_COLOR, Color::Engaged, NULL);
mvwchgat(win, 2, start_pos, end_pos - start_pos + 1, A_COLOR, Color::Engaged, NULL);

@ -67,14 +67,14 @@ public:
inline double currentSeconds() const { return double(cur_mono_time_ - route_start_ts_) / 1e9; }
inline QDateTime currentDateTime() const { return route_->datetime().addSecs(currentSeconds()); }
inline uint64_t routeStartTime() const { return route_start_ts_; }
inline int toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; }
inline double toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; }
inline int totalSeconds() const { return (!segments_.empty()) ? (segments_.rbegin()->first + 1) * 60 : 0; }
inline void setSpeed(float speed) { speed_ = speed; }
inline float getSpeed() const { return speed_; }
inline const std::vector<Event *> *events() const { return events_.get(); }
inline const std::map<int, std::unique_ptr<Segment>> &segments() const { return segments_; };
inline const std::string &carFingerprint() const { return car_fingerprint_; }
inline const std::vector<std::tuple<int, int, TimelineType>> getTimeline() {
inline const std::vector<std::tuple<double, double, TimelineType>> getTimeline() {
std::lock_guard lk(timeline_lock);
return timeline;
}
@ -131,7 +131,7 @@ protected:
std::mutex timeline_lock;
QFuture<void> timeline_future;
std::vector<std::tuple<int, int, TimelineType>> timeline;
std::vector<std::tuple<double, double, TimelineType>> timeline;
std::set<cereal::Event::Which> allow_list;
std::string car_fingerprint_;
float speed_ = 1.0;

Loading…
Cancel
Save