12#include <QRegularExpression>
17#include "pluginlib/class_list_macros.hpp"
18#include "rviz_common/display_context.hpp"
24using RequestSamples = autonomy_datasets_msgs::srv::RequestSamples;
27const char*
const kDefaultServiceName =
"/datasets/request_samples";
30constexpr int kAvailabilityPollIntervalMs = 500;
33constexpr int kMaximumStepSize = 100000;
50 tr(
"Name of the 'request_samples' service of the dataset node, as reported by 'ros2 service list'"));
52 auto* service_layout =
new QFormLayout();
56 pause_button_->setToolTip(tr(
"Stop the playback at the current sample, without publishing another one"));
59 tr(
"Publish all remaining samples of the dataset.\n"
60 "The dataset node answers this request only once the dataset ends, so the playback cannot be paused again "
63 auto* transport_layout =
new QHBoxLayout();
72 step_button_->setToolTip(tr(
"Publish the next samples, whichever they are"));
74 auto* step_layout =
new QHBoxLayout();
76 step_layout->addWidget(
new QLabel(tr(
"sample(s)"),
this));
77 step_layout->addStretch();
83 tr(
"IDs of the samples to publish, skipping every sample in between.\n"
84 "The playback is forward-only, so it cannot go back to a sample it has already passed."));
86 skip_button_->setToolTip(tr(
"Skip ahead to the entered samples"));
88 auto* skip_layout =
new QHBoxLayout();
92 auto* separator =
new QFrame(
this);
93 separator->setFrameShape(QFrame::HLine);
94 separator->setFrameShadow(QFrame::Sunken);
101 label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
102 label->installEventFilter(
this);
105 auto* layout =
new QVBoxLayout();
106 layout->addLayout(service_layout);
107 layout->addLayout(transport_layout);
108 layout->addLayout(step_layout);
109 layout->addLayout(skip_layout);
110 layout->addWidget(separator);
113 layout->addStretch();
126 connect(
step_size_spin_, QOverload<int>::of(&QSpinBox::valueChanged),
this, &PlaybackPanel::configChanged);
133 showStatus(tr(
"Not connected to a dataset node yet"));
138 if (event->type() == QEvent::Resize) {
145 return rviz_common::Panel::eventFilter(watched, event);
159 const QString shown = label->fontMetrics().elidedText(text, Qt::ElideRight, label->contentsRect().width());
160 if (shown != label->text()) {
162 label->setText(shown);
165 label->setToolTip(text.isEmpty() ? QString() : QString(
"<qt>%1</qt>").arg(text.toHtmlEscaped()));
169 node_ = getDisplayContext()->getRosNodeAbstraction().lock()->get_raw_node();
175 rviz_common::Panel::load(config);
177 QString service_name;
178 if (config.mapGetString(
"ServiceName", &service_name) && !service_name.isEmpty()) {
181 float step_size = 0.0F;
182 if (config.mapGetFloat(
"StepSize", &step_size) && step_size >= 1.0F) {
185 if (
node_ !=
nullptr) {
191 rviz_common::Panel::save(config);
198 if (
node_ ==
nullptr || service_name.isEmpty()) {
203 client_ =
node_->create_client<RequestSamples>(service_name.toStdString());
210 Q_EMIT configChanged();
214 const bool available =
client_ !=
nullptr &&
client_->service_is_ready();
224 showStatus(tr(
"The dataset node disappeared while it was processing the request"));
225 }
else if (!available) {
246 sendRequest(RequestSamples::Request::MODE_NEXT_SAMPLES, 0, {}, tr(
"Pausing playback..."));
250 const auto num_samples =
static_cast<uint64_t
>(
step_size_spin_->value());
251 sendRequest(RequestSamples::Request::MODE_NEXT_SAMPLES, num_samples, {},
252 tr(
"Publishing the next %1 sample(s)...").arg(
static_cast<qulonglong
>(num_samples)));
258 if (!error.isEmpty()) {
262 if (sample_ids.empty()) {
263 showStatus(tr(
"Enter the ID of the sample to skip to"));
266 sendRequest(RequestSamples::Request::MODE_SAMPLE_IDS, 0, sample_ids,
267 tr(
"Skipping ahead to %1 sample(s)...").arg(
static_cast<qulonglong
>(sample_ids.size())));
271 sendRequest(RequestSamples::Request::MODE_ALL_SAMPLES, 0, {}, tr(
"Publishing all remaining samples..."));
275 std::vector<uint64_t> sample_ids;
276 const QStringList entries =
sample_ids_edit_->text().split(QRegularExpression(
"[,;\\s]+"), Qt::SkipEmptyParts);
277 for (
const QString& entry : entries) {
279 const qulonglong sample_id = entry.toULongLong(&valid);
281 *error = tr(
"'%1' is not a sample ID").arg(entry);
284 sample_ids.push_back(
static_cast<uint64_t
>(sample_id));
290 uint64_t num_samples,
291 const std::vector<uint64_t>& sample_ids,
292 const QString& pending_status) {
298 auto request = std::make_shared<RequestSamples::Request>();
299 request->mode = mode;
300 request->num_samples = num_samples;
301 request->sample_ids = sample_ids;
307 client_->async_send_request(request, [
this](rclcpp::Client<RequestSamples>::SharedFuture future) {
308 const auto response = future.get();
309 QString status = QString::fromStdString(response->message);
310 if (response->end_of_dataset) {
311 status += tr(
"; the dataset has ended");
318 if (response.published_sample_ids.empty()) {
321 const uint64_t sample_id = response.published_sample_ids.back();
322 if (response.published_scene_ids.size() != response.published_sample_ids.size()) {
323 return tr(
"Sample %1").arg(
static_cast<qulonglong
>(sample_id));
325 return tr(
"Sample %1 of scene '%2'")
326 .arg(
static_cast<qulonglong
>(sample_id))
327 .arg(QString::fromStdString(response.published_scene_ids.back()));
332 if (!position.isEmpty()) {
335 showStatus(success ? tr(
"OK - %1").arg(status) : tr(
"Failed - %1").arg(status));
345#include "autonomy_datasets_rviz_plugins/moc_playback_panel.cpp"
void onServiceNameChanged()
Re-creates the service client after the service name has been edited.
rclcpp::Node::SharedPtr node_
ROS node of RViz, which its executor spins.
void onStepClicked()
Requests the next samples, which advances the playback by the configured step size.
QPushButton * play_button_
Runs the playback to the end of the dataset.
QString status_text_
Full text reported by status_label_, of which only what fits is shown.
QString position_text_
Full text reported by position_label_, of which only what fits is shown.
QPushButton * step_button_
Advances the playback by the configured step size.
void showStatus(const QString &text)
Reports what the panel is waiting for or what a request resulted in.
QPushButton * skip_button_
Skips the playback ahead to the entered sample IDs.
static QString describePosition(const autonomy_datasets_msgs::srv::RequestSamples::Response &response)
Describes the sample a response reports as published last.
void save(rviz_common::Config config) const override
Stores the service name and step size in the RViz configuration.
void onSkipClicked()
Requests the entered sample IDs, which skips the playback ahead to them.
QLabel * position_label_
Sample the dataset node published last.
~PlaybackPanel() override
Destroys the panel, dropping the service client before the RViz node goes away.
void onInitialize() override
Picks up the ROS node of RViz and creates the client for the configured service.
void updateAvailability()
Tracks whether the service is available and reflects it in the panel.
QLineEdit * sample_ids_edit_
Sample IDs to skip ahead to.
void onPauseClicked()
Requests no sample at all, which stops the playback at the current sample.
bool service_available_
Whether the service was available the last time it was polled.
bool eventFilter(QObject *watched, QEvent *event) override
Re-shortens the reported texts whenever one of the labels showing them is resized.
static void showElided(QLabel *label, const QString &text)
Shows as much of a text as fits into a label and offers all of it as its tooltip.
void setupUi()
Creates the widgets of the panel and connects them to their handlers.
rclcpp::Client< autonomy_datasets_msgs::srv::RequestSamples >::SharedPtr client_
Client of the request_samples service of the dataset node.
void onPlayClicked()
Requests all remaining samples, which runs the playback to the end of the dataset.
std::vector< uint64_t > parseSampleIds(QString *error) const
Reads the sample IDs entered in the panel.
void requestFinished(const QString &position, const QString &status, bool success)
Reports the outcome of a sample request from the thread that received the response.
void load(const rviz_common::Config &config) override
Restores the service name and step size stored in the RViz configuration.
bool request_in_flight_
Whether a request has been sent whose response has not arrived yet.
QPushButton * pause_button_
Stops the playback at the current sample.
void applyRequestResult(const QString &position, const QString &status, bool success)
Shows the outcome of a sample request and re-enables the controls.
PlaybackPanel(QWidget *parent=nullptr)
Creates the panel and its widgets, without connecting to a service yet.
QLineEdit * service_name_edit_
Name of the request_samples service of the dataset node to control.
void updateControlState()
Enables the controls that may be used while no request is being processed.
QTimer * availability_timer_
Polls whether the service is available, as the panel cannot be notified about it.
void sendRequest(uint8_t mode, uint64_t num_samples, const std::vector< uint64_t > &sample_ids, const QString &pending_status)
Sends a sample request and reports its response through requestFinished().
void createClient()
Creates the service client for the service name currently entered in the panel.
void showPosition(const QString &text)
Reports which sample the dataset node published last.
QSpinBox * step_size_spin_
Number of samples a step advances the playback by.
QLabel * status_label_
Outcome of the last request, or why no request can be sent.