blob: 006c09acaa7cb0d1845d99ac257452e083301baf [file] [log] [blame]
Parker Schuh90641112017-02-25 12:18:36 -08001#include <gdk/gdk.h>
2#include <gtk/gtk.h>
3#include <sys/stat.h>
4#include <unistd.h>
Austin Schuh60e77942022-05-16 17:48:24 -07005
Parker Schuh90641112017-02-25 12:18:36 -08006#include <fstream>
7#include <functional>
8#include <string>
9
10#include "aos/vision/blob/codec.h"
Parker Schuhcd258b82017-04-09 16:28:29 -070011#include "aos/vision/blob/stream_view.h"
Austin Schuh60e77942022-05-16 17:48:24 -070012#include "aos/vision/debug/debug_framework.h"
Parker Schuhcd258b82017-04-09 16:28:29 -070013#include "aos/vision/debug/overlay.h"
Brian Silverman58899fd2019-03-24 11:03:11 -070014#include "glog/logging.h"
15
Parker Schuh90641112017-02-25 12:18:36 -080016namespace aos {
17namespace vision {
18
19namespace {
20
Parker Schuhcd258b82017-04-09 16:28:29 -070021__attribute__((format(printf, 1, 2))) std::string SPrintf(const char *format,
22 ...) {
23 va_list arglist;
24 va_start(arglist, format);
25 size_t count = vsnprintf(nullptr, 0, format, arglist);
26 va_end(arglist);
27
28 char out[count + 1];
29 va_start(arglist, format);
30 vsnprintf(out, count + 1, format, arglist);
31 va_end(arglist);
32 return std::string(&out[0], &out[count]);
33}
34
Parker Schuhef47dbf2017-03-04 16:59:30 -080035long GetFileSize(const std::string &filename) {
Parker Schuh90641112017-02-25 12:18:36 -080036 struct stat stat_buf;
37 int rc = stat(filename.c_str(), &stat_buf);
38 return rc == 0 ? stat_buf.st_size : -1;
39}
40
41// Parses the blob-log file format.
42// File format goes:
43//
44// Repeated:
45//
46// frame_length.
47// timestamp.
48// fmt.w
49// fmt.h
50// Encoded blob.
51class InputFile {
52 public:
53 InputFile(const std::string &fname)
54 : ifs_(fname, std::ifstream::in), len_(GetFileSize(fname)) {
55 if (len_ <= 0) {
Brian Silverman58899fd2019-03-24 11:03:11 -070056 LOG(FATAL) << "File (" << fname << ") not found. Size (" << len_ << ")";
Parker Schuh90641112017-02-25 12:18:36 -080057 }
58 // assert(len_ > 0);
59 tmp_buf_.resize(len_, 0);
60 ifs_.read(&tmp_buf_[0], len_);
61 buf_ = &tmp_buf_[0];
62 }
63
64 bool ReadNext(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) {
Parker Schuhcd258b82017-04-09 16:28:29 -070065 if (buf_ - &tmp_buf_[0] + sizeof(uint32_t) >= static_cast<size_t>(len_))
66 return false;
Parker Schuh90641112017-02-25 12:18:36 -080067 if (prev_ != nullptr) prev_frames_.emplace_back(prev_);
68 prev_ = buf_;
69 DoRead(blob_list, fmt, timestamp);
70 return true;
71 }
72
73 bool ReadPrev(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) {
74 if (prev_frames_.empty()) return false;
75 buf_ = prev_frames_.back();
76 prev_frames_.pop_back();
Parker Schuh90641112017-02-25 12:18:36 -080077 DoRead(blob_list, fmt, timestamp);
78 prev_ = nullptr;
79 return true;
80 }
81
82 private:
83 void DoRead(BlobList *blob_list, ImageFormat *fmt, uint64_t *timestamp) {
Parker Schuhcd258b82017-04-09 16:28:29 -070084 uint32_t size_delta = Int32Codec::Read(&buf_);
85 if (buf_ - &tmp_buf_[0] + size_delta > len_) {
86 fprintf(stderr, "Corrupted last record.\n");
87 exit(-1);
88 }
Parker Schuh90641112017-02-25 12:18:36 -080089 *timestamp = Int64Codec::Read(&buf_);
90 fmt->w = Int32Codec::Read(&buf_);
91 fmt->h = Int32Codec::Read(&buf_);
92 buf_ = ParseBlobList(blob_list, buf_);
93 }
94 std::vector<const char *> prev_frames_;
95 const char *buf_;
96 const char *prev_ = nullptr;
97 std::ifstream ifs_;
98
99 long len_;
100 std::vector<char> tmp_buf_;
101};
102
103// A single parsed frame.
104class BlobStreamFrame {
105 public:
106 BlobList blob_list;
107 ImageFormat fmt;
108 uint64_t timestamp;
109 void ReadNext(InputFile *fin) {
110 blob_list.clear();
111 if (!fin->ReadNext(&blob_list, &fmt, &timestamp)) {
112 exit(0);
113 return;
114 }
115 }
116 bool ReadPrev(InputFile *fin) {
117 blob_list.clear();
118 return fin->ReadPrev(&blob_list, &fmt, &timestamp);
119 }
120};
121
122} // namespace
123
124// class for installing a lambda as a gtk timeout.
125class TimeoutCallback {
126 public:
127 TimeoutCallback() {}
128
129 void Reset(guint32 interval, std::function<bool()> callback) {
130 Stop();
131 callback_ = callback;
132 timeout_key_ = g_timeout_add(interval, &TimeoutCallback::Callback, this);
133 }
134 void Stop() {
135 if (callback_) {
136 g_source_remove(timeout_key_);
137 }
138 callback_ = std::function<bool()>();
139 }
140
141 private:
142 static gint Callback(void *self) {
143 return reinterpret_cast<TimeoutCallback *>(self)->Callback();
144 }
145 gint Callback() {
146 auto callback = callback_;
147 if (!callback()) {
148 return FALSE;
149 }
150 return TRUE;
151 }
152 gint timeout_key_;
153 std::function<bool()> callback_;
154};
155
Parker Schuhcd258b82017-04-09 16:28:29 -0700156class FrameStats {
157 public:
158 void UpdateStats(int64_t timestamp, bool has_target) {
159 if (has_target != last_has_target_) {
160 if (has_target && timestamp > last_timestamp_) {
161 ++frame_count_;
162 }
163 if (!has_target && timestamp < last_timestamp_) {
164 --frame_count_;
165 }
166 }
167 if (has_target && first_frame_timestamp_ == -1) {
168 first_frame_timestamp_ = timestamp;
169 }
170
171 last_timestamp_ = timestamp;
172 last_has_target_ = has_target;
173 }
174
175 void SetStartTimestamp(int64_t start_timestamp) {
176 start_timestamp_ = start_timestamp;
177 }
178
179 std::string Summary() const {
180 return SPrintf(" frame_count: %ld\n time_since_first_target: %7.3f",
181 frame_count_,
182 (last_timestamp_ - GetStartTimestamp()) / 1.0e9);
183 }
184
185 int64_t GetStartTimestamp() const {
186 if (first_frame_timestamp_ != -1) return first_frame_timestamp_;
187 return start_timestamp_;
188 }
189
190 private:
191 int64_t start_timestamp_;
192 int64_t frame_count_ = 0;
193 int64_t first_frame_timestamp_ = -1;
194 int64_t last_timestamp_ = -1;
195 bool last_has_target_ = 0;
196};
197
198// TODO: display this on the screen when they press help.
199const char *kHudText2 = &R"(
200commands:
201 h - display this message.
202 space - pause / unpause at normal speed (No extra mode).
203 s - Skip forward fast to the next target.
204 p - Skip backwards fast to the previous target.
205 left_arrow - single-step backwards.
206 right_arrow - single-step forwards.
207
208)"[1];
209
Parker Schuh90641112017-02-25 12:18:36 -0800210class BlobLogImageSource : public ImageSource {
211 public:
212 void Init(const std::string &blob_log_filename,
213 DebugFrameworkInterface *interface) override {
214 interface_ = interface;
215 image_source_.reset(new InputFile(blob_log_filename));
216
217 // Tick 25 fps.
218 // TODO(parker): Make this FPS configurable.
219 cb_.Reset(1000 / 25, [this]() { return Tick(); });
220
221 frame_.ReadNext(image_source_.get());
Parker Schuhcd258b82017-04-09 16:28:29 -0700222 start_timestamp_ = frame_.timestamp;
223 frame_stats_.SetStartTimestamp(start_timestamp_);
224
Parker Schuh90641112017-02-25 12:18:36 -0800225 interface_->NewBlobList(frame_.blob_list, frame_.fmt);
226 interface_->InstallKeyPress([this](uint32_t keyval) {
227 if (keyval == GDK_KEY_Left) {
Parker Schuhcd258b82017-04-09 16:28:29 -0700228 ReadPrevFrame();
229 bool has_target = interface_->NewBlobList(frame_.blob_list, frame_.fmt);
230 frame_stats_.UpdateStats(frame_.timestamp, has_target);
Parker Schuh90641112017-02-25 12:18:36 -0800231 } else if (keyval == GDK_KEY_Right) {
Parker Schuhcd258b82017-04-09 16:28:29 -0700232 ReadNextFrame();
233 bool has_target = interface_->NewBlobList(frame_.blob_list, frame_.fmt);
234 frame_stats_.UpdateStats(frame_.timestamp, has_target);
235 } else if (keyval == GDK_KEY_space) {
236 if (mode_ != PAUSED) {
237 mode_ = PAUSED;
238 } else {
239 mode_ = NORMAL_MODE;
240 }
241 } else if (keyval == GDK_KEY_s) {
242 controller_.reset(new FastForwardUntilFrameController(this));
243 mode_ = FAST_MODE;
244 } else if (keyval == GDK_KEY_p) {
245 controller_.reset(new FastForwardUntilFrameController(this));
246 mode_ = FAST_MODE_REV;
Parker Schuh90641112017-02-25 12:18:36 -0800247 } else {
248 return;
249 }
250 });
Parker Schuhcd258b82017-04-09 16:28:29 -0700251 interface_->viewer()->AddOverlay(&overlay_);
252 overlay_.draw_fn = [this](RenderInterface *cr, double w, double h) {
253 (void)w;
254 (void)h;
255 cr->SetSourceRGB(1, 0, 0);
256 auto text = SPrintf(" time: %7.3f\n",
257 (frame_.timestamp - start_timestamp_) / 1.0e9);
258 text += frame_stats_.Summary();
259 cr->Text(2, h - 100, 0, 0, text);
260 };
Parker Schuh90641112017-02-25 12:18:36 -0800261 }
262
263 bool Tick() {
Parker Schuhcd258b82017-04-09 16:28:29 -0700264 if (need_timestamp_print_) {
265 fprintf(stderr, "time: %g\n",
266 (frame_.timestamp - start_timestamp_) / 1.0e9);
267 need_timestamp_print_ = false;
268 }
269 for (int i = 0; i < GetSpeed(); ++i) {
270 if (Direction()) {
271 ReadNextFrame();
272 } else {
273 ReadPrevFrame();
274 }
275 bool has_target =
276 interface_->JustCheckForTarget(frame_.blob_list, frame_.fmt);
277 frame_stats_.UpdateStats(frame_.timestamp, has_target);
278 if (controller_) {
279 controller_->NewFrame(has_target);
280 }
281 // Draw on the last frame:
282 if (i + 1 >= GetSpeed()) {
283 interface_->NewBlobList(frame_.blob_list, frame_.fmt);
284 }
285 }
Parker Schuh90641112017-02-25 12:18:36 -0800286 return true;
287 }
288
Parker Schuhcd258b82017-04-09 16:28:29 -0700289 int GetSpeed() {
290 if (mode_ == PAUSED) return 0;
291 if (mode_ == NORMAL_MODE) return 1;
292 if (mode_ == FAST_MODE || mode_ == FAST_MODE_REV) return 60;
293 return 0;
294 }
295
296 bool Direction() {
297 if (mode_ == FAST_MODE_REV) return false;
298 return true;
299 }
300
301 void ReadNextFrame() {
302 frame_.ReadNext(image_source_.get());
303 need_timestamp_print_ = true;
304 }
305
306 void ReadPrevFrame() {
307 frame_.ReadPrev(image_source_.get());
308 need_timestamp_print_ = true;
309 }
310
Parker Schuh90641112017-02-25 12:18:36 -0800311 const char *GetHelpMessage() override {
312 return &R"(
313 format_spec is the name of a file in blob list format.
314 This viewer source will stream blobs from the log.
315)"[1];
316 }
317
318 private:
Parker Schuhcd258b82017-04-09 16:28:29 -0700319 bool need_timestamp_print_ = true;
320 uint64_t start_timestamp_ = 0;
321
322 class Controller {
323 public:
324 virtual ~Controller() {}
325 virtual void NewFrame(bool has_target) = 0;
326 };
327
328 class FastForwardUntilFrameController : public Controller {
329 public:
330 FastForwardUntilFrameController(BlobLogImageSource *proxy)
331 : proxy_(proxy) {}
332
333 void NewFrame(bool has_target) override {
334 if (!has_target) inside_target = false;
335 if (!inside_target && has_target) {
336 proxy_->mode_ = PAUSED;
337 proxy_->controller_.reset(nullptr);
338 }
339 }
340
341 BlobLogImageSource *proxy_;
342 bool inside_target = true;
343 };
344
345 std::unique_ptr<Controller> controller_;
346
347 FrameStats frame_stats_;
348
349 enum Mode {
350 PAUSED,
351 NORMAL_MODE,
352 FAST_MODE,
353 FAST_MODE_REV,
354 };
355 Mode mode_ = PAUSED;
356
357 // LambdaOverlay text_overlay_;
Parker Schuh90641112017-02-25 12:18:36 -0800358 TimeoutCallback cb_;
359 DebugFrameworkInterface *interface_ = nullptr;
360 std::unique_ptr<InputFile> image_source_;
361 BlobStreamFrame frame_;
Parker Schuhcd258b82017-04-09 16:28:29 -0700362 LambdaOverlay overlay_;
Parker Schuh90641112017-02-25 12:18:36 -0800363};
364
365REGISTER_IMAGE_SOURCE("blob_log", BlobLogImageSource);
366
367} // namespace vision
368} // namespace aos