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