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