JeVois  1.20
JeVois Smart Embedded Machine Vision Toolkit
Share this page:
GUIhelper.C
Go to the documentation of this file.
1 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2020 by Laurent Itti, the University of Southern
4 // California (USC), and iLab at USC. See http://iLab.usc.edu and http://jevois.org for information about this project.
5 //
6 // This file is part of the JeVois Smart Embedded Machine Vision Toolkit. This program is free software; you can
7 // redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
8 // Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
10 // License for more details. You should have received a copy of the GNU General Public License along with this program;
11 // if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
12 //
13 // Contact information: Laurent Itti - 3641 Watt Way, HNB-07A - Los Angeles, CA 90089-2520 - USA.
14 // Tel: +1 213 740 3527 - itti@pollux.usc.edu - http://iLab.usc.edu - http://jevois.org
15 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
16 /*! \file */
17 
18 #ifdef JEVOIS_PRO
19 
20 // for demo
21 #include <math.h>
22 #include <imgui.h>
23 #define IMGUI_DEFINE_MATH_OPERATORS // Access to math operators
24 #include <imgui_internal.h>
25 #include <algorithm> // for tweak demo
26 
27 #include <jevois/GPU/GUIhelper.H>
28 #include <jevois/GPU/GUIconsole.H>
29 #include <jevois/GPU/GPUimage.H>
30 #include <jevois/Core/Module.H>
31 #include <jevois/GPU/GUIeditor.H>
32 #include <jevois/Debug/SysInfo.H>
33 #include <jevois/Util/Utils.H>
35 #include <imgui.h>
36 #include <imgui_internal.h>
37 #include <glm/gtc/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
38 #include <glm/gtx/euler_angles.hpp>
39 #include <fstream>
40 #include <set>
41 
42 // ##############################################################################################################
43 jevois::GUIhelper::GUIhelper(std::string const & instance, bool conslock) :
44  jevois::Component(instance), itsConsLock(conslock), itsBackend()
45 {
46  // We defer OpenGL init to startFrame() so that all OpenGL code is in the same thread.
47 
48  // If auto-detecting screen size, get the framebuffer size here so we can use it later:
49  if (winsize::get().width == 0)
50  {
51  // Query the framebuffer if no window size was given:
52  int w = 1920, h = 1080; // defaults in case we fail to read from framebuffer
53  try
54  {
55  std::string const ws = jevois::getFileString("/sys/class/graphics/fb0/virtual_size");
56  std::vector<std::string> const tok = jevois::split(ws, "\\s*[,;x]\\s*");
57  if (tok.size() == 2) { w = std::stoi(tok[0]); h = std::stoi(tok[1]) / 2; } // Reported height is double...
58  LINFO("Detected framebuffer size: " << w << 'x' << h);
59  }
60  catch (...) { } // silently ignore any errors
61 
62  winsize::set(cv::Size(w, h));
63  }
64 
65  itsWindowTitle = "JeVois-Pro v" + std::string(JEVOIS_VERSION_STRING);
66 
67  // Create some config files for the config editor:
68  std::vector<EditorItem> fixedcfg
69  {
70  { JEVOIS_ROOT_PATH "/config/videomappings.cfg", "JeVois videomappings.cfg", EditorSaveAction::RefreshMappings },
71  { JEVOIS_ROOT_PATH "/config/initscript.cfg", "JeVois initscript.cfg", EditorSaveAction::Reboot },
72  { "params.cfg", "Module's params.cfg", EditorSaveAction::Reload },
73  { "script.cfg", "Module's script.cfg", EditorSaveAction::Reload },
74  { JEVOIS_ROOT_PATH "/share/dnn/models.yml",
75  "JeVois models.yml DNN Zoo Root", EditorSaveAction::Reload },
76  { JEVOIS_ROOT_PATH "/share/dnn/opencv.yml",
77  "JeVois opencv.yml DNN Zoo for OpenCV models", EditorSaveAction::Reload },
78  { JEVOIS_ROOT_PATH "/share/dnn/npu.yml",
79  "JeVois npu.yml DNN Zoo for A311D NPU models", EditorSaveAction::Reload },
80  { JEVOIS_ROOT_PATH "/share/dnn/spu.yml",
81  "JeVois spu.yml DNN Zoo for Hailo SPU models", EditorSaveAction::Reload },
82  { JEVOIS_ROOT_PATH "/share/dnn/tpu.yml",
83  "JeVois tpu.yml DNN Zoo for Coral TPU models", EditorSaveAction::Reload },
84  { JEVOIS_ROOT_PATH "/share/dnn/vpu.yml",
85  "JeVois vpu.yml DNN Zoo for Myriad-X VPU models", EditorSaveAction::Reload },
86  { JEVOIS_ROOT_PATH "/share/dnn/ort.yml",
87  "JeVois ort.yml DNN Zoo for ONNX-Runtime CPU models", EditorSaveAction::Reload },
88  };
89 
90  // Create the config editor:
91  itsCfgEditor.reset(new jevois::GUIeditor(this, "cfg", std::move(fixedcfg), JEVOIS_CUSTOM_DNN_PATH, "Custom DNN ",
92  { ".yaml", ".yml" }));
93 
94  // Create some config files for the code editor:
95  std::vector<EditorItem> fixedcode
96  {
97  { "*", "Module's source code", EditorSaveAction::Reload },
98  // Gets added at runtime: { "#", "Module's CMakeLists.txt", EditorSaveAction::Compile },
99  };
100 
101  // Create the code editor:
102  itsCodeEditor.reset(new jevois::GUIeditor(this, "code", std::move(fixedcode), JEVOIS_PYDNN_PATH, "PyDNN ",
103  { ".py", ".C", ".H", ".cpp", ".hpp", ".c", ".h", ".txt" }));
104 }
105 
106 // ##############################################################################################################
108 {
109  // In case a DNN get was in progress, wait for it while letting user know:
110  JEVOIS_WAIT_GET_FUTURE(itsDnnGetFut);
111 }
112 
113 // ##############################################################################################################
114 void jevois::GUIhelper::reset(bool modulechanged)
115 {
116  itsEndFrameCalled = true;
117  itsImages.clear();
118  itsImages2.clear();
119  itsLastDrawnImage = nullptr;
120  itsLastDrawnTextLine = -1;
121  itsIdle = true;
122  itsIcon.clear();
123  itsModName.clear();
124  itsModDesc.clear();
125  itsModAuth.clear();
126  itsModLang.clear();
127  itsModDoc.clear();
128 
129  if (modulechanged)
130  {
131  itsCfgEditor->refresh();
132  itsCodeEditor->refresh();
133  }
134 
135  {
136  // Clear old error messages:
137  std::lock_guard<std::mutex> _(itsErrorMtx);
138  itsErrors.clear();
139  }
140 
141  // Get the actual window/screen size:
142  unsigned short w, h;
143  itsBackend.getWindowSize(w, h);
144 
145  if (w == 0) LFATAL("Need to call startFrame() at least once first");
146 
147  float const fov_y = 45.0f;
148 
149  proj = glm::perspective(glm::radians(fov_y), float(w) / float(h), 1.0f, h * 2.0f);
150  const_cast<float &>(pixel_perfect_z) = -float(h) / (2.0 * tan(fov_y * M_PI / 360.0));
151 #ifdef JEVOIS_PLATFORM
152  // On platform, we need to translate a bit to avoid aliasing issues, which are problematic with our YUYV shader:
153  proj = glm::translate(proj, glm::vec3(0.375f, 0.375f, 0.0f));
154 #endif
155  view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, pixel_perfect_z));
156 
157  // Set the style (on first call):
158  style::set(style::get());
159 
160  // Refresh whether we have a USB serial gadget loaded:
161  itsUSBserial = ! engine()->getParamStringUnique("engine:usbserialdev").empty();
162 
163  // If the engine's current videomapping has a CMakeLists.txt, copy it to itsNewMapping to allow recompilation:
164  jevois::VideoMapping const & m = engine()->getCurrentVideoMapping();
165  if (std::filesystem::exists(m.cmakepath())) itsNewMapping = m;
166 }
167 
168 // ##############################################################################################################
169 bool jevois::GUIhelper::startFrame(unsigned short & w, unsigned short & h)
170 {
171  if (itsEndFrameCalled == false) LFATAL("You need to call endFrame() at the end of your process() function");
172 
173  // Get current window size, will be 0x0 if not initialized yet:
174  itsBackend.getWindowSize(w, h);
175 
176  if (w == 0)
177  {
178  // Need to init the display:
179  cv::Size const siz = winsize::get();
180  bool const fs = fullscreen::get();
181  LINFO("OpenGL init " << siz.width << 'x' << siz.height << (fs ? " fullscreen" : ""));
182  itsBackend.init(siz.width, siz.height, fs, scale::get(), itsConsLock);
183  rounding::set(int(rounding::get() * scale::get() + 0.499F));
184 
185  // Get the actual window size and update our param:
186  unsigned short winw, winh; itsBackend.getWindowSize(winw, winh); winsize::set(cv::Size(winw, winh));
187  winsize::freeze();
188  fullscreen::freeze();
189 
190  // Reset the GUI:
191  reset();
192  }
193 
194  // Poll events:
195  bool shouldclose = false; auto const now = std::chrono::steady_clock::now();
196  if (itsBackend.pollEvents(shouldclose)) itsLastEventTime = now;
197 
198  if (shouldclose && allowquit::get())
199  {
200  LINFO("Closing down on user request...");
201  engine()->quit();
202  }
203 
204  // Start the frame on the backend:
205  itsBackend.newFrame();
206 
207  // Check if we have been idle:
208  if (itsIdleBlocked)
209  itsIdle = false;
210  else
211  {
212  float const hs = hidesecs::get();
213  if (hs)
214  {
215  std::chrono::duration<float> elapsed = now - itsLastEventTime;
216  itsIdle = (elapsed.count() >= hs);
217  }
218  else itsIdle = false;
219  }
220 
221  itsEndFrameCalled = false;
222 
223  return itsIdle;
224 }
225 
226 // ##############################################################################################################
228 { return itsIdle; }
229 
230 // ##############################################################################################################
231 void jevois::GUIhelper::onParamChange(jevois::gui::scale const & param, float const & newval)
232 {
233  float oldval = param.get();
234  if (newval == oldval) return;
235 
236  ImGui::GetStyle().ScaleAllSizes(newval / oldval);
237  ImGui::GetIO().FontGlobalScale = newval;
238  ImGui::GetStyle().MouseCursorScale = 2.0f; // do not scale the cursor, otherwise it disappears...
239 
240  // Also scale the window corner rounding, make it no more than 24:
241  if (oldval) rounding::set(std::min(24, int(rounding::get() * newval / oldval + 0.499F)));
242 }
243 
244 // ##############################################################################################################
245 void jevois::GUIhelper::onParamChange(jevois::gui::style const & JEVOIS_UNUSED_PARAM(param),
246  jevois::gui::GuiStyle const & newval)
247 {
248  switch (newval)
249  {
250  case jevois::gui::GuiStyle::Dark:
251  ImGui::StyleColorsDark();
252  itsCfgEditor->SetPalette(TextEditor::GetDarkPalette());
253  itsCodeEditor->SetPalette(TextEditor::GetDarkPalette());
254  break;
255 
256  case jevois::gui::GuiStyle::Light:
257  ImGui::StyleColorsLight();
258  ImGui::GetStyle().Colors[ImGuiCol_WindowBg] = ImVec4(0.94f, 0.94f, 0.94f, 0.92f); // a bit transparent
259  itsCfgEditor->SetPalette(TextEditor::GetLightPalette());
260  itsCodeEditor->SetPalette(TextEditor::GetLightPalette());
261  break;
262 
263  case jevois::gui::GuiStyle::Classic:
264  ImGui::StyleColorsClassic();
265  itsCfgEditor->SetPalette(TextEditor::GetRetroBluePalette());
266  itsCodeEditor->SetPalette(TextEditor::GetRetroBluePalette());
267  break;
268  }
269 }
270 
271 // ##############################################################################################################
272 void jevois::GUIhelper::onParamChange(jevois::gui::rounding const & JEVOIS_UNUSED_PARAM(param), int const & newval)
273 {
274  auto & s = ImGui::GetStyle();
275  s.WindowRounding = newval;
276  s.ChildRounding = newval;
277  s.FrameRounding = newval;
278  s.PopupRounding = newval;
279  s.ScrollbarRounding = newval;
280  s.GrabRounding = newval;
281 }
282 
283 // ##############################################################################################################
285 { return (itsEndFrameCalled == false); }
286 
287 // ##############################################################################################################
288 void jevois::GUIhelper::drawImage(char const * name, jevois::RawImage const & img, int & x, int & y,
289  unsigned short & w, unsigned short & h, bool noalias, bool isoverlay)
290 {
291  // We will use one image per v4l2 buffer:
292  std::string const imgname = name + std::to_string(img.bufindex);
293 
294  // Get the image by name (will create a default-constructed one the first time a new name is used):
295  auto & im = itsImages[imgname];
296 
297  // Set the new pixel data:
298  im.set(img);
299 
300  // Draw it:
301  im.draw(x, y, w, h, noalias, proj * view);
302 
303  // Remember which image was drawn last, used by i2d():
304  if (isoverlay == false)
305  {
306  itsLastDrawnImage = & im;
307  itsUsingScaledImage = false;
308  itsLastDrawnTextLine = -1;
309  }
310 }
311 
312 // ##############################################################################################################
313 void jevois::GUIhelper::drawImage(char const * name, cv::Mat const & img, bool rgb, int & x, int & y,
314  unsigned short & w, unsigned short & h, bool noalias, bool isoverlay)
315 {
316  // Get the image by name (will create a default-constructed one the first time a new name is used):
317  auto & im = itsImages[name];
318 
319  // Set the new pixel data:
320  im.set(img, rgb);
321 
322  // Draw it:
323  im.draw(x, y, w, h, noalias, proj * view);
324 
325  // Remember which image was drawn last, used by i2d():
326  if (isoverlay == false)
327  {
328  itsLastDrawnImage = & im;
329  itsUsingScaledImage = false;
330  itsLastDrawnTextLine = -1;
331  }
332 }
333 
334 // ##############################################################################################################
335 void jevois::GUIhelper::drawInputFrame(char const * name, jevois::InputFrame const & frame, int & x, int & y,
336  unsigned short & w, unsigned short & h, bool noalias, bool casync)
337 {
338  // We will use one image per v4l2 buffer:
339  jevois::RawImage const img = frame.get(casync);
340  std::string const imgname = name + std::to_string(img.bufindex);
341 
342  // Get the image by name (will create a default-constructed one the first time a new name is used):
343  auto & im = itsImages[imgname];
344 
345  // Set the new pixel data using DMABUF acceleration. This will boil down to a no-op unless image size or format has
346  // changed (including the first time we set it):
347  im.set(frame, itsBackend.getDisplay());
348 
349  // Draw it:
350  im.draw(x, y, w, h, noalias, proj * view);
351 
352  // Remember which image was drawn last, used by i2d():
353  itsLastDrawnImage = & im;
354  itsUsingScaledImage = frame.hasScaledImage();
355  itsLastDrawnTextLine = -1;
356  if (itsUsingScaledImage)
357  {
358  jevois::RawImage img2 = frame.get2(casync);
359  itsScaledImageFacX = float(img.width) / float(img2.width);
360  itsScaledImageFacY = float(img.height) / float(img2.height);
361  }
362 }
363 
364 // ##############################################################################################################
365 void jevois::GUIhelper::drawInputFrame2(char const * name, jevois::InputFrame const & frame, int & x, int & y,
366  unsigned short & w, unsigned short & h, bool noalias, bool casync)
367 {
368  // We will use one image per v4l2 buffer:
369  jevois::RawImage const img = frame.get2(casync);
370  std::string const imgname = name + std::to_string(img.bufindex);
371 
372  // Get the image by name (will create a default-constructed one the first time a new name is used):
373  auto & im = itsImages2[imgname];
374 
375  // Set the new pixel data using DMABUF acceleration. This will boil down to a no-op unless image size or format has
376  // changed (including the first time we set it):
377  im.set2(frame, itsBackend.getDisplay());
378 
379  // Draw it:
380  im.draw(x, y, w, h, noalias, proj * view);
381 
382  // Remember which image was drawn last, used by i2d():
383  itsLastDrawnImage = & im;
384  itsUsingScaledImage = false;
385  itsLastDrawnTextLine = -1;
386 }
387 
388 // ##############################################################################################################
389 void jevois::GUIhelper::onParamChange(gui::twirl const & JEVOIS_UNUSED_PARAM(param), float const & newval)
390 {
391  // Compute alpha so that more twirl is also more faded (lower alpha). Here, we want to mask out the whole display,
392  // including all GUI drawings except the banner. Hence we will just draw a full-screen semi-transparent rectangle in
393  // endFrame(), using our computed alpha value:
394  itsGlobalAlpha = std::abs(1.0F - newval / 15.0F);
395 
396  // Note: we only twirl the display images (itsImages), not the processing images (itsImages2):
397  for (auto & ip : itsImages) ip.second.twirl(newval);
398 }
399 
400 // ##############################################################################################################
401 ImVec2 jevois::GUIhelper::i2d(ImVec2 p, char const * name)
402 {
403  // Find the image:
404  GPUimage * img;
405 
406  if (name == nullptr)
407  {
408  if (itsLastDrawnImage == nullptr) throw std::range_error("You need to call drawImage() or drawInputFrame() first");
409  img = itsLastDrawnImage;
410  }
411  else
412  {
413  std::string nstr = name;
414  auto itr = itsImages.find(nstr);
415  if (itr == itsImages.end())
416  {
417  // Add a zero in case we are dealing with a camera frame (drawImage() and drawInputFrame() add suffixes, but all
418  // image buffers are the same size):
419  itr = itsImages.find(nstr + '0');
420  if (itr == itsImages.end()) throw std::range_error("No previously drawn image with name [" + nstr + "] found");
421  }
422  img = & itr->second;
423  }
424 
425  // Adjust size if using scaled image:
426  if (itsUsingScaledImage) { p.x *= itsScaledImageFacX; p.y *= itsScaledImageFacY; }
427 
428  // Delegate:
429  return img->i2d(p);
430 }
431 
432 // ##############################################################################################################
433 ImVec2 jevois::GUIhelper::i2d(float x, float y, char const * name)
434 { return i2d(ImVec2(x, y), name); }
435 
436 // ##############################################################################################################
437 ImVec2 jevois::GUIhelper::i2ds(ImVec2 p, char const * name)
438 {
439  // Find the image:
440  GPUimage * img;
441 
442  if (name == nullptr)
443  {
444  if (itsLastDrawnImage == nullptr) throw std::range_error("You need to call drawImage() or drawInputFrame() first");
445  img = itsLastDrawnImage;
446  }
447  else
448  {
449  std::string nstr = name;
450  auto itr = itsImages.find(nstr);
451  if (itr == itsImages.end())
452  {
453  // Add a zero in case we are dealing with a camera frame (drawImage() and drawInputFrame() add suffixes, but all
454  // image buffers are the same size):
455  itr = itsImages.find(nstr + '0');
456  if (itr == itsImages.end()) throw std::range_error("No previously drawn image with name [" + nstr + "] found");
457  }
458  img = & itr->second;
459  }
460 
461  // Adjust size if using scaled image:
462  if (itsUsingScaledImage) { p.x *= itsScaledImageFacX; p.y *= itsScaledImageFacY; }
463 
464  // Delegate:
465  return img->i2ds(p);
466 }
467 
468 // ##############################################################################################################
469 ImVec2 jevois::GUIhelper::i2ds(float x, float y, char const * name)
470 { return i2ds(ImVec2(x, y), name); }
471 
472 // ##############################################################################################################
473 void jevois::GUIhelper::drawLine(float x1, float y1, float x2, float y2, ImU32 col)
474 {
475  ImGui::GetBackgroundDrawList()->AddLine(i2d(x1, y1), i2d(x2, y2), col, linethick::get());
476 }
477 
478 // ##############################################################################################################
479 void jevois::GUIhelper::drawRect(float x1, float y1, float x2, float y2, ImU32 col, bool filled)
480 {
481  auto dlb = ImGui::GetBackgroundDrawList();
482  ImVec2 const tl = i2d(x1, y1);
483  ImVec2 const br = i2d(x2, y2);
484 
485  if (filled) dlb->AddRectFilled(tl, br, applyFillAlpha(col));
486 
487  dlb->AddRect(tl, br, col, 0.0F, ImDrawCornerFlags_All, linethick::get());
488 }
489 
490 // ##############################################################################################################
491 void jevois::GUIhelper::drawPolyInternal(ImVec2 const * pts, size_t npts, ImU32 col, bool filled)
492 {
493  auto dlb = ImGui::GetBackgroundDrawList();
494  float const thick = linethick::get();
495 
496  if (filled) dlb->AddConvexPolyFilled(pts, npts, applyFillAlpha(col));
497 
498  if (npts > 1)
499  {
500  for (size_t i = 0; i < npts - 1; ++i) dlb->AddLine(pts[i], pts[i + 1], col, thick);
501  dlb->AddLine(pts[npts - 1], pts[0], col, thick); // close the polygon
502  }
503 }
504 
505 // ##############################################################################################################
506 void jevois::GUIhelper::drawPoly(std::vector<cv::Point> const & pts, ImU32 col, bool filled)
507 {
508  size_t const npts = pts.size();
509 
510  ImVec2 iv[npts]; int i = 0;
511  for (auto const & p : pts) iv[i++] = i2d(p.x, p.y);
512 
513  drawPolyInternal(iv, npts, col, filled);
514 }
515 
516 // ##############################################################################################################
517 void jevois::GUIhelper::drawPoly(std::vector<cv::Point2f> const & pts, ImU32 col, bool filled)
518 {
519  size_t const npts = pts.size();
520 
521  ImVec2 iv[npts]; int i = 0;
522  for (auto const & p : pts) iv[i++] = i2d(p.x, p.y);
523 
524  drawPolyInternal(iv, npts, col, filled);
525 }
526 
527 // ##############################################################################################################
528 void jevois::GUIhelper::drawCircle(float x, float y, float r, ImU32 col, bool filled)
529 {
530  auto dlb = ImGui::GetBackgroundDrawList();
531 
532  ImVec2 const center = i2d(x, y);
533  float const rad = i2ds(r, 0).x;
534 
535  if (filled) dlb->AddCircleFilled(center, rad, applyFillAlpha(col), 0);
536 
537  dlb->AddCircle(center, rad, col, 0, linethick::get());
538 }
539 
540 // ##############################################################################################################
541 void jevois::GUIhelper::drawText(float x, float y, char const * txt, ImU32 col)
542 {
543  ImGui::GetBackgroundDrawList()->AddText(i2d(x, y), col, txt);
544 }
545 
546 // ##############################################################################################################
547 void jevois::GUIhelper::drawText(float x, float y, std::string const & txt, ImU32 col)
548 {
549  drawText(x, y, txt.c_str(), col);
550 }
551 
552 // ##############################################################################################################
553 ImU32 jevois::GUIhelper::applyFillAlpha(ImU32 col) const
554 {
555  unsigned char alpha = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
556  alpha = (unsigned char)(fillalpha::get() * alpha);
557  return (col & ~IM_COL32_A_MASK) | (alpha << IM_COL32_A_SHIFT);
558 }
559 
560 // ##############################################################################################################
561 ImVec2 jevois::GUIhelper::iline(int line, char const * name)
562 {
563  if (line == -1) line = ++itsLastDrawnTextLine; else itsLastDrawnTextLine = line;
564  ImVec2 p = i2d(0, 0, name);
565  p.x += 5.0F;
566  p.y += 5.0F + (ImGui::GetFontSize() + 5.0F) * line;
567  return p;
568 }
569 
570 // ##############################################################################################################
571 void jevois::GUIhelper::itext(char const * txt, ImU32 const & col, int line)
572 {
573  ImU32 const c = (col == IM_COL32_BLACK_TRANS) ? ImU32(overlaycolor::get()) : col;
574  ImGui::GetBackgroundDrawList()->AddText(iline(line), c, txt);
575 }
576 
577 // ##############################################################################################################
578 void jevois::GUIhelper::itext(std::string const & txt, ImU32 const & col, int line)
579 {
580  itext(txt.c_str(), col, line);
581 }
582 
583 // ##############################################################################################################
584 void jevois::GUIhelper::iinfo(jevois::InputFrame const & inframe, std::string const & fpscpu,
585  unsigned short winw, unsigned short winh)
586 {
587  unsigned short ww, wh;
588  if (winw == 0 || winh == 0) itsBackend.getWindowSize(ww, wh); else { ww = winw; wh = winh; }
589 
590  jevois::RawImage const & inimg = inframe.get();
591  std::string cam2str;
592  if (inframe.hasScaledImage())
593  {
594  jevois::RawImage const & inimg2 = inframe.get2();
595  cam2str += jevois::sformat(" + %s:%dx%d", jevois::fccstr(inimg2.fmt).c_str(), inimg2.width, inimg2.height);
596  }
597  std::string const msg = jevois::sformat("%s, Camera: %s:%dx%d%s, Display: RGBA:%dx%d", fpscpu.c_str(),
598  jevois::fccstr(inimg.fmt).c_str(), inimg.width, inimg.height,
599  cam2str.c_str(), ww, wh);
600 
601  ImGui::GetBackgroundDrawList()->AddText(ImVec2(10, wh-10-ImGui::GetFontSize()), overlaycolor::get(), msg.c_str());
602 }
603 
604 // ##############################################################################################################
605 void jevois::GUIhelper::releaseImage(char const * name)
606 {
607  auto itr = itsImages.find(name);
608  if (itr != itsImages.end()) itsImages.erase(itr);
609 }
610 
611 // ##############################################################################################################
612 void jevois::GUIhelper::releaseImage2(char const * name)
613 {
614  auto itr = itsImages2.find(name);
615  if (itr != itsImages2.end()) itsImages2.erase(itr);
616 }
617 
618 // ##############################################################################################################
619 ImVec2 jevois::GUIhelper::d2i(ImVec2 p, char const * name)
620 {
621  // Find the image:
622  GPUimage * img;
623  float fx = 1.0F, fy = 1.0F;
624 
625  if (name == nullptr)
626  {
627  if (itsLastDrawnImage == nullptr) throw std::range_error("You need to call drawImage() or drawInputFrame() first");
628  img = itsLastDrawnImage;
629  if (itsUsingScaledImage) { fx = 1.0F / itsScaledImageFacX; fy = 1.0F / itsScaledImageFacY; }
630  }
631  else
632  {
633  std::string nstr = name;
634  auto itr = itsImages.find(nstr);
635  if (itr == itsImages.end())
636  {
637  // Add a zero in case we are dealing with a camera frame (drawImage() and drawInputFrame() add suffixes, but all
638  // image buffers are the same size):
639  itr = itsImages.find(nstr + '0');
640  if (itr == itsImages.end()) throw std::range_error("No previously drawn image with name [" + nstr + "] found");
641  }
642  img = & itr->second;
643  }
644 
645  // Delegate:
646  ImVec2 ret = img->d2i(p);
647  ret.x *= fx; ret.y *= fy;
648  return ret;
649 }
650 
651 // ##############################################################################################################
652 ImVec2 jevois::GUIhelper::d2i(float x, float y, char const * name)
653 { return d2i(ImVec2(x, y), name); }
654 
655 // ##############################################################################################################
656 ImVec2 jevois::GUIhelper::d2is(ImVec2 p, char const * name)
657 {
658  // Find the image:
659  GPUimage * img;
660  float fx = 1.0F, fy = 1.0F;
661 
662  if (name == nullptr)
663  {
664  if (itsLastDrawnImage == nullptr) throw std::range_error("You need to call drawImage() or drawInputFrame() first");
665  img = itsLastDrawnImage;
666  if (itsUsingScaledImage) { fx = 1.0F / itsScaledImageFacX; fy = 1.0F / itsScaledImageFacY; }
667  }
668  else
669  {
670  std::string nstr = name;
671  auto itr = itsImages.find(nstr);
672  if (itr == itsImages.end())
673  {
674  // Add a zero in case we are dealing with a camera frame (drawImage() and drawInputFrame() add suffixes, but all
675  // image buffers are the same size):
676  itr = itsImages.find(nstr + '0');
677  if (itr == itsImages.end()) throw std::range_error("No previously drawn image with name [" + nstr + "] found");
678  }
679  img = & itr->second;
680  }
681 
682  // Delegate:
683  ImVec2 ret = img->d2is(p);
684  ret.x *= fx; ret.y *= fy;
685  return ret;
686 }
687 
688 // ##############################################################################################################
689 ImVec2 jevois::GUIhelper::d2is(float x, float y, char const * name)
690 { return d2is(ImVec2(x, y), name); }
691 
692 // ##############################################################################################################
694 {
695  // Decide whether to show mouse cursor based on idle state:
696  ImGui::GetIO().MouseDrawCursor = ! itsIdle;
697 
698  // Draw our JeVois GUI on top of everything:
699  if (itsIdle == false) drawJeVoisGUI();
700 
701  // If compiling, draw compilation window:
702  if (itsCompileState != CompilationState::Idle) compileModule();
703 
704  // Do we want to fade out the whole display?
705  if (itsGlobalAlpha != 1.0F)
706  {
707  if (itsGlobalAlpha < 0.0F || itsGlobalAlpha > 1.0F)
708  {
709  LERROR("Invalid global alpha " << itsGlobalAlpha << " -- RESET TO 1.0");
710  itsGlobalAlpha = 1.0F;
711  }
712  else
713  {
714  auto dlb = ImGui::GetBackgroundDrawList();
715  cv::Size const ws = winsize::get();
716  dlb->AddRectFilled(ImVec2(0, 0), ImVec2(ws.width, ws.height), ImColor(0.0F, 0.0F, 0.0F, 1.0F - itsGlobalAlpha));
717  }
718  }
719 
720  // Do we have a demo banner to show?
721  if (itsBannerTitle.empty() == false)
722  {
723  ImGui::SetNextWindowPos(ImVec2(800, 500));
724  ImGui::SetNextWindowSize(ImVec2(1000, 400));
725  ImGui::PushStyleColor(ImGuiCol_WindowBg, 0xf0e0ffe0); // ABGR
726  ImGui::Begin("JeVois-Pro Demo Mode", nullptr, ImGuiWindowFlags_NoResize |
727  ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse |
728  ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
729  ImGui::SetWindowFontScale(1.5F);
730  ImGui::TextUnformatted(" ");
731  ImGui::TextUnformatted(itsBannerTitle.c_str());
732  ImGui::TextUnformatted(" ");
733  ImGui::SetWindowFontScale(1.0F);
734 
735  int wrap = ImGui::GetWindowSize().x - ImGui::GetFontSize() * 2.0f;
736  ImGui::PushTextWrapPos(wrap);
737  ImGui::TextUnformatted(itsBannerMsg.c_str());
738  ImGui::PopTextWrapPos();
739 
740  ImGui::SetCursorPosY(ImGui::GetWindowSize().y - ImGui::CalcTextSize("X").y * 2.3F);
741  ImGui::Separator();
742 
743  if (ImGui::Button("Skip to Next Demo")) engine()->nextDemo();
744  ImGui::SetItemDefaultFocus();
745  ImGui::SameLine(600);
746  if (ImGui::Button("Abort Demo Mode")) engine()->abortDemo();
747  ImGui::End();
748  ImGui::PopStyleColor();
749  }
750 
751  // Render everything and swap buffers:
752  itsBackend.render();
753 
754  itsEndFrameCalled = true;
755 }
756 
757 // ##############################################################################################################
759 {
760  if (ImGui::Begin(itsWindowTitle.c_str(), nullptr /* no closing */))//, ImGuiWindowFlags_MenuBar))
761  {
762  //drawMenuBar();
763  drawModuleSelect();
764 
765  //ImGui::Text("Camera + Process + Display: %.2f fps", ImGui::GetIO().Framerate);
766  ImGui::Separator();
767 
768  if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None))
769  {
770 
771  if (ImGui::BeginTabItem("Info"))
772  {
773  drawInfo();
774  ImGui::EndTabItem();
775  }
776 
777  if (ImGui::BeginTabItem("Parameters"))
778  {
779  drawParameters();
780  ImGui::EndTabItem();
781  }
782 
783  if (ImGui::BeginTabItem("Console"))
784  {
785  drawConsole();
786  ImGui::EndTabItem();
787  }
788 
789  if (ImGui::BeginTabItem("Camera"))
790  {
791  drawCamCtrls();
792  ImGui::EndTabItem();
793  }
794 
795  if (ImGui::BeginTabItem("Config"))
796  {
797  itsCfgEditor->draw();
798  ImGui::EndTabItem();
799  }
800 
801  if (ImGui::BeginTabItem("Code"))
802  {
803  itsCodeEditor->draw();
804  ImGui::EndTabItem();
805  }
806 
807  if (ImGui::BeginTabItem("System"))
808  {
809  drawSystem();
810  ImGui::EndTabItem();
811  }
812 
813  if (ImGui::BeginTabItem("Tweaks"))
814  {
815  drawTweaks();
816  ImGui::EndTabItem();
817  }
818  ImGui::EndTabBar();
819  }
820  ImGui::End();
821  }
822  else ImGui::End(); // do not draw anything if window is collapsed
823 
824  // Show style editor window, imgui demo, etc if user requested it:
825  if (itsShowStyleEditor)
826  {
827  ImGui::Begin("GUI Style Editor", &itsShowStyleEditor);
828  ImGui::ShowStyleEditor();
829  ImGui::End();
830  }
831  if (itsShowAppMetrics) ImGui::ShowMetricsWindow(&itsShowAppMetrics);
832  if (itsShowImGuiDemo) ImGui::ShowDemoWindow(&itsShowImGuiDemo);
833 
834  // Draw an error popup, if any exception was received through reportError():
835  drawErrorPopup();
836 }
837 
838 // ##############################################################################################################
840 {
841  if (ImGui::BeginMenuBar())
842  {
843  if (ImGui::BeginMenu("File"))
844  {
845  if (ImGui::MenuItem("Quit")) engine()->quit();
846 
847  //ShowExampleMenuFile();
848  ImGui::EndMenu();
849  }
850  /*
851  if (ImGui::BeginMenu("Machine Vision Module"))
852  {
853  auto e = engine();
854 
855  // Start with the JeVois-Pro mappings:
856  size_t idx = 0;
857  e->foreachVideoMapping([&idx,&e](jevois::VideoMapping const & m) {
858  if (m.ofmt == JEVOISPRO_FMT_GUI && ImGui::MenuItem(m.str().c_str()))
859  e->requestSetFormat(idx++); else ++idx;
860  });
861  ImGui::Separator();
862 
863  // Then the compatibility JeVois modules:
864  idx = 0;
865  e->foreachVideoMapping([&idx,&e](jevois::VideoMapping const & m) {
866  if (m.ofmt != 0 && m.ofmt != JEVOISPRO_FMT_GUI && ImGui::MenuItem(m.str().c_str()))
867  e->requestSetFormat(idx++); else ++idx;
868  });
869 
870  ImGui::Separator();
871  // Finally the no-USB modules: FIXME - those currently kill the gui since no startFrame() is emitted
872  idx = 0;
873  e->foreachVideoMapping([&idx,&e](jevois::VideoMapping const & m) {
874  if (m.ofmt == 0 && ImGui::MenuItem(m.str().c_str()))
875  e->requestSetFormat(idx++); else ++idx;
876  });
877 
878  ImGui::EndMenu();
879  }
880  */
881  if (ImGui::BeginMenu("Tools"))
882  {
883  ImGui::MenuItem("ImGui Style Editor", NULL, &itsShowStyleEditor);
884  ImGui::MenuItem("ImGui Metrics/Debugger", NULL, &itsShowAppMetrics);
885  ImGui::MenuItem("ImGui Demo/Doc", NULL, &itsShowImGuiDemo);
886 
887  ImGui::EndMenu();
888  }
889 
890  ImGui::EndMenuBar();
891  }
892 }
893 
894 // ##############################################################################################################
896 {
897  static std::map<std::string, size_t> mods;
898  auto e = engine();
899  static std::string currstr;
900 
901  // Refresh the list of modules if needed:
902  ImGui::AlignTextToFramePadding();
903  ImGui::Text("Module:");
904  ImGui::SameLine();
905  ImGui::SetNextItemWidth(6 * ImGui::GetFontSize() + 5);
906  if (ImGui::Combo("##typemachinevisionmodule", &itsVideoMappingListType, "Pro/GUI\0Legacy\0Headless\0\0")
907  || currstr.empty() || itsRefreshVideoMappings)
908  {
909  // Recompute mods for the selected type:
910  itsRefreshVideoMappings = false;
911  mods.clear();
912 
913  switch (itsVideoMappingListType)
914  {
915  case 0:
916  {
917  // JeVois-Pro GUI mappings:
918  size_t idx = 0;
919  e->foreachVideoMapping([&idx](jevois::VideoMapping const & m) {
920  if (m.ofmt == JEVOISPRO_FMT_GUI) mods[m.menustr()] = idx;
921  ++idx;
922  });
923  }
924  break;
925 
926  case 1:
927  {
928  // Legacy JeVois mappings:
929  size_t idx = 0;
930  e->foreachVideoMapping([&idx](jevois::VideoMapping const & m) {
931  if (m.ofmt != 0 && m.ofmt != JEVOISPRO_FMT_GUI) mods[m.menustr()] = idx;
932  ++idx;
933  });
934  }
935  break;
936 
937  case 2:
938  {
939  // Headless modules
940  size_t idx = 0;
941  e->foreachVideoMapping([&idx](jevois::VideoMapping const & m) {
942  if (m.ofmt == 0) mods[m.menustr()] = idx;
943  ++idx;
944  });
945  }
946  break;
947 
948  default: LFATAL("Internal error, itsVideoMappingListType=" << itsVideoMappingListType);
949  }
950 
951  // Refresh our display string:
952  currstr = e->getCurrentVideoMapping().menustr().c_str();
953  }
954 
955  // Draw the module selector and allow selection:
956  ImGui::SameLine();
957 
958  if (ImGui::BeginCombo("##machinevisionmodule", currstr.c_str()))
959  {
960  for (auto const & m : mods)
961  {
962  bool is_selected = false;
963  if (ImGui::Selectable(m.first.c_str(), is_selected))
964  {
965  e->requestSetFormat(m.second);
966  currstr.clear();
967  }
968 
969  }
970  ImGui::EndCombo();
971  }
972 }
973 
974 // ##############################################################################################################
976 {
977  std::shared_ptr<jevois::Module> m = engine()->module();
978  if (m)
979  {
980  // Get the icon and display it:
981  if (itsIcon.loaded() == false)
982  try { itsIcon.load(m->absolutePath("icon.png")); }
983  catch (...)
984  {
985  // Load a default C++ or Python icon:
986  LERROR("This module has no icon -- USING DEFAULT");
987  try
988  {
989  if (engine()->getCurrentVideoMapping().ispython) itsIcon.load(JEVOIS_SHARE_PATH "/icons/py.png");
990  else itsIcon.load(JEVOIS_SHARE_PATH "/icons/cpp.png");
991  }
992  catch (...)
993  {
994  // Defaults icons not found, just use a blank:
995  cv::Mat blank(32, 32, CV_8UC4, 0);
996  itsIcon.load(blank);
997  }
998  }
999 
1000  if (itsIcon.loaded())
1001  {
1002  int const siz = ImGui::CalcTextSize(" ").x;
1003  itsIcon.draw(ImGui::GetCursorScreenPos(), ImVec2(siz, siz));
1004  }
1005 
1006  // Get the html doc if we have not yet parsed it:
1007  if (itsModName.empty())
1008  {
1009  std::filesystem::path fname = m->absolutePath("modinfo.html");
1010  std::ifstream ifs(fname);
1011  if (ifs.is_open() == false)
1012  {
1013  // No modinfo file, maybe we can create one if we have the source:
1014  LINFO("Recomputing module's modinfo.html ...");
1015  jevois::VideoMapping const & vm = engine()->getCurrentVideoMapping();
1016  try
1017  {
1018  std::string const cmdout =
1019  jevois::system("cd " + m->absolutePath().string() + " && "
1020  "JEVOIS_SRC_ROOT=none jevois-modinfo " + vm.modulename, true);
1021 
1022  if (! std::filesystem::exists(fname))
1023  throw std::runtime_error("Failed to create " + vm.modinfopath() + ": " + cmdout);
1024  }
1025  catch (...) { itsModName = vm.modulename; itsModAuth = "Cannot read file: " + fname.string(); }
1026  // If we did not throw, itsModName is still empty, but now modinfo.html exists. Will load it next time.
1027  }
1028  else
1029  {
1030  int state = 0;
1031  for (std::string s; std::getline(ifs, s); )
1032  switch (state)
1033  {
1034  case 0: // Looking for module display name
1035  {
1036  std::string const str = jevois::extractString(s, "<td class=modinfoname>", "</td>");
1037  if (str.empty() == false) { itsModName = str; ++state; }
1038  break;
1039  }
1040 
1041  case 1: // Looking for short synopsis
1042  {
1043  std::string const str = jevois::extractString(s, "<td class=modinfosynopsis>", "</td>");
1044  if (str.empty() == false) { itsModDesc = str; ++state; }
1045  break;
1046  }
1047 
1048  case 2: // Looking for author info
1049  {
1050  std::string const str = jevois::extractString(s, "<table class=modinfoauth width=100%>", "</table>");
1051  if (str.empty() == false)
1052  {
1053  itsModAuth = jevois::join(jevois::split(str, "<[^<]*>"), " ").substr(2); // remove HTML tags
1054  ++state;
1055  }
1056  break;
1057  }
1058 
1059  case 3: // Looking for language info
1060  {
1061  std::string const str = jevois::extractString(s, "<table class=moduledata>", "</table>");
1062  if (str.empty() == false)
1063  {
1064  itsModLang = jevois::join(jevois::split(str, "<[^<]*>"), " "); // remove HTML tags
1065  auto tok = jevois::split(itsModLang, "&nbsp;");
1066  if (tok.size() >= 3)
1067  {
1068  if (jevois::stringStartsWith(tok[2], "C++")) itsModLang = "Language: C++";
1069  else itsModLang = "Language: Python";
1070  }
1071  else itsModLang = "Language: Unknown";
1072  ++state;
1073  }
1074  break;
1075  }
1076 
1077  case 4: // Looking for main doc start
1078  {
1079  std::string const str = jevois::extractString(s, "<td class=modinfodesc>", "");
1080  if (str.empty() == false)
1081  {
1082  std::string str2 = jevois::extractString(str, "<div class=\"textblock\">", "");
1083  str2 = jevois::join(jevois::split(str2, "<[^<]*>"), " ");
1084  size_t idx = str2.find_first_not_of(" "); if (idx != str2.npos) str2 = str2.substr(idx);
1085  itsModDoc.emplace_back(str2);
1086  ++state;
1087  }
1088  break;
1089  }
1090 
1091  case 5: // Extracting main doc until its end marker is encountered
1092  {
1093  if (s == "</div></td></tr>") ++state;
1094  else
1095  {
1096  std::string ss = jevois::join(jevois::split(s, "<[^<]*>"), " ");
1097  std::string prefix;
1098 
1099  if (s.find("/h1>") != s.npos || s.find("/h2>") != s.npos || s.find("/h3>") != s.npos) prefix = "* ";
1100  if (s.find("<li>") != s.npos) prefix = "- ";
1101  ss = prefix + ss;
1102 
1103  // Finally fix multiple spaces:
1104  // https://stackoverflow.com/questions/8362094/replace-multiple-spaces-with-one-space-in-a-string
1105  std::string::iterator new_end =
1106  std::unique(ss.begin(), ss.end(), [](char lhs, char rhs) { return (lhs == rhs) && (lhs == ' '); });
1107  ss.erase(new_end, ss.end());
1108  size_t idx = ss.find_first_not_of(" "); if (idx != ss.npos) ss = ss.substr(idx);
1109  itsModDoc.push_back(ss);
1110  }
1111  }
1112 
1113  default: break;
1114  }
1115  ifs.close();
1116  }
1117  }
1118 
1119  // Display the doc:
1120  ImGui::TextUnformatted((" " + itsModName).c_str());
1121  ImGui::TextUnformatted((" " + itsModDesc).c_str());
1122  ImGui::TextUnformatted((" " + itsModAuth + " " + itsModLang).c_str());
1123  ImGui::TextUnformatted(" ");
1124 
1125  int wrap = ImGui::GetCursorPos().x + ImGui::GetWindowSize().x - ImGui::GetFontSize() * 2.0f;
1126  if (wrap < 200) wrap = 200;
1127  ImGui::PushTextWrapPos(wrap);
1128 
1129  bool show = true;
1130  for (std::string const & s : itsModDoc)
1131  {
1132  // Create collapsible header and get its collapsed status:
1133  if (jevois::stringStartsWith(s, "* "))
1134  show = ImGui::CollapsingHeader(s.c_str() + 2, ImGuiTreeNodeFlags_DefaultOpen);
1135  else if (show)
1136  {
1137  // If header not collapsed, show data:
1138  if (jevois::stringStartsWith(s, "- ")) { ImGui::Bullet(); ImGui::TextUnformatted(s.c_str() + 2); }
1139  else ImGui::TextUnformatted(s.c_str());
1140  }
1141  }
1142  ImGui::PopTextWrapPos();
1143  }
1144  else
1145  ImGui::TextUnformatted("No JeVois Module currently loaded.");
1146 }
1147 
1148 // ##############################################################################################################
1149 void jevois::GUIhelper::setparstr(std::string const & descriptor, std::string const & val)
1150 {
1151  try { engine()->setParamStringUnique(descriptor, val); }
1152  catch (...) { reportError(jevois::warnAndIgnoreException()); }
1153  // drawGUI() will call drawErrorPopup() which will show the popup
1154 }
1155 
1156 // ##############################################################################################################
1158 {
1159  static bool show_frozen = true; static bool show_system = false;
1160 
1161  toggleButton("Show Frozen Parameters", &show_frozen);
1162  ImGui::SameLine(ImGui::GetWindowWidth() - ImGui::CalcTextSize("Show System Parameters").x - 30.0f);
1163  toggleButton("Show System Parameters", &show_system);
1164 
1165  jevois::Engine * e = engine();
1166  jevois::Component * c; if (show_system) c = e; else c = e->module().get();
1167 
1168  // Stop here if we want to show module params but we have no module:
1169  if (c == nullptr) { ImGui::TextUnformatted("No module loaded."); return; }
1170 
1171  // Record any ambiguous parameter names, so we can prefix them by component name:
1172  std::set<std::string> pnames, ambig;
1173 
1174  // Get all the parameter summaries:
1175  std::map<std::string /* categ */, std::vector<jevois::ParameterSummary>> psm;
1176  c->foreachParam([this, &psm, &pnames, &ambig](std::string const &, jevois::ParameterBase * p) {
1177  jevois::ParameterSummary psum = p->summary();
1178  if (pnames.insert(psum.name).second == false) ambig.insert(psum.name);
1179  psm[psum.category].push_back(std::move(psum)); } );
1180 
1181  // Stop here if no params to display:
1182  if (psm.empty()) { ImGui::Text("This module has no parameters."); return; }
1183 
1184  // Create a collapsing header for each parameter category:
1185  int widgetnum = 0; // we need a unique ID for each widget
1186  float maxlen = 0.0f;
1187  for (auto const & pp : psm)
1188  {
1189  // Do not even show a header if all params under it are frozen and we do not want to show frozen params:
1190  if (show_frozen == false)
1191  {
1192  bool all_frozen = true;
1193  for (auto const & ps : pp.second) if (ps.frozen == false) { all_frozen = false; break; }
1194  if (all_frozen) continue;
1195  }
1196 
1197  // Show a header for this category:
1198  if (ImGui::CollapsingHeader(pp.first.c_str()))
1199  {
1200  ImGui::Columns(3, "parameters");
1201 
1202  // Create a widget for each param:
1203  for (auto const & ps : pp.second)
1204  {
1205  // Skip if frozen and we do not want to show frozen params:
1206  if (ps.frozen && show_frozen == false) continue;
1207 
1208  // We need a unique ID for each ImGui widget, and we will use no visible widget name:
1209  static char wname[16]; snprintf(wname, 16, "##p%d", widgetnum);
1210  bool reset = true; // will set to false if we do not want a reset button
1211 
1212  // Start with parameter name and a tooltip with its descriptor:
1213  ImGui::AlignTextToFramePadding();
1214  std::string nam = ps.name;
1215  if (ambig.contains(nam))
1216  {
1217  // Here we are only going to disambiguate by the owning component name:
1218  auto tok = jevois::split(ps.descriptor, ":");
1219  if (tok.size() >= 2) nam = tok[tok.size()-2] + ':' + nam;
1220  }
1221 
1222  ImGui::TextUnformatted(nam.c_str());
1223  if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", ps.descriptor.c_str());
1224  maxlen = std::max(maxlen, ImGui::CalcTextSize(nam.c_str()).x);
1225 
1226  // Add a tooltip:
1227  ImGui::NextColumn();
1228  ImGui::AlignTextToFramePadding();
1229  std::string const & vtype = ps.valuetype;
1230  if (jevois::stringStartsWith(ps.validvalues, "None:"))
1231  helpMarker(ps.description.c_str(), ("Parameter type: " + vtype).c_str());
1232  else
1233  helpMarker(ps.description.c_str(), ("Parameter type: " + vtype).c_str(),
1234  ("Allowed values: " + ps.validvalues).c_str());
1235 
1236  // Now a widget for the parameter:
1237  ImGui::NextColumn();
1238  bool const is_uchar = (vtype == "unsigned char");
1239  bool const is_int = (vtype == "short" || vtype == "int" || vtype == "long int" || vtype == "long long int");
1240  bool const is_uint = (is_uchar || vtype == "unsigned short" || vtype == "unsigned int" ||
1241  vtype == "unsigned long int" || vtype == "unsigned long long int" || vtype == "size_t");
1242  bool const is_real = (vtype == "float" || vtype == "double" || vtype == "long double");
1243 
1244  // Grey out the item if it is disabled:
1245  if (ps.frozen)
1246  {
1247  ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
1248  ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
1249  }
1250 
1251  // ----------------------------------------------------------------------
1252  if (jevois::stringStartsWith(ps.validvalues, "List:["))
1253  {
1254  std::string vals = ps.validvalues.substr(6, ps.validvalues.size() - 7);
1255  auto vv = jevois::split(vals, "\\|");
1256 
1257  if (vv.empty() == false)
1258  {
1259  // Find the index of the current value:
1260  int index = 0; for (auto const & v : vv) if (v == ps.value) break; else ++index;
1261  if (ImGui::Combo(wname, &index,
1262  [](void * vec, int idx, const char ** out_text)
1263  {
1264  auto & ve = *static_cast<std::vector<std::string>*>(vec);
1265  if (idx < 0 || idx >= static_cast<int>(ve.size())) return false;
1266  *out_text = ve.at(idx).c_str();
1267  return true;
1268  },
1269  static_cast<void *>(&vv), vv.size()))
1270  setparstr(ps.descriptor, vv[index]);
1271  }
1272  }
1273  // ----------------------------------------------------------------------
1274  else if (is_uchar || jevois::stringStartsWith(ps.validvalues, "Range:["))
1275  {
1276  if (is_uchar)
1277  {
1278  // For unsigned char, use a slider:
1279  unsigned long val; jevois::paramStringToVal(ps.value, val);
1280  std::string rng = ps.validvalues.substr(7, ps.validvalues.size() - 8);
1281 
1282  // We may or not have a range spec, if not, use 0..255:
1283  if (rng.empty())
1284  {
1285  long mi = 0, ma = 255;
1286  if (ImGui::SliderScalar(wname, ImGuiDataType_U64, &val, &mi, &ma))
1287  setparstr(ps.descriptor, std::to_string(val));
1288  }
1289  else
1290  {
1292  if (ImGui::SliderScalar(wname, ImGuiDataType_U64, &val, &r.min(), &r.max()))
1293  setparstr(ps.descriptor, std::to_string(val));
1294  }
1295  }
1296  else if (is_uint)
1297  {
1298  // For uint that has a range specified, use a slider:
1299  std::string rng = ps.validvalues.substr(7, ps.validvalues.size() - 8);
1301  unsigned long val; jevois::paramStringToVal(ps.value, val);
1302  if (ImGui::SliderScalar(wname, ImGuiDataType_U64, &val, &r.min(), &r.max()))
1303  setparstr(ps.descriptor, std::to_string(val));
1304  }
1305  else if (is_int)
1306  {
1307  // For int that has a range specified, use a slider:
1308  std::string rng = ps.validvalues.substr(7, ps.validvalues.size() - 8);
1310  long val; jevois::paramStringToVal(ps.value, val);
1311  if (ImGui::SliderScalar(wname, ImGuiDataType_S64, &val, &r.min(), &r.max()))
1312  setparstr(ps.descriptor, std::to_string(val));
1313  }
1314  else if (is_real)
1315  {
1316  // For real with a range specified, use a slider:
1317  std::string rng = ps.validvalues.substr(7, ps.validvalues.size() - 8);
1319  double val; jevois::paramStringToVal(ps.value, val);
1320  if (ImGui::SliderScalar(wname, ImGuiDataType_Double, &val, &r.min(), &r.max()))
1321  setparstr(ps.descriptor, std::to_string(val));
1322  }
1323  else
1324  {
1325  // For more complex types, just allow free typing, parameter will do the checking:
1326  char buf[256]; strncpy(buf, ps.value.c_str(), sizeof(buf)-1);
1327  if (ImGui::InputText(wname, buf, sizeof(buf), ImGuiInputTextFlags_EnterReturnsTrue))
1328  setparstr(ps.descriptor, buf);
1329  }
1330  }
1331  // ----------------------------------------------------------------------
1332  else if (vtype == "jevois::Range<unsigned char>")
1333  {
1334  // For a range parameter, use a double drag:
1336  int mi = val.min(), ma = val.max();
1337  if (ImGui::DragIntRange2(wname, &mi, &ma, 0, 0, 255, "Min: %d", "Max: %d"))
1338  setparval(ps.descriptor, jevois::Range<unsigned char>(mi, ma));
1339  }
1340  // ----------------------------------------------------------------------
1341  else if (vtype == "bool")
1342  {
1343  bool val; jevois::paramStringToVal(ps.value, val);
1344  if (ImGui::Checkbox(wname, &val)) setparval(ps.descriptor, val);
1345  }
1346  // ----------------------------------------------------------------------
1347  else if (vtype == "ImColor")
1348  {
1349  ImColor val; jevois::paramStringToVal(ps.value, val);
1350  if (ImGui::ColorEdit4(wname, (float *)&val, ImGuiColorEditFlags_AlphaPreview)) setparval(ps.descriptor, val);
1351  }
1352  // ----------------------------------------------------------------------
1353  else
1354  {
1355  // User will type in some value, parameter will check it:
1356  char buf[256]; strncpy(buf, ps.value.c_str(), sizeof(buf)-1);
1357  if (ImGui::InputText(wname, buf, sizeof(buf), ImGuiInputTextFlags_EnterReturnsTrue))
1358  setparstr(ps.descriptor, buf);
1359  }
1360 
1361  // Possibly add a reset button:
1362  if (reset)
1363  {
1364  static char rname[18]; snprintf(rname, 18, "Reset##%d", widgetnum);
1365  ImGui::SameLine();
1366  if (ImGui::Button(rname)) setparstr(ps.descriptor, ps.defaultvalue);
1367  }
1368 
1369  // Restore any grey out:
1370  if (ps.frozen)
1371  {
1372  ImGui::PopItemFlag();
1373  ImGui::PopStyleVar();
1374  }
1375 
1376  // Ready for next row:
1377  ImGui::NextColumn(); ++widgetnum;
1378  }
1379 
1380  // Back to single column before the next param categ:
1381  ImGui::Columns(1);
1382  }
1383  }
1384 
1385  if (maxlen)
1386  {
1387  ImGui::Columns(3, "parameters");
1388  ImGui::SetColumnWidth(0, maxlen + ImGui::CalcTextSize("XX").x);
1389  ImGui::SetColumnWidth(1, ImGui::CalcTextSize(" (?) ").x);
1390  ImGui::SetColumnWidth(2, 2000);
1391  ImGui::Columns(1);
1392  }
1393 }
1394 
1395 // ##############################################################################################################
1397 {
1398  jevois::Engine * e = engine();
1399 
1400  // Start with toggle buttons for serlog and serout:
1401  bool slusb = false, slhard = false, schanged = false;
1402  auto sl = e->getParamValUnique<jevois::engine::SerPort>("engine:serlog");
1403  switch (sl)
1404  {
1405  case jevois::engine::SerPort::None: slusb = false; slhard = false; break;
1406  case jevois::engine::SerPort::All: slusb = true; slhard = true; break;
1407  case jevois::engine::SerPort::Hard: slusb = false; slhard = true; break;
1408  case jevois::engine::SerPort::USB: slusb = true; slhard = false; break;
1409  }
1410  ImGui::AlignTextToFramePadding();
1411  ImGui::Text("Log messages:"); ImGui::SameLine();
1412  if (itsUSBserial == false) // grey out USB button if no driver
1413  {
1414  ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
1415  ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
1416  if (toggleButton("USB##serlogu", &slusb)) schanged = true;
1417  ImGui::PopItemFlag();
1418  ImGui::PopStyleVar();
1419 #ifdef JEVOIS_PLATFORM
1420  if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
1421  { ImGui::BeginTooltip(); ImGui::Text("Disabled - enable USB serial in the System tab"); ImGui::EndTooltip(); }
1422 #else
1423  if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
1424  { ImGui::BeginTooltip(); ImGui::Text("Disabled - not available on host"); ImGui::EndTooltip(); }
1425 #endif
1426  }
1427  else if (toggleButton("USB##serlogu", &slusb)) schanged = true;
1428 
1429  ImGui::SameLine(); if (toggleButton("Hard##serlogh", &slhard)) schanged = true;
1430  ImGui::SameLine(); toggleButton("Cons##serlogc", &itsSerLogEnabled);
1431  ImGui::SameLine(0, 50);
1432 
1433  bool sousb = false, sohard = false;
1434  auto so = e->getParamValUnique<jevois::engine::SerPort>("engine:serout");
1435  switch (so)
1436  {
1437  case jevois::engine::SerPort::None: sousb = false; sohard = false; break;
1438  case jevois::engine::SerPort::All: sousb = true; sohard = true; break;
1439  case jevois::engine::SerPort::Hard: sousb = false; sohard = true; break;
1440  case jevois::engine::SerPort::USB: sousb = true; sohard = false; break;
1441  }
1442  ImGui::Text("Module output:"); ImGui::SameLine();
1443  if (itsUSBserial == false) // grey out USB button if no driver
1444  {
1445  ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
1446  ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
1447  if (toggleButton("USB##seroutu", &sousb)) schanged = true;
1448  ImGui::PopItemFlag();
1449  ImGui::PopStyleVar();
1450 #ifdef JEVOIS_PLATFORM
1451  if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
1452  { ImGui::BeginTooltip(); ImGui::Text("Disabled - enable USB serial in the System tab"); ImGui::EndTooltip(); }
1453 #else
1454  if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
1455  { ImGui::BeginTooltip(); ImGui::Text("Disabled - not available on host"); ImGui::EndTooltip(); }
1456 #endif
1457  }
1458  else if (toggleButton("USB##seroutu", &sousb)) schanged = true;
1459  ImGui::SameLine(); if (toggleButton("Hard##serouth", &sohard)) schanged = true;
1460  ImGui::SameLine(); toggleButton("Cons##seroutc", &itsSerOutEnabled);
1461 
1462  if (schanged)
1463  {
1464  if (slusb)
1465  {
1466  if (slhard) e->setParamValUnique("engine:serlog", jevois::engine::SerPort::All);
1467  else e->setParamValUnique("engine:serlog", jevois::engine::SerPort::USB);
1468  }
1469  else
1470  {
1471  if (slhard) e->setParamValUnique("engine:serlog", jevois::engine::SerPort::Hard);
1472  else e->setParamValUnique("engine:serlog", jevois::engine::SerPort::None);
1473  }
1474 
1475  if (sousb)
1476  {
1477  if (sohard) e->setParamValUnique("engine:serout", jevois::engine::SerPort::All);
1478  else e->setParamValUnique("engine:serout", jevois::engine::SerPort::USB);
1479  }
1480  else
1481  {
1482  if (sohard) e->setParamValUnique("engine:serout", jevois::engine::SerPort::Hard);
1483  else e->setParamValUnique("engine:serout", jevois::engine::SerPort::None);
1484  }
1485  }
1486 
1487  // Now a combo for serstyle:
1488  auto m = dynamic_cast<jevois::StdModule *>(e->module().get());
1489  if (m)
1490  {
1491  auto sp = m->getParamValUnique<jevois::module::SerStyle>("serstyle");
1492  int idx = 0; for (auto const & v : jevois::module::SerStyle_Values) if (v == sp) break; else ++idx;
1493 
1494  ImGui::SameLine();
1495  if (ImGui::Combo("serstyle", &idx,
1496  [](void * vec, int idx, const char ** out_text)
1497  {
1498  auto & ve = *static_cast<std::vector<std::string>*>(vec);
1499  if (idx < 0 || idx >= static_cast<int>(ve.size())) return false;
1500  *out_text = ve.at(idx).c_str();
1501  return true;
1502  },
1503  const_cast<void *>(static_cast<void const *>(&jevois::module::SerStyle_Strings)),
1504  jevois::module::SerStyle_Strings.size()))
1505  try { e->setParamValUnique("serstyle", jevois::module::SerStyle_Values[idx]); }
1506  catch (...) { jevois::warnAndIgnoreException(); }
1507  }
1508  ImGui::Separator();
1509 
1510  // Now a console:
1511  auto c = e->getComponent<jevois::GUIconsole>("guiconsole");
1512  if (c) c->draw();
1513 }
1514 
1515 // ##############################################################################################################
1517 { return itsSerLogEnabled; }
1518 
1519 // ##############################################################################################################
1521 { return itsSerOutEnabled; }
1522 
1523 // ##############################################################################################################
1525 {
1526  engine()->drawCameraGUI();
1527 }
1528 
1529 // ##############################################################################################################
1530 int jevois::GUIhelper::modal(std::string const & title, char const * text, int * default_val,
1531  char const * b1txt, char const * b2txt)
1532 {
1533  // Handle optional default_val pointer:
1534  int ret = 0; int * retptr = default_val ? default_val : &ret;
1535 
1536  // Do we want to just return the default value?
1537  if (*retptr == 1 || *retptr == 2) return *retptr;
1538 
1539  // Open the modal if needed, and remember it:
1540  if (itsOpenModals.find(title) == itsOpenModals.end())
1541  {
1542  ImGui::OpenPopup(title.c_str());
1543  itsOpenModals.insert(title);
1544  }
1545 
1546  // Display the modal and get any button clicks:
1547  bool dont_ask_me_next_time = (*retptr == 3);
1548 
1549  if (ImGui::BeginPopupModal(title.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize))
1550  {
1551  ImGui::TextUnformatted(text); ImGui::TextUnformatted(" ");
1552  ImGui::Separator();
1553  if (default_val)
1554  {
1555  ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
1556  ImGui::PushStyleColor(ImGuiCol_FrameBg, 0xf0ffe0e0); // ABGR - in light theme, can't see the box
1557  ImGui::Checkbox("Don't ask me next time", &dont_ask_me_next_time);
1558  ImGui::PopStyleColor();
1559  ImGui::PopStyleVar();
1560  }
1561  float const b1w = std::max(120.0F, ImGui::CalcTextSize(b1txt).x + 20.0F);
1562  if (ImGui::Button(b1txt, ImVec2(b1w, 0))) ret = 1;
1563  ImGui::SetItemDefaultFocus();
1564  ImGui::SameLine();
1565  float const b2w = std::max(120.0F, ImGui::CalcTextSize(b2txt).x + 20.0F);
1566  if (ImGui::Button(b2txt, ImVec2(b2w, 0))) ret = 2;
1567  ImGui::EndPopup();
1568  }
1569 
1570  // Close the modal if button clicked:
1571  if (ret == 1 || ret == 2)
1572  {
1573  ImGui::CloseCurrentPopup();
1574  itsOpenModals.erase(title);
1575  if (dont_ask_me_next_time) *retptr = ret; // remember the choice as new default choice
1576  }
1577  else *retptr = dont_ask_me_next_time ? 3 : 0; // propagate checkbox status
1578 
1579  return ret;
1580 }
1581 
1582 // ##############################################################################################################
1583 void jevois::GUIhelper::newModEntry(char const * wname, std::string & str, char const * desc,
1584  char const * hint, char const * hlp)
1585 {
1586  static char buf[256];
1587  ImGui::AlignTextToFramePadding();
1588  ImGui::TextUnformatted(desc);
1589  ImGui::NextColumn();
1590  helpMarker(desc, hlp);
1591  ImGui::NextColumn();
1592  strncpy(buf, str.c_str(), sizeof(buf)-1);
1593  if (ImGui::InputTextWithHint(wname, hint, buf, sizeof(buf))) str = buf;
1594  ImGui::NextColumn();
1595 }
1596 
1597 // ##############################################################################################################
1599 {
1600  float const fontw = ImGui::GetFontSize();
1601 
1602  static int refresh = 1;
1603  static std::string cpu, mem, ver;
1604  static size_t npu, tpu, vpu, spu; static int fan;
1605  if (--refresh == 0)
1606  {
1607  refresh = 60;
1608  cpu = jevois::getSysInfoCPU();
1609  mem = jevois::getSysInfoMem();
1610  ver = jevois::getSysInfoVersion();
1615  fan = jevois::getFanSpeed();
1616  }
1617  ImGui::Text("JeVois-Pro v%s -- %s", JEVOIS_VERSION_STRING, ver.c_str());
1618  ImGui::Text(cpu.c_str());
1619  ImGui::Text(mem.c_str());
1620  ImGui::Text("NPU: %d, TPU: %d, VPU: %d, SPU: %d. Fan: %d%%", npu, tpu, vpu, spu, fan);
1621  ImGui::Separator();
1622 
1623  // #################### Create new module:
1624  drawNewModuleForm();
1625  ImGui::Separator();
1626 
1627  // #################### ping:
1628  static std::string pingstr;
1629  static int showping = 0;
1630  if (ImGui::Button("Ping jevois.usc.edu"))
1631  {
1632  std::string ret = jevois::system("/usr/bin/ping -c 1 -w 2 jevois.usc.edu");
1633  std::vector<std::string> rvec = jevois::split(ret, "\n");
1634  if (rvec.size() < 2) reportError("Unable to ping jevois.usc.edu");
1635  else { pingstr = rvec[1]; showping = 60; }
1636  }
1637  if (showping)
1638  {
1639  ImGui::SameLine();
1640  ImGui::Text(pingstr.c_str());
1641  --showping;
1642  }
1643 
1644  ImGui::Separator();
1645 
1646  // #################### dnnget:
1647  static std::string zip;
1648  static std::string donestr;
1649  static int state = 0;
1650 
1651  ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue;
1652  if (state) flags |= ImGuiInputTextFlags_ReadOnly;
1653  ImGui::SetNextItemWidth(fontw * 6.0F);
1654  char buf[5] = { };
1655  if (ImGui::InputText("Load Custom DNN", buf, sizeof(buf), flags)) state = 1;
1656  ImGui::SameLine();
1657 
1658  switch (state)
1659  {
1660  case 0:
1661  ImGui::Text(" ");
1662  break;
1663 
1664  case 1:
1665  {
1666  ImGui::Text("-- Downloading...");
1667  zip = std::string(buf) + ".zip";
1668  itsDnnGetFut =
1670  {
1671  return jevois::system("/usr/bin/curl " JEVOIS_CUSTOM_DNN_URL "/" + zip + " -o "
1672  JEVOIS_CUSTOM_DNN_PATH "/" + zip, true);
1673  });
1674  state = 2;
1675  }
1676  break;
1677 
1678  case 2:
1679  {
1680  if (itsDnnGetFut.valid() == false) { reportError("Unknown error while loading custom DNN"); state = 0; break; }
1681  ImGui::Text("-- Downloading...");
1682  if (itsDnnGetFut.wait_for(std::chrono::microseconds(100)) == std::future_status::ready)
1683  {
1684  itsDnnGetFut.get();
1685 
1686  // Check that the file exists:
1687  std::ifstream ifs(JEVOIS_CUSTOM_DNN_PATH "/" + zip);
1688  if (ifs.is_open() == false)
1689  {
1690  reportError("Failed to download. Check network connectivity and available disk space.");
1691  state = 0;
1692  break;
1693  }
1694  itsDnnGetFut =
1696  {
1697  return jevois::system("/usr/bin/unzip -o " JEVOIS_CUSTOM_DNN_PATH "/" + zip +
1698  " -d " JEVOIS_CUSTOM_DNN_PATH, true);
1699  });
1700  state = 3;
1701  }
1702  }
1703  break;
1704 
1705  case 3:
1706  {
1707  if (itsDnnGetFut.valid() == false) { reportError("Unknown error while unpacking custom DNN"); state = 0; break; }
1708  ImGui::Text("-- Installing...");
1709  if (itsDnnGetFut.wait_for(std::chrono::microseconds(100)) == std::future_status::ready)
1710  {
1711  std::string ret = itsDnnGetFut.get();
1712  std::vector<std::string> rvec = jevois::split(ret, "\n");
1713  if (rvec.size() > 2 && jevois::stringStartsWith(rvec[1], " End-of-central-directory signature not found"))
1714  donestr = "-- Invalid file, check DNN download key.";
1715  else
1716  donestr = "-- Done. Reload model zoo to take effect.";
1717  jevois::system("/bin/rm " JEVOIS_CUSTOM_DNN_PATH "/" + zip, true);
1718  state = 4;
1719  }
1720  }
1721  break;
1722 
1723  case 200:
1724  ImGui::Text(" ");
1725  state = 0;
1726  break;
1727 
1728  default:
1729  ImGui::Text(donestr.c_str());
1730  ++state;
1731  }
1732  ImGui::Separator();
1733 
1734 #ifdef JEVOIS_PLATFORM
1735  // #################### boot mode:
1736  static int bootmode = 0;
1737  ImGui::AlignTextToFramePadding();
1738  ImGui::Text("On boot, start:");
1739  ImGui::SameLine();
1740  if (ImGui::Combo("##onboot", &bootmode, "(no change)\0JeVois-Pro\0Ubuntu Console\0Ubuntu Graphical\0\0"))
1741  {
1742  switch (bootmode)
1743  {
1744  case 0: break;
1745  case 2: jevois::system("systemctl --no-reload set-default multi-user.target"); break;
1746  case 3: jevois::system("systemctl --no-reload set-default graphical.target"); break;
1747  default: jevois::system("systemctl --no-reload set-default jevoispro.target"); break;
1748  }
1749  jevois::system("sync");
1750  }
1751  ImGui::Separator();
1752 
1753  // #################### gadget serial:
1754  static bool gserial = false;
1755  try { gserial = (0 != std::stoi(jevois::getFileString(JEVOISPRO_GSERIAL_FILE))); } catch (...) { }
1756  if (ImGui::Checkbox("Enable serial outputs/logs over mini-USB (on next reboot)", &gserial))
1757  {
1758  std::ofstream ofs(JEVOISPRO_GSERIAL_FILE);
1759  ofs << (gserial ? 1 : 0) << std::endl;
1760  ofs.close();
1761  jevois::system("sync");
1762  }
1763  ImGui::Separator();
1764 
1765  // #################### Edit fan settings:
1766  static bool show_fan_modal = false;
1767  if (ImGui::Button("Edit fan settings"))
1768  {
1769  itsCfgEditor->loadFile("/lib/systemd/system/jevoispro-fan.service");
1770  show_fan_modal = true;
1771  }
1772 
1773  if (show_fan_modal)
1774  {
1775  static int doit_default = 0;
1776  int ret = modal("Ready to edit", "File will now be loaded in the Config tab of the main window and "
1777  "ready to edit.\nPlease switch to the Config tab in the main window.\n\n"
1778  "When you save it, we will reboot the camera.",
1779  &doit_default, "Ok", "Thanks");
1780  switch (ret)
1781  {
1782  case 1: show_fan_modal = false; break;
1783  case 2: show_fan_modal = false; break;
1784  default: break; // Need to wait
1785  }
1786  }
1787 
1788  ImGui::Separator();
1789 
1790 #endif
1791 
1792 }
1793 
1794 // ##############################################################################################################
1795 namespace
1796 {
1797  unsigned int get_v4l2_fmt(int idx)
1798  {
1799  switch (idx)
1800  {
1801  case 0: return V4L2_PIX_FMT_YUYV;
1802  case 1: return V4L2_PIX_FMT_RGB24;
1803  case 2: return V4L2_PIX_FMT_RGB32;
1804  case 3: return V4L2_PIX_FMT_GREY;
1805  case 4: return V4L2_PIX_FMT_SBGGR16;
1806  default: return 0;
1807  }
1808  }
1809 
1810  unsigned int get_v4l2_idx(int fcc)
1811  {
1812  switch (fcc)
1813  {
1814  case V4L2_PIX_FMT_YUYV: return 0;
1815  case V4L2_PIX_FMT_RGB24: return 1;
1816  case V4L2_PIX_FMT_RGB32: return 2;
1817  case V4L2_PIX_FMT_GREY: return 3;
1818  case V4L2_PIX_FMT_SBGGR16: return 4;
1819  default: return 0;
1820  }
1821  }
1822 
1823  // Copy a file and replace special fields:
1824  void cookedCopy(std::filesystem::path const & src, std::filesystem::path const & dst,
1825  std::string const & name, std::string const & vendor, std::string const & synopsis,
1826  std::string const & author, std::string const & email, std::string const & website,
1827  std::string const & license, std::string const & videomapping)
1828  {
1829  std::ifstream f(src);
1830  if (f.is_open() == false) LFATAL("Cannot read " << src);
1831  std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
1832 
1833  std::string proj = jevois::tolower(name);
1834 
1835  jevois::replaceStringAll(str, "__MODULE__", name);
1836  jevois::replaceStringAll(str, "__PROJECT__", proj);
1837  jevois::replaceStringAll(str, "__VENDOR__", vendor);
1838  jevois::replaceStringAll(str, "__SYNOPSIS__", synopsis);
1839  jevois::replaceStringAll(str, "__AUTHOR__", author);
1840  jevois::replaceStringAll(str, "__EMAIL__", email);
1841  jevois::replaceStringAll(str, "__WEBSITE__", website);
1842  jevois::replaceStringAll(str, "__LICENSE__", license);
1843  jevois::replaceStringAll(str, "__VIDEOMAPPING__", videomapping);
1844 
1845  std::ofstream ofs(dst);
1846  if (ofs.is_open() == false) LFATAL("Cannot write " << dst << " -- check that you are running as root.");
1847  ofs << str << std::endl;
1848  LINFO("Translated copy " << src << " => " << dst);
1849  }
1850 
1851  // Copy an existing CMakeLists.txt and replace some fields:
1852  void cookedCmakeClone(std::filesystem::path const & src, std::filesystem::path const & dst,
1853  std::string const & oldname, std::string const & newname,
1854  std::string const & oldvendor, std::string const & newvendor, std::string const & synopsis,
1855  std::string const & author, std::string const & email, std::string const & website,
1856  std::string const & license, std::string const & videomapping)
1857  {
1858  std::ifstream f(src);
1859  if (f.is_open() == false) LFATAL("Cannot read " << src);
1860  std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
1861 
1862  std::string oldproj = jevois::tolower(oldname);
1863  std::string newproj = jevois::tolower(newname);
1864 
1865  jevois::replaceStringAll(str, oldname, newname);
1866  jevois::replaceStringAll(str, oldproj, newproj);
1867  jevois::replaceStringAll(str, oldvendor, newvendor);
1868  jevois::replaceStringAll(str, "__SYNOPSIS__", synopsis);
1869  jevois::replaceStringAll(str, "__AUTHOR__", author);
1870  jevois::replaceStringAll(str, "__EMAIL__", email);
1871  jevois::replaceStringAll(str, "__WEBSITE__", website);
1872  jevois::replaceStringAll(str, "__LICENSE__", license);
1873  jevois::replaceStringAll(str, "__VIDEOMAPPING__", videomapping);
1874 
1875  std::ofstream ofs(dst);
1876  if (ofs.is_open() == false) LFATAL("Cannot write " << dst << " -- check that you are running as root.");
1877  ofs << str << std::endl;
1878  LINFO("Translated copy " << src << " => " << dst);
1879  }
1880 
1881  // Try to replace class name and namespace name. This is very hacky...
1882  void cookedCodeClone(std::filesystem::path const & src, std::filesystem::path const & dst,
1883  std::string const & oldname, std::string const & newname)
1884  {
1885  std::ifstream f(src);
1886  if (f.is_open() == false) LFATAL("Cannot read " << src);
1887  std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
1888 
1889  std::string oldnspc = jevois::tolower(oldname);
1890  std::string newnspc = jevois::tolower(newname);
1891 
1892  jevois::replaceStringAll(str, "class " + oldname, "class " + newname);
1893  jevois::replaceStringAll(str, oldname + '(', newname + '('); // constructor and destructor
1894  jevois::replaceStringAll(str, '(' + oldname, '(' + newname); // JEVOIS_REGISTER_MODULE
1895  jevois::replaceStringAll(str, oldname + "::", newname + "::");
1896 
1897  jevois::replaceStringAll(str, "namespace " + oldnspc, "namespace " + newnspc);
1898  jevois::replaceStringAll(str, oldnspc + "::", newnspc + "::");
1899 
1900  // Revert a few replacements if jevois was involved. E.g., when cloning the DNN module that uses the
1901  // jevois::dnn namespace...
1902  jevois::replaceStringAll(str, "jevois::" + newnspc, "jevois::" + oldnspc);
1903  jevois::replaceStringAll(str, "jevois::" + newname, "jevois::" + oldname);
1904 
1905  std::ofstream ofs(dst);
1906  if (ofs.is_open() == false) LFATAL("Cannot write " << dst << " -- check that you are running as root.");
1907  ofs << str << std::endl;
1908  LINFO("Translated copy " << src << " => " << dst);
1909  }
1910 }
1911 
1912 // ##############################################################################################################
1914 {
1915  float const fontw = ImGui::GetFontSize();
1916  auto e = engine();
1917  static int templ = 0; // Which code template to use (pro, legacy, headless, clone). Resets for each new module.
1918 
1919  ImGui::PushStyleColor(ImGuiCol_PopupBg, 0xf0e0ffe0);
1920 
1921  if (ImGui::Button("Create new machine vision module..."))
1922  {
1923  ImGui::OpenPopup("Create new machine vision module");
1924  itsIdleBlocked = true; // prevent GUI from disappearing if we are slow to fill the form...
1925  templ = 0; // default template pro/GUI each time
1926  }
1927 
1928  ImVec2 const center = ImGui::GetMainViewport()->GetCenter();
1929  ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
1930  ImGui::SetNextWindowContentSize(ImVec2(940, 750));
1931 
1932  if (ImGui::BeginPopupModal("Create new machine vision module", NULL, ImGuiWindowFlags_AlwaysAutoResize))
1933  {
1934  try
1935  {
1936  static std::string name, vendor, synopsis, author, email, website, license, srcvendor, srcname;
1937  static int language = 0;
1938  static int ofmt = 0; static int ow = 320, oh = 240; static float ofps = 30.0F;
1939  static int cmode = 0;
1940  static int wdrmode = 0;
1941  static int cfmt = 0; static int cw = 1920, ch = 1080; static float cfps = 30.0F;
1942  static int c2fmt = 1; static int c2w = 512, c2h = 288;
1943  static jevois::VideoMapping m { };
1944 
1945  ImGui::AlignTextToFramePadding();
1946  ImGui::Text("Fill out the details below, or clone from");
1947  ImGui::SameLine();
1948 
1949  static std::map<std::string, size_t> mods;
1950  static std::string currstr = "...";
1951 
1952  size_t idx = 0;
1953  e->foreachVideoMapping([&idx](jevois::VideoMapping const & m) { mods[m.menustr2()] = idx++; });
1954 
1955  // Draw the module clone selector and allow selection:
1956  if (ImGui::BeginCombo("##clonemodule", currstr.c_str()))
1957  {
1958  for (auto const & mod : mods)
1959  {
1960  bool is_selected = false;
1961  if (ImGui::Selectable(mod.first.c_str(), is_selected))
1962  {
1963  m = e->getVideoMapping(mod.second);
1964  currstr = mod.first;
1965 
1966  // Prefill some data:
1967  vendor = "Testing";
1968  name = "My" + m.modulename; int i = 2;
1969  while (std::filesystem::exists(JEVOIS_MODULE_PATH "/" + vendor + '/' + name))
1970  { name = "My" + m.modulename + std::to_string(i++); }
1971 
1972  language = m.ispython ? 0 : 1;
1973  templ = 3; // clone
1974  ofmt = m.ofmt; ow = m.ow; oh = m.oh; ofps = m.ofps;
1975  switch (m.crop)
1976  {
1977  case jevois::CropType::CropScale: cmode = 0; break;
1978  case jevois::CropType::Crop: cmode = 1; break;
1979  case jevois::CropType::Scale: cmode = 2; break;
1980  }
1981  switch (m.wdr)
1982  {
1983  case jevois::WDRtype::Linear: wdrmode = 0; break;
1984  case jevois::WDRtype::DOL: wdrmode = 1; break;
1985  }
1986  cfmt = get_v4l2_idx(m.cfmt); cw = m.cw; ch = m.ch; cfps = m.cfps;
1987  c2fmt = get_v4l2_idx(m.c2fmt); c2w = m.c2w; c2h = m.c2h;
1988  srcvendor = m.vendor; srcname = m.modulename;
1989  }
1990  }
1991  ImGui::EndCombo();
1992  }
1993 
1994  // Draw the form items:
1995  ImGui::Separator();
1996 
1997  ImGui::Columns(3, "new module");
1998 
1999  newModEntry("##NewModname", name, "Module Name", "MyModule",
2000  "Required, even when cloning. Must start with an uppercase letter. "
2001  "Will be a folder name under /jevoispro/modules/VendorName");
2002 
2003  newModEntry("##NewModvendor", vendor, "Vendor Name", "MyVendor",
2004  "Required, even when cloning. Must start with an uppercase letter. "
2005  "Will be a folder name under /jevoispro/modules/");
2006 
2007  // If cloning, disable all edits except for vendor and module names:
2008  if (templ == 3)
2009  {
2010  ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
2011  ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
2012  }
2013 
2014  newModEntry("##NewModsynopsis", synopsis, "Synopsis", "Detect Object of type X",
2015  "Optional. Brief description of what the module does.");
2016 
2017  newModEntry("##NewModauthor", author, "Author Name", "John Smith", "Optional");
2018 
2019  newModEntry("##NewModemail", email, "Author Email", "you@yourcompany.com", "Optional");
2020 
2021  newModEntry("##NewModwebsite", website, "Author Website", "http://yourcompany.com", "Optional");
2022 
2023  newModEntry("##NewModlicense", license, "License", "GPL v3", "Optional");
2024 
2025  // Language:
2026  ImGui::AlignTextToFramePadding();
2027  ImGui::TextUnformatted("Module Language");
2028  ImGui::NextColumn();
2029  helpMarker("Module Language", "Machine language to use for your module.");
2030  ImGui::NextColumn();
2031  ImGui::Combo("##NewModlanguage", &language, "Python\0C++\0\0");
2032  ImGui::NextColumn();
2033 
2034  // Template:
2035  ImGui::AlignTextToFramePadding();
2036  ImGui::TextUnformatted("Module Template");
2037  ImGui::NextColumn();
2038  helpMarker("Module Template", "Type of placeholder code that will be provided to get your module started.");
2039  ImGui::NextColumn();
2040  if (templ < 3) ImGui::Combo("##NewModtemplate", &templ, "Pro/GUI\0Legacy\0Headless\0\0");
2041  else ImGui::Combo("##NewModtemplate", &templ, "Pro/GUI\0Legacy\0Headless\0Clone\0\0");
2042  ImGui::NextColumn();
2043 
2044  // Output video mapping:
2045  int oflags = ImGuiInputTextFlags_None;
2046  if (templ != 1)
2047  {
2048  oflags |= ImGuiInputTextFlags_ReadOnly;
2049  ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
2050  ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
2051  }
2052 
2053  ImGui::AlignTextToFramePadding();
2054  ImGui::TextUnformatted("Module Output");
2055  ImGui::NextColumn();
2056  helpMarker("Module Output", "Output video format for legacy module.");
2057  ImGui::NextColumn();
2058  ImGui::SetNextItemWidth(fontw * 5.0F);
2059  ImGui::Combo("##NewModofmt", &ofmt, "YUYV\0RGB\0RGBA\0GREY\0BAYER\0\0");
2060  ImGui::SameLine();
2061  ImGui::SetNextItemWidth(fontw * 4.0F);
2062  ImGui::InputInt("##NewModow", &ow, 0, 0, ImGuiInputTextFlags_CharsDecimal | oflags);
2063  ImGui::SameLine();
2064  ImGui::Text("x");
2065  ImGui::SameLine();
2066  ImGui::SetNextItemWidth(fontw * 4.0F);
2067  ImGui::InputInt("##NewModoh", &oh, 0, 0, ImGuiInputTextFlags_CharsDecimal | oflags);
2068  ImGui::SameLine();
2069  ImGui::Text("@");
2070  ImGui::SameLine();
2071  ImGui::SetNextItemWidth(fontw * 4.0F);
2072  ImGui::InputFloat("##NewModofps", &ofps, 0.0F, 0.0F, "%.1f", ImGuiInputTextFlags_CharsDecimal | oflags);
2073  ImGui::SameLine();
2074  ImGui::Text("fps");
2075  ImGui::NextColumn();
2076 
2077  if (templ != 1)
2078  {
2079  ImGui::PopItemFlag();
2080  ImGui::PopStyleVar();
2081  }
2082 
2083  // Camera mode:
2084  ImGui::AlignTextToFramePadding();
2085  ImGui::TextUnformatted("Camera Mode");
2086  ImGui::NextColumn();
2087  helpMarker("Camera Mode", "Camera sensor configuration for your module.");
2088  ImGui::NextColumn();
2089  ImGui::SetNextItemWidth(fontw * 18.0F);
2090  ImGui::Combo("##NewModcmode", &cmode, "Dual-resolution (Crop+Scale)\0Single-resolution Crop\0"
2091  "Single-resolution Scale\0\0");
2092  ImGui::NextColumn();
2093 
2094  // Camera WDR mode:
2095  ImGui::AlignTextToFramePadding();
2096  ImGui::TextUnformatted("Camera WDR");
2097  ImGui::NextColumn();
2098  helpMarker("Camera WDR", "Camera sensor wide-dynamic-range (WDR) setting for your module. Linear is for no WDR, "
2099  "DOL is for digital overlap (merging short and long exposure frames).");
2100  ImGui::NextColumn();
2101  ImGui::Combo("##NewModwdrmode", &wdrmode, "Linear\0\0"); // FIXME
2102  //ImGui::Combo("##NewModwdrmode", &wdrmode, "Linear\0DOL\0\0");
2103  ImGui::NextColumn();
2104 
2105  // Camera video mapping:
2106  ImGui::AlignTextToFramePadding();
2107  ImGui::TextUnformatted("Camera Format");
2108  ImGui::NextColumn();
2109  helpMarker("Camera Format", "Camera video format to use for input to module and for GUI display.");
2110  ImGui::NextColumn();
2111  ImGui::SetNextItemWidth(fontw * 5.0F);
2112  ImGui::Combo("##NewModcfmt", &cfmt, "YUYV\0RGB\0RGBA\0GREY\0BAYER\0\0");
2113  ImGui::SameLine();
2114  ImGui::SetNextItemWidth(fontw * 4.0F);
2115  ImGui::InputInt("##NewModcw", &cw, 0, 0, ImGuiInputTextFlags_CharsDecimal);
2116  ImGui::SameLine();
2117  ImGui::Text("x");
2118  ImGui::SameLine();
2119  ImGui::SetNextItemWidth(fontw * 4.0F);
2120  ImGui::InputInt("##NewModch", &ch, 0, 0, ImGuiInputTextFlags_CharsDecimal);
2121  ImGui::SameLine();
2122  ImGui::Text("@");
2123  ImGui::SameLine();
2124  ImGui::SetNextItemWidth(fontw * 4.0F);
2125  ImGui::InputFloat("##NewModcfps", &cfps, 0.0F, 0.0F, "%.1f", ImGuiInputTextFlags_CharsDecimal);
2126  ImGui::SameLine();
2127  ImGui::Text("fps");
2128  ImGui::NextColumn();
2129 
2130  // Camera second frame video mapping:
2131  int c2flags = ImGuiInputTextFlags_None;
2132  if (cmode != 0)
2133  {
2134  oflags |= ImGuiInputTextFlags_ReadOnly;
2135  ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
2136  ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
2137  }
2138 
2139  ImGui::AlignTextToFramePadding();
2140  ImGui::TextUnformatted("Camera Format 2");
2141  ImGui::NextColumn();
2142  helpMarker("Camera Format 2", "Camera video format for the second stream (for processing).");
2143  ImGui::NextColumn();
2144  ImGui::SetNextItemWidth(fontw * 5.0F);
2145  ImGui::Combo("##NewModc2fmt", &c2fmt, "YUYV\0RGB\0RGBA\0GREY\0BAYER\0\0");
2146  ImGui::SameLine();
2147  ImGui::SetNextItemWidth(fontw * 4.0F);
2148  ImGui::InputInt("##NewModc2w", &c2w, 0, 0, ImGuiInputTextFlags_CharsDecimal | c2flags);
2149  ImGui::SameLine();
2150  ImGui::Text("x");
2151  ImGui::SameLine();
2152  ImGui::SetNextItemWidth(fontw * 4.0F);
2153  ImGui::InputInt("##NewModc2h", &c2h, 0, 0, ImGuiInputTextFlags_CharsDecimal | c2flags);
2154  ImGui::NextColumn();
2155 
2156  if (cmode != 0)
2157  {
2158  ImGui::PopItemFlag();
2159  ImGui::PopStyleVar();
2160  }
2161 
2162  if (templ == 3)
2163  {
2164  ImGui::PopItemFlag();
2165  ImGui::PopStyleVar();
2166  }
2167 
2168  // Adjust columns:
2169  ImGui::SetColumnWidth(0, fontw * 10.0F);
2170  ImGui::SetColumnWidth(1, ImGui::CalcTextSize("(?)").x + 30.0F);
2171  //ImGui::SetColumnWidth(2, 800.0F);
2172 
2173  ImGui::Columns(1);
2174 
2175  ImGui::Separator();
2176  ImGui::Separator();
2177 
2178  ImVec2 const button_size(ImGui::GetFontSize() * 7.0f, 0.0f);
2179  if (ImGui::Button("Cancel", button_size))
2180  {
2181  ImGui::CloseCurrentPopup();
2182  ImGui::EndPopup();
2183  ImGui::PopStyleColor();
2184  itsIdleBlocked = false;
2185  currstr = "...";
2186  return;
2187  }
2188 
2189  ImGui::SameLine(0, 540);
2190 
2191  // ====================================================================================================
2192  if (ImGui::Button("Create", button_size))
2193  {
2194  // Validate inputs:
2195  if (name.empty()) LFATAL("New Module cannot have an empty name.");
2196  if (name[0]<'A' || name[0]>'Z') LFATAL("New Module name must start with an uppercase letter.");
2197  if (vendor.empty()) LFATAL("New Module cannot have empty vendor name.");
2198  LINFO("New Module data valid...");
2199 
2200  // First create a directory for vendor, if not already present:
2201  std::filesystem::path const newvdir = JEVOIS_MODULE_PATH "/" + vendor;
2202  if (std::filesystem::exists(newvdir) == false && std::filesystem::create_directory(newvdir) == false)
2203  PLFATAL("Error creating dir " << newvdir << " -- check that you are running as root");
2204  std::filesystem::path const newmdir = newvdir / name;
2205  std::string vmstr; // machine string version of our videomapping
2206 
2207  // ----------------------------------------------------------------------------------------------------
2208  // If cloning, clone and cook:
2209  if (templ == 3)
2210  {
2211  // Remember original module's source path:
2212  std::filesystem::path srcpath = m.srcpath();
2213  std::filesystem::path cmakepath = m.cmakepath();
2214 
2215  // Replace vendor and module names in our mapping, keep all other data from source module:
2216  m.vendor = vendor;
2217  m.modulename = name;
2218 
2219  // Copy all files, so we get any icons, aux source files, calibration data, etc:
2220  std::string const copycmd = jevois::sformat("/bin/cp -ar %s/%s/%s %s", JEVOIS_MODULE_PATH, srcvendor.c_str(),
2221  srcname.c_str(), newmdir.c_str());
2222  jevois::system(copycmd);
2223  LINFO("Cloned module using: " << copycmd);
2224 
2225  // Delete source and .so files, we will replace those with cooking:
2226  unlink((m.path() + '/' + srcname + ".C").c_str());
2227  unlink((m.path() + '/' + srcname + ".py").c_str());
2228  unlink((m.path() + '/' + srcname + ".so").c_str());
2229  std::filesystem::remove_all(m.path() + "/__pycache__");
2230  std::filesystem::remove_all(m.path() + "/build");
2231 
2232  // Cook and copy the module source:
2233  std::ostringstream oss; oss << m; vmstr = oss.str();
2234  cookedCodeClone(srcpath, m.srcpath(), srcname, name);
2235 
2236  // If C++, also copy and cook CMakeLists.txt, if present, otherwise make one from template:
2237  if (language == 1)
2238  {
2239  if (std::filesystem::exists(cmakepath))
2240  cookedCmakeClone(cmakepath, m.cmakepath(), srcname, name, srcvendor, vendor, synopsis, author,
2241  email, website, license, vmstr);
2242  else
2243  cookedCopy(JEVOIS_SHARE_PATH "/templates/CMakeLists.txt", m.cmakepath(),
2244  name, vendor, synopsis, author, email, website, license, vmstr);
2245  }
2246  }
2247  // ----------------------------------------------------------------------------------------------------
2248  // If not cloning, copy and cook from template:
2249  else
2250  {
2251  // Create a new directory and copy the template into it:
2252  if (std::filesystem::exists(newmdir))
2253  LFATAL("Directory [" << newmdir << "] already exists -- Choose another name");
2254  if (std::filesystem::create_directory(newmdir) == false)
2255  PLFATAL("Error creating directory [" << newmdir << "] for new module. Maybe not running as root?");
2256  LINFO("Created new Module directory: " << newmdir);
2257 
2258  // Create a new video mapping to add to videomappimgs.cfg:
2259  m = { };
2260  switch (templ)
2261  {
2262  case 0: m.ofmt = JEVOISPRO_FMT_GUI; break;
2263  case 1: m.ofmt = get_v4l2_fmt(ofmt); m.ow = ow; m.oh = oh; m.ofps = ofps; break;
2264  case 2: m.ofmt = 0; break;
2265  default: break;
2266  }
2267 
2268  m.cfmt = get_v4l2_fmt(cfmt);
2269  m.cw = cw; m.ch = ch; m.cfps = cfps;
2270 
2271  m.vendor = vendor;
2272  m.modulename = name;
2273 
2274  switch (wdrmode)
2275  {
2276  case 0: m.wdr = jevois::WDRtype::Linear; break;
2277  case 1: m.wdr = jevois::WDRtype::DOL; break;
2278  default: break;
2279  }
2280 
2281  switch (cmode)
2282  {
2283  case 0: m.crop = jevois::CropType::CropScale;
2284  m.c2fmt = get_v4l2_fmt(c2fmt);
2285  m.c2w = c2w; m.c2h = c2h;
2286  break;
2287  case 1: m.crop = jevois::CropType::Crop; break;
2288  case 2: m.crop = jevois::CropType::Scale; break;
2289  default: break;
2290  }
2291 
2292  m.ispython = (language == 0);
2293  std::ostringstream oss; oss << m; vmstr = oss.str();
2294 
2295  // Copy the desired code template and cook it:
2296  std::string code; std::string tname;
2297  switch (language)
2298  {
2299  case 0:
2300  tname = "PyModule.py";
2301  break;
2302 
2303  case 1:
2304  {
2305  tname = "Module.C";
2306 
2307  // Also copy and cook the CMakeLists.txt
2308  std::filesystem::path srccmak = JEVOIS_SHARE_PATH "/templates/CMakeLists.txt";
2309  std::filesystem::path dstcmak = m.cmakepath();
2310  cookedCopy(srccmak, dstcmak, name, vendor, synopsis, author, email, website, license, vmstr);
2311  }
2312  break;
2313 
2314  default: LFATAL("Invalid language " << language);
2315  }
2316 
2317  std::filesystem::path srcmod = JEVOIS_SHARE_PATH "/templates/" + tname;
2318  std::filesystem::path dstmod = m.srcpath();
2319  cookedCopy(srcmod, dstmod, name, vendor, synopsis, author, email, website, license, vmstr);
2320  }
2321 
2322  // Add the video mapping:
2323  jevois::system(JEVOIS "-add-videomapping " + vmstr);
2324  LINFO("Added videomapping: " << vmstr);
2325 
2326  // Remember this new mapping in case we need it to compile and load the module later:
2327  itsNewMapping = m;
2328 
2329  // For python modules, load right now. For C++ modules, we will not find the mapping because the .so is missing,
2330  // we need to compile the module first:
2331  if (language == 0) runNewModule();
2332  else itsCompileState = CompilationState::Start;
2333 
2334  // Clear a few things before the next module:
2335  name.clear(); vendor.clear(); synopsis.clear(); currstr = "...";
2336 
2337  ImGui::CloseCurrentPopup();
2338  itsIdleBlocked = false;
2339  }
2340  }
2341  catch (...) { reportAndIgnoreException(); }
2342 
2343  // Make sure we always end the popup, even if we had an exception:
2344  ImGui::EndPopup();
2345  }
2346 
2347  ImGui::PopStyleColor();
2348 }
2349 
2350 // ##############################################################################################################
2352 {
2353  engine()->reloadVideoMappings();
2354  size_t idx = 0; size_t foundidx = 12345678;
2355  engine()->foreachVideoMapping([&](VideoMapping const & mm)
2356  { if (itsNewMapping.isSameAs(mm)) foundidx = idx; ++idx; });
2357 
2358  if (foundidx != 12345678) engine()->requestSetFormat(foundidx);
2359  else LFATAL("Internal error, could not find the module we just created -- CHECK LOGS");
2360 
2361  itsRefreshVideoMappings = true; // Force a refresh of our list of video mappings
2362  /////itsRefreshCfgList = true; // Force a refresh of videomappings.cfg in the config editor
2363 
2364  // Switch to the mapping list that contains our new module:
2365  if (itsNewMapping.ofmt == JEVOISPRO_FMT_GUI) itsVideoMappingListType = 0;
2366  else if (itsNewMapping.ofmt != 0 && itsNewMapping.ofmt != JEVOISPRO_FMT_GUI) itsVideoMappingListType = 1;
2367  else if (itsNewMapping.ofmt == 0) itsVideoMappingListType = 2;
2368  else LERROR("Internal error: cannot determine video mapping list type -- IGNORED");
2369 }
2370 
2371 // ##############################################################################################################
2372 // imgui opengl demo in 1024 bytes:
2373 using V=ImVec2;using F=float;int h;struct E{V p;F z,w;bool operator<(E&o){return z<o.z;}};E G[999];
2374 #define L(i,x,y,z)for(F i=x;i<y;i+=z)
2375 #define Q(y)sin((y+t)*.03)*(1-sin(t*3)*cos(y/99+t))*9
2376 #define H(p,w)L(k,0,5,1)d->AddCircleFilled(p+V(1,-1)*w*k/8,w*(1-k/5),k<4?0xff000000+k*0x554400:-1);
2377 #define J(b)L(i,0,h,1){E&o=G[int(i)];if(b*o.z>0)H(o.p,(o.z*.3+4)*o.w)}
2378 #define O(b)i=t+1.6+j/99;if(b*sin(i)>0)H(c+v*j+u*(cos(i)*40+Q(j)),30)
2379 void FX(ImDrawList*d,V a,V b,V,ImVec4,F t){F i=sin(t)-.7;V u(cos(i),sin(i)),v(-sin(i),cos(i)),c=(a+b)/2;F l=300;
2380 F w=0;L(z,4,20,1){w+=z;L(yy,-l,l,z*2){F y=yy+fmod(t*z*10,z*2);L(i,-1,2,2)d->AddCircle(c+v*y+u*i*(w+sin((y+t)/25)*w/30),z,0xff000000+0x110e00*int(z*z*z/384),12,z/2);}}
2381 h=0;L(y,-l,l,15)L(b,0,16,1){i=t+b*.2+y/99;G[h++]={c+v*y+u*(cos(i)*60+Q(y)),sinf(i),(b<1||b>14)?2.f:1.5f};}std::sort(G,G+h);
2382  F j=(-2+fmod(t*3,5))*99;J(-1)O(-1)a=c+v*-l;L(y,-l,l,15){b=c+v*y+u*((15-rand())&31)*.1;L(k,0,9,1)d->AddLine(a-v,b,k<8?0x11222200*k:-1,(9-k)*4);a=b;}O(1)J(1)}
2383 
2384 // ##############################################################################################################
2386 {
2387  if (ImGui::Button("Open Style Editor")) itsShowStyleEditor = true;
2388  ImGui::SameLine();
2389  if (ImGui::Button("Open App Metrics")) itsShowAppMetrics = true;
2390  ImGui::SameLine();
2391  if (ImGui::Button("Open ImGui Demo")) itsShowImGuiDemo = true;
2392  ImGui::Separator();
2393 
2394 
2395  float camz = pixel_perfect_z;
2396  float yaw = 0.0f;
2397  float pitch = 0.0f;
2398  float roll = 0.0f;
2399  static float fudgex = 0.375f;
2400  static float fudgey = 0.375f;
2401  static float fudgez = 0.0f;
2402  unsigned short winw, winh; itsBackend.getWindowSize(winw, winh);
2403 
2404  ImGui::SliderFloat("OpenGL Camera z", &camz, -2.0f * winh, -1.0f);
2405  ImGui::SliderAngle("OpenGL yaw", &yaw, -179.0f, 180.0f);
2406  ImGui::SliderAngle("OpenGL pitch", &pitch, -179.0f, 180.0f);
2407  ImGui::SliderAngle("OpenGL roll", &roll, -179.0f, 180.0f);
2408 
2409  ImGui::SliderFloat("fudge x", &fudgex, -1.0f, 1.0f);
2410  ImGui::SliderFloat("fudge y", &fudgey, -1.0f, 1.0f);
2411  ImGui::SliderFloat("fudge z", &fudgez, -1.0f, 1.0f);
2412 
2413  // mess with the projection matrix:
2414  proj = glm::perspective(glm::radians(45.0f), float(winw) / float(winh), 1.0f, winh * 2.0f);
2415  proj = glm::translate(proj, glm::vec3(fudgex, fudgey, fudgez));
2416 
2417  // update our view matrix
2418  view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, camz));
2419  view *= glm::yawPitchRoll(yaw, pitch, roll);
2420 
2421  // Note: the ortho projection of ImGui is like: glm::ortho(0.0f, 1920.0f, 1080.0f, 0.0f);
2422  /*
2423  glm::mat4 gogo = glm::ortho(0.0f, 1920.0f, 1080.0f, 0.0f);
2424  gogo = glm::translate(gogo, glm::vec3(0.125f, 0.125f, 0.0f));
2425 
2426  for (int i = 0; i < 4; ++i)
2427  for (int j = 0; j < 4; ++j)
2428  printf("gogo[%d][%d] = %f\n", i, j, gogo[i][j]);
2429  */
2430 
2431  //static bool demo = false;
2432  //if (ImGui::Checkbox("OpenGL Demo", &demo))
2433  //{
2434  // from https://github.com/ocornut/imgui/issues/3606
2435  ImGuiIO& io = ImGui::GetIO();
2436  ImGui::Begin("FX", NULL, ImGuiWindowFlags_AlwaysAutoResize);
2437  ImVec2 size(320.0f, 180.0f);
2438  ImGui::InvisibleButton("canvas", size);
2439  ImVec2 p0 = ImGui::GetItemRectMin();
2440  ImVec2 p1 = ImGui::GetItemRectMax();
2441  ImDrawList* draw_list = ImGui::GetWindowDrawList();
2442  draw_list->PushClipRect(p0, p1);
2443 
2444  ImVec4 mouse_data;
2445  mouse_data.x = (io.MousePos.x - p0.x) / size.x;
2446  mouse_data.y = (io.MousePos.y - p0.y) / size.y;
2447  mouse_data.z = io.MouseDownDuration[0];
2448  mouse_data.w = io.MouseDownDuration[1];
2449 
2450  FX(draw_list, p0, p1, size, mouse_data, (float)ImGui::GetTime());
2451  draw_list->PopClipRect();
2452  ImGui::End();
2453  //}
2454 
2455 }
2456 
2457 // ##############################################################################################################
2458 void jevois::GUIhelper::reportError(std::string const & err)
2459 {
2460  auto now = std::chrono::steady_clock::now();
2461 
2462  std::lock_guard<std::mutex> _(itsErrorMtx);
2463 
2464  // Did we already report this error? If so, just update the last received time:
2465  for (auto & e : itsErrors) if (e.err == err) { e.lasttime = now; return; }
2466 
2467  // Too many errors already?
2468  if (itsErrors.size() > 10) return;
2469  else if (itsErrors.size() == 10)
2470  {
2471  ErrorData d { "Too many errors -- TRUNCATING", now, now };
2472  itsErrors.emplace(itsErrors.end(), std::move(d));
2473  return;
2474  }
2475 
2476  // It's a new error, push a new entry into our list:
2477  ErrorData d { err, now, now };
2478  itsErrors.emplace(itsErrors.end(), std::move(d));
2479 
2480  // drawErrorPopup() will clear old entries
2481 }
2482 
2483 // ##############################################################################################################
2485 {
2486  std::lock_guard<std::mutex> _(itsErrorMtx);
2487  itsErrors.clear();
2488 }
2489 
2490 // ##############################################################################################################
2491 void jevois::GUIhelper::reportAndIgnoreException(std::string const & prefix)
2492 {
2493  if (prefix.empty())
2494  {
2495  try { throw; }
2496  catch (std::exception const & e)
2497  { reportError(e.what()); }
2498  catch (boost::python::error_already_set & e)
2499  { reportError("Python error:\n"+jevois::getPythonExceptionString(e)); }
2500  catch (...)
2501  { reportError("Unknown error"); }
2502  }
2503  else
2504  {
2505  try { throw; }
2506  catch (std::exception const & e)
2507  { reportError(prefix + ": " + e.what()); }
2508  catch (boost::python::error_already_set & e)
2509  { reportError(prefix + ": Python error:\n"+jevois::getPythonExceptionString(e)); }
2510  catch (...)
2511  { reportError(prefix + ": Unknown error"); }
2512  }
2513 }
2514 
2515 // ##############################################################################################################
2516 void jevois::GUIhelper::reportAndRethrowException(std::string const & prefix)
2517 {
2518  reportAndIgnoreException(prefix);
2519  throw;
2520 }
2521 
2522 // ##############################################################################################################
2524 {
2525  std::lock_guard<std::mutex> _(itsErrorMtx);
2526  if (itsErrors.empty()) return;
2527 
2528  ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
2529  ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNav;
2530 
2531  ImGui::SetNextWindowPos(ImVec2(10.0f, 10.0f), ImGuiCond_Always);
2532  ImGui::PushStyleColor(ImGuiCol_WindowBg, 0xc0e0e0ff);
2533 
2534  static bool show = true;
2535 
2536  if (ImGui::Begin("Error detected!", &show, window_flags))
2537  {
2538  ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
2539  ImGui::Text("Error detected!");
2540 
2541  auto itr = itsErrors.begin();
2542  while (itr != itsErrors.end())
2543  {
2544  // Clear the error after a while, unless mouse cursor is on it:
2545  std::chrono::duration<float> d = std::chrono::steady_clock::now() - itr->lasttime;
2546  std::chrono::duration<float> d2 = std::chrono::steady_clock::now() - itr->firsttime;
2547  if (d.count() >= 1.0f && d2.count() >= 10.0f && ImGui::IsWindowHovered() == false)
2548  itr = itsErrors.erase(itr);
2549  else
2550  {
2551  // Show this error:
2552  ImGui::Separator();
2553  ImGui::TextUnformatted(itr->err.c_str());
2554  ++itr;
2555  }
2556  }
2557  ImGui::PopTextWrapPos();
2558 
2559  ImGui::End();
2560  }
2561  ImGui::PopStyleColor();
2562 }
2563 
2564 // ##############################################################################################################
2565 void jevois::GUIhelper::helpMarker(char const * msg, char const * msg2, char const * msg3)
2566 {
2567  //ImGui::TextDisabled("(?)");
2568  ImGui::Text("(?)");
2569  if (ImGui::IsItemHovered())
2570  {
2571  ImGui::BeginTooltip();
2572  ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
2573  ImGui::TextUnformatted(msg);
2574  if (msg2) { ImGui::Separator(); ImGui::TextUnformatted(msg2); }
2575  if (msg3) { ImGui::Separator(); ImGui::TextUnformatted(msg3); }
2576  ImGui::PopTextWrapPos();
2577  ImGui::EndTooltip();
2578  }
2579 }
2580 
2581 // ##############################################################################################################
2582 bool jevois::GUIhelper::toggleButton(char const * name, bool * val)
2583 {
2584  bool changed = false;
2585  if (*val)
2586  {
2587  ImGui::PushID(name);
2588  ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(4.0f/7.0f, 1.0f, 1.0f));
2589  ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(4.0f/7.0f, 1.0f, 1.0f));
2590  ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(4.0f/7.0f, 0.5f, 0.5f));
2591  if (ImGui::Button(name)) { *val = false; changed = true; }
2592  ImGui::PopStyleColor(3);
2593  ImGui::PopID();
2594  }
2595  else if (ImGui::Button(name)) { *val = true; changed = true; }
2596 
2597  return changed;
2598 }
2599 
2600 // ##############################################################################################################
2602 {
2603  unsigned short winw, winh;
2604  startFrame(winw, winh);
2605 
2606  if (itsHeadless.loaded() == false)
2607  try { itsHeadless.load(JEVOIS_SHARE_PATH "/icons/headless.png"); }
2608  catch (...)
2609  {
2611  cv::Mat blank(winh, winw, CV_8UC4, 0);
2612  itsHeadless.load(blank);
2613  }
2614 
2615  if (itsHeadless.loaded()) itsHeadless.draw(ImVec2(0, 0), ImVec2(winw, winh), ImGui::GetBackgroundDrawList());
2616 
2617  endFrame();
2618 }
2619 
2620 // ##############################################################################################################
2621 void jevois::GUIhelper::highlightText(std::string const & str)
2622 {
2623  ImGui::PushStyleColor(ImGuiCol_Text, 0xff0000ff);
2624  ImGui::TextUnformatted(str.c_str());
2625  ImGui::PopStyleColor();
2626 }
2627 
2628 // ##############################################################################################################
2629 void jevois::GUIhelper::demoBanner(std::string const & title, std::string const & msg)
2630 {
2631  // Here we just update our internals, we will do the drawing in the main loop:
2632  itsBannerTitle = title;
2633  itsBannerMsg = msg;
2634 }
2635 
2636 // ##############################################################################################################
2638 {
2639  if (std::filesystem::exists(itsNewMapping.srcpath()) && std::filesystem::exists(itsNewMapping.cmakepath()))
2640  itsCompileState = CompilationState::Start;
2641  else
2642  reportError("Cannot find " + itsNewMapping.srcpath() + " or " + itsNewMapping.cmakepath() + " -- IGNORED");
2643 }
2644 
2645 // ##############################################################################################################
2647 {
2648  ImGui::PushStyleColor(ImGuiCol_WindowBg, 0xf0e0ffe0);
2649  // Set window size applied only on first use ever, otherwise from imgui.ini:
2650  ImGui::SetNextWindowSize(ImVec2(800, 680), ImGuiCond_FirstUseEver);
2651 
2652  // Open the window, allow closing when we are in error:
2653  int constexpr flags = ImGuiWindowFlags_HorizontalScrollbar;
2654  bool keep_window_open = true;
2655  if (itsCompileState == CompilationState::Error) ImGui::Begin("C++ Module Compilation", &keep_window_open, flags);
2656  else ImGui::Begin("C++ Module Compilation", nullptr, flags);
2657 
2658  ImGui::TextUnformatted(("Compiling " + itsNewMapping.srcpath()).c_str());
2659  ImGui::TextUnformatted(("Using " + itsNewMapping.cmakepath()).c_str());
2660  ImGui::Separator();
2661  std::string const modpath = itsNewMapping.path();
2662  std::string const buildpath = modpath + "/build";
2663 
2664  try
2665  {
2666  switch (itsCompileState)
2667  {
2668  // ----------------------------------------------------------------------------------------------------
2669  case CompilationState::Start:
2670  LINFO("Start compiling " << itsNewMapping.srcpath());
2671  itsIdleBlocked = true;
2672  for (std::string & s : itsCompileMessages) s.clear();
2673  std::filesystem::remove_all(buildpath);
2674  itsCompileState = CompilationState::Cmake;
2675  break;
2676 
2677  // ----------------------------------------------------------------------------------------------------
2678  case CompilationState::Cmake:
2679  if (compileCommand("cmake -S " + modpath + " -B " + buildpath +
2680  " -DJEVOIS_HARDWARE=PRO"
2681 #ifdef JEVOIS_PLATFORM
2682  " -DJEVOIS_PLATFORM=ON -DJEVOIS_NATIVE=ON"
2683 #endif
2684  , itsCompileMessages[0]))
2685  itsCompileState = CompilationState::Make;
2686  break;
2687 
2688  // ----------------------------------------------------------------------------------------------------
2689  case CompilationState::Make:
2690  if (compileCommand("JEVOIS_SRC_ROOT=none cmake --build " + buildpath, itsCompileMessages[1]))
2691  itsCompileState = CompilationState::Install;
2692  break;
2693 
2694  // ----------------------------------------------------------------------------------------------------
2695  case CompilationState::Install:
2696  if (compileCommand("cmake --install " + buildpath, itsCompileMessages[2]))
2697  itsCompileState = CompilationState::CPack;
2698  break;
2699 
2700  // ----------------------------------------------------------------------------------------------------
2701  case CompilationState::CPack:
2702  if (compileCommand("cd " + buildpath + " && cpack && mkdir -p /jevoispro/debs && /bin/mv *.deb /jevoispro/debs/",
2703  itsCompileMessages[3]))
2704  itsCompileState = CompilationState::Success;
2705  break;
2706 
2707  // ----------------------------------------------------------------------------------------------------
2708  case CompilationState::Success:
2709  {
2710  ImGui::TextUnformatted("Compilation success!");
2711  ImGui::TextUnformatted("See below for details...");
2712  ImGui::Separator();
2713  static int run_default = 0;
2714  int ret = modal("Success! Run the module now?", "Compilation success!\n\nA deb package of your module is in "
2715  "/jevoispro/debs/\nif you want to share it.\n\nLoad and run the new module now?",
2716  &run_default, "Yes", "No");
2717  switch (ret)
2718  {
2719  case 1:
2720  runNewModule();
2721  itsIdleBlocked = false;
2722  itsCompileState = CompilationState::Idle;
2723  break;
2724 
2725  case 2:
2726  // make sure we have the mapping for the new module...
2727  itsIdleBlocked = false;
2728  itsCompileState = CompilationState::Idle;
2729  engine()->reloadVideoMappings();
2730  itsRefreshVideoMappings = true;
2731  break;
2732 
2733  default: break; // Need to wait
2734  }
2735  }
2736  break;
2737 
2738  case CompilationState::Error:
2739  {
2740  itsIdleBlocked = false;
2741  highlightText("Compilation failed!");
2742  ImGui::TextUnformatted("You may need to edit the two files above.");
2743  ImGui::TextUnformatted("See below for details...");
2744  ImGui::Separator();
2745  static bool show_modal = false;
2746 
2747  ImGui::AlignTextToFramePadding();
2748  if (ImGui::Button("Edit CMakeLists.txt"))
2749  { itsCodeEditor->loadFile(itsNewMapping.cmakepath()); show_modal = true; }
2750 
2751  ImGui::SameLine(); ImGui::Text(" "); ImGui::SameLine();
2752  if (ImGui::Button(("Edit " + itsNewMapping.modulename + ".C").c_str()))
2753  { itsCodeEditor->loadFile(itsNewMapping.srcpath()); show_modal = true; }
2754 
2755  ImGui::SameLine(); ImGui::Text(" "); ImGui::SameLine();
2756  if (ImGui::Button("Compile again")) itsCompileState = CompilationState::Start;
2757 
2758  ImGui::Separator();
2759 
2760  if (show_modal)
2761  {
2762  static int doit_default = 0;
2763  int ret = modal("Ready to edit", "File will now be loaded in the Code tab of the main window and "
2764  "ready to edit. Please switch to the Code tab in the main window.\n\n"
2765  "When you save it, we will try to compile it again.",
2766  &doit_default, "Close Compilation Window", "Keep Open");
2767  switch (ret)
2768  {
2769  case 1: show_modal = false; itsCompileState = CompilationState::Idle; break;
2770  case 2: show_modal = false; break; // user want to keep this window open
2771  default: break; // Need to wait
2772  }
2773  }
2774  }
2775  break;
2776 
2777  // ----------------------------------------------------------------------------------------------------
2778  default:
2779  LERROR("Internal error: invalid compilation state " << int(itsCompileState) << " -- RESET TO IDLE");
2780  itsCompileState = CompilationState::Idle;
2781  itsIdleBlocked = false;
2782  }
2783  } catch (...) { reportAndIgnoreException(); }
2784 
2785  // Display the messages:
2786  for (std::string & s : itsCompileMessages)
2787  if (s.empty() == false)
2788  {
2789  ImGui::TextUnformatted(s.c_str());
2790  ImGui::Separator();
2791  }
2792 
2793  ImGui::End();
2794  ImGui::PopStyleColor();
2795 
2796  // Switch to idle if we were in error and user closed the window:
2797  if (keep_window_open == false) { itsIdleBlocked = false; itsCompileState = CompilationState::Idle; }
2798 }
2799 
2800 
2801 // ##############################################################################################################
2802 bool jevois::GUIhelper::compileCommand(std::string const & cmd, std::string & msg)
2803 {
2804  if (itsCompileFut.valid() == false)
2805  {
2806  itsCompileFut = jevois::async([&](std::string cmd) { return jevois::system(cmd, true); }, cmd);
2807  msg = "Running: " + cmd + "\nPlease wait ...\n";
2808  LINFO("Running: " + cmd);
2809  }
2810  else if (itsCompileFut.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready)
2811  try
2812  {
2813  msg += itsCompileFut.get() + "\nSuccess!";
2814  LINFO("Success running: " << cmd);
2815  return true;
2816  }
2817  catch (...)
2818  {
2819  LERROR("Failed running: " << cmd);
2820  itsCompileState = CompilationState::Error;
2822  }
2823 
2824  return false;
2825 }
2826 
2827 #endif // JEVOIS_PRO
jevois::GUIhelper::ErrorData
Definition: GUIhelper.H:405
E
Definition: GUIhelper.C:2373
jevois::module::SerStyle
SerStyle
Definition: Module.H:197
JEVOIS_WAIT_GET_FUTURE
#define JEVOIS_WAIT_GET_FUTURE(f)
Wait for a future to become ready for 5 seconds, get(), warn and ignore exception,...
Definition: Log.H:336
JEVOISPRO_FMT_GUI
#define JEVOISPRO_FMT_GUI
JeVois-Pro zero-copy display of camera input frame (to be used as output mode in VideoMapping)
Definition: Utils.H:31
jevois::GUIhelper::itsCodeEditor
std::shared_ptr< GUIeditor > itsCodeEditor
Definition: GUIhelper.H:470
jevois::GUIhelper::drawImage
void drawImage(char const *name, RawImage const &img, int &x, int &y, unsigned short &w, unsigned short &h, bool noalias=false, bool isoverlay=false)
Draw a RawImage, copying pixel data to an OpenGL texture.
Definition: GUIhelper.C:288
jevois::getFanSpeed
int getFanSpeed()
Get fan speed in percent, only meaningful on JeVois-Pro Platform, all others return 0.
Definition: SysInfo.C:153
jevois::imu::get
Data collection mode RAW means that the latest available raw data is returned each time get() is called
jevois::GUIhelper::highlightText
void highlightText(std::string const &str)
Like ImGui::Textunformatted but in a highlight color (typically, red)
Definition: GUIhelper.C:2621
jevois::GUIhelper::drawInputFrame2
void drawInputFrame2(char const *name, InputFrame const &frame, int &x, int &y, unsigned short &w, unsigned short &h, bool noalias=false, bool casync=false)
Draw the second (scaled) input video frame from the camera using zero-copy.
Definition: GUIhelper.C:365
jevois::GUIhelper::drawNewModuleForm
void drawNewModuleForm()
Definition: GUIhelper.C:1913
jevois::paramStringToVal
void paramStringToVal(std::string const &valstring, T &result)
Machine-readable conversion from string to T, for use in jevois::Parameter.
jevois::GUIhelper::idle
bool idle() const
Check for idle in case startFrame() was called elsewhere.
Definition: GUIhelper.C:227
jevois::GUIhelper::reportAndIgnoreException
void reportAndIgnoreException(std::string const &prefix="")
Report current exception in a modal dialog, then ignore it.
Definition: GUIhelper.C:2491
jevois::Range
A generic range class.
Definition: Range.H:80
jevois::GUIhelper::drawMenuBar
void drawMenuBar()
Definition: GUIhelper.C:839
jevois::GUIhelper::demoBanner
void demoBanner(std::string const &title="", std::string const &msg="")
Display some text in a big banner, used by demo mode.
Definition: GUIhelper.C:2629
jevois::GUIhelper::drawCircle
void drawCircle(float x, float y, float r, ImU32 col=IM_COL32(128, 255, 128, 255), bool filled=true)
Draw circle over an image.
Definition: GUIhelper.C:528
jevois::async
std::future< std::invoke_result_t< std::decay_t< Function >, std::decay_t< Args >... > > async(Function &&f, Args &&... args)
Async execution using a thread pool.
Module.H
jevois::GUIhelper::startFrame
bool startFrame(unsigned short &w, unsigned short &h)
Start a new rendering frame.
Definition: GUIhelper.C:169
jevois::GUIhelper::modal
int modal(std::string const &title, char const *text, int *default_val=nullptr, char const *b1txt="Ok", char const *b2txt="Cancel")
Helper to draw a modal with 2 choices.
Definition: GUIhelper.C:1530
jevois::GUIhelper::compileCommand
bool compileCommand(std::string const &cmd, std::string &msg)
Definition: GUIhelper.C:2802
GPUimage.H
jevois::GUIhelper::runNewModule
void runNewModule()
Definition: GUIhelper.C:2351
jevois::GUIhelper::itext
void itext(char const *txt, ImU32 const &col=IM_COL32_BLACK_TRANS, int line=-1)
Draw some overlay text on top of an image.
Definition: GUIhelper.C:571
jevois::GUIhelper::onParamChange
void onParamChange(gui::scale const &param, float const &newval) override
jevois::Range::min
const T & min() const
Return the minimum value.
jevois::GUIhelper::drawInfo
void drawInfo()
Definition: GUIhelper.C:975
jevois::sformat
std::string sformat(char const *fmt,...) __attribute__((format(__printf__
Create a string using printf style arguments.
Definition: Utils.C:439
jevois::split
std::vector< std::string > split(std::string const &input, std::string const &regex="\\s+")
Split string into vector of tokens using a regex to specify what to split on; default regex splits by...
Definition: Utils.C:270
jevois::GUIhelper::setparstr
void setparstr(std::string const &descriptor, std::string const &val)
Definition: GUIhelper.C:1149
jevois::GPUimage
Class to hold a GPUtexture, GPUprogram, and other data associated with rendering an image in OpenGL.
Definition: GPUimage.H:38
jevois::getSysInfoMem
std::string getSysInfoMem()
Get memory info.
Definition: SysInfo.C:66
jevois::GUIhelper::headlessDisplay
void headlessDisplay()
Show a message that we are running headless.
Definition: GUIhelper.C:2601
jevois::GUIhelper::drawPolyInternal
void drawPolyInternal(ImVec2 const *pts, size_t npts, ImU32 col, bool filled)
Definition: GUIhelper.C:491
jevois::GUIhelper::endFrame
void endFrame()
Finish current frame and render it.
Definition: GUIhelper.C:693
jevois::Component
A component of a model hierarchy.
Definition: Component.H:181
jevois::GUIhelper::reportAndRethrowException
void reportAndRethrowException(std::string const &prefix="")
Report current exception in a modal dialog, then re-throw it.
Definition: GUIhelper.C:2516
jevois::GUIhelper::releaseImage
void releaseImage(char const *name)
Release an image.
Definition: GUIhelper.C:605
jevois::GUIhelper::drawTweaks
void drawTweaks()
Definition: GUIhelper.C:2385
jevois::GUIhelper::newModEntry
void newModEntry(char const *wname, std::string &str, char const *desc, char const *hint, char const *hlp)
Definition: GUIhelper.C:1583
jevois::GUIhelper::clearErrors
void clearErrors()
Clear all errors currently displayed in the JeVois-Pro GUI.
Definition: GUIhelper.C:2484
jevois::RawImage
A raw image as coming from a V4L2 Camera and/or being sent out to a USB Gadget.
Definition: RawImage.H:110
jevois::GUIhelper::d2i
ImVec2 d2i(ImVec2 p, char const *name=nullptr)
Convert coordinates of a point from on-screen to within a rendered image.
Definition: GUIhelper.C:619
jevois::GUIhelper::helpMarker
void helpMarker(char const *msg, char const *msg2=nullptr, char const *msg3=nullptr)
Display a (?) label and show tooltip when it is hovered.
Definition: GUIhelper.C:2565
jevois::Component::foreachParam
void foreachParam(std::function< void(std::string const &compname, ParameterBase *p)> func, std::string const &cname="")
Run a function on every param we hold.
Definition: Component.C:584
jevois::GUIhelper::i2d
ImVec2 i2d(ImVec2 p, char const *name=nullptr)
Convert coordinates of a point from within a rendered image to on-screen.
Definition: GUIhelper.C:401
jevois::ParameterBase
Base class for Parameter.
Definition: Parameter.H:121
jevois::GUIhelper::drawErrorPopup
void drawErrorPopup()
Definition: GUIhelper.C:2523
jevois::GUIhelper::drawModuleSelect
void drawModuleSelect()
Definition: GUIhelper.C:895
o
#define o
Definition: Font10x20.C:6
jevois::RawImage::bufindex
size_t bufindex
The index of the data buffer in the kernel driver.
Definition: RawImage.H:150
jevois::GUIhelper::drawParameters
void drawParameters()
Definition: GUIhelper.C:1157
jevois::ParameterSummary::category
std::string category
Category of the parameter, as a string.
Definition: Parameter.H:108
LERROR
#define LERROR(msg)
Convenience macro for users to print out console or syslog messages, ERROR level.
Definition: Log.H:211
jevois::GUIhelper::drawRect
void drawRect(float x1, float y1, float x2, float y2, ImU32 col=IM_COL32(128, 255, 128, 255), bool filled=true)
Draw rectangular box over an image.
Definition: GUIhelper.C:479
jevois::GUIhelper::drawCamCtrls
void drawCamCtrls()
Definition: GUIhelper.C:1524
jevois::getFileString
std::string getFileString(char const *fname, int skip=0)
Read one line from a file and return it as a string.
Definition: Utils.C:541
jevois::InputFrame::get
const RawImage & get(bool casync=false) const
Get the next captured camera image.
Definition: InputFrame.C:50
jevois::getSysInfoVersion
std::string getSysInfoVersion()
Get O.S. version info.
Definition: SysInfo.C:74
jevois::RawImage::width
unsigned int width
Image width in pixels.
Definition: RawImage.H:145
Q
#define Q(y)
Definition: GUIhelper.C:2375
jevois::GUIhelper::drawLine
void drawLine(float x1, float y1, float x2, float y2, ImU32 col=IM_COL32(128, 255, 128, 255))
Draw line over an image.
Definition: GUIhelper.C:473
jevois::GUIhelper::seroutEnabled
bool seroutEnabled() const
Tell whether user enabled serout messages to GUI console.
Definition: GUIhelper.C:1520
jevois::GUIeditor
Editor panel for JeVois-Pro GUI.
Definition: GUIeditor.H:46
PLFATAL
#define PLFATAL(msg)
Like LDEBUG but appends errno and strerror(errno), to be used when some system call fails.
Definition: Log.H:239
jevois::GUIhelper::reportError
void reportError(std::string const &err)
Report an error in an overlay window.
Definition: GUIhelper.C:2458
jevois
Definition: Concepts.dox:1
jevois::getNumInstalledNPUs
size_t getNumInstalledNPUs()
Get the number of JeVois-Pro NPUs present on this system.
Definition: SysInfo.C:127
F
float F
Definition: GUIhelper.C:2373
jevois::tolower
std::string tolower(std::string const &str)
Convert string to lowercase.
Definition: Utils.C:377
jevois::getSysInfoCPU
std::string getSysInfoCPU()
Get CPU info: frequency, thermal, load.
Definition: SysInfo.C:24
jevois::GUIhelper::drawPoly
void drawPoly(std::vector< cv::Point > const &pts, ImU32 col=IM_COL32(128, 255, 128, 255), bool filled=true)
Draw polygon over an image.
Definition: GUIhelper.C:506
jevois::Component::getParamValUnique
T getParamValUnique(std::string const &paramdescriptor) const
Get a parameter value, simple version assuming only one parameter match.
jevois::system
std::string system(std::string const &cmd, bool errtoo=true)
Execute a command and grab stdout output to a string.
Definition: Utils.C:461
jevois::InputFrame::hasScaledImage
bool hasScaledImage() const
Check whether a second input image scaled by the JeVoisPro Platform ISP is available.
Definition: InputFrame.C:61
GUIconsole.H
J
#define J(b)
Definition: GUIhelper.C:2377
SysInfo.H
jevois::replaceStringAll
size_t replaceStringAll(std::string &str, std::string const &from, std::string const &to)
Replace all instances of 'from' with 'to'.
Definition: Utils.C:344
jevois::GUIhelper::toggleButton
bool toggleButton(char const *name, bool *val)
Helper to draw a toggle button.
Definition: GUIhelper.C:2582
jevois::GUIhelper::reset
void reset(bool modulechanged=true)
Reset to default state, typically called on Module or video format change.
Definition: GUIhelper.C:114
jevois::GUIhelper::frameStarted
bool frameStarted() const
Helper to indicate that startFrame() was called, and thus endFrame() should be called.
Definition: GUIhelper.C:284
jevois::join
std::string join(std::vector< std::string > const &strings, std::string const &delimiter)
Concatenate a vector of tokens into a string.
Definition: Utils.C:280
jevois::getNumInstalledTPUs
size_t getNumInstalledTPUs()
Get the number of Coral TPUs present on this system.
Definition: SysInfo.C:87
jevois::Component::setParamValUnique
void setParamValUnique(std::string const &paramdescriptor, T const &val)
Set a parameter value, simple version assuming only one parameter match.
jevois::extractString
std::string extractString(std::string const &str, std::string const &startsep, std::string const &endsep)
Extract a portion of a string between two delimiters.
Definition: Utils.C:316
jevois::fccstr
std::string fccstr(unsigned int fcc)
Convert a V4L2 four-cc code (V4L2_PIX_FMT_...) to a 4-char string.
Definition: Utils.C:45
jevois::GUIhelper::drawJeVoisGUI
void drawJeVoisGUI()
Definition: GUIhelper.C:758
jevois::GPUimage::i2ds
ImVec2 i2ds(ImVec2 const &p)
Convert a 2D size from within a rendered image to on-screen.
Definition: GPUimage.C:393
jevois::InputFrame::get2
const RawImage & get2(bool casync=false) const
Get the next captured camera image, ISP-scaled second frame.
Definition: InputFrame.C:67
jevois::Engine
JeVois processing engine - gets images from camera sensor, processes them, and sends results over USB...
Definition: Engine.H:387
jevois::GUIhelper::serlogEnabled
bool serlogEnabled() const
Tell whether user enabled serlog messages to GUI console.
Definition: GUIhelper.C:1516
jevois::Manager::getComponent
std::shared_ptr< Comp > getComponent(std::string const &instanceName) const
Get a top-level component by instance name.
jevois::Crop
() Scale() Crop(CropScale)) struct VideoMapping
Simple struct to hold video mapping definitions for the processing Engine.
Definition: VideoMapping.H:43
jevois::GUIhelper::releaseImage2
void releaseImage2(char const *name)
Release an image, second video stream.
Definition: GUIhelper.C:612
jevois::warnAndIgnoreException
std::string warnAndIgnoreException(std::string const &prefix="")
Convenience function to catch an exception, issue some LERROR (depending on type),...
Definition: Log.C:236
jevois::GUIhelper::drawSystem
void drawSystem()
Definition: GUIhelper.C:1598
L
#define L(i, x, y, z)
Definition: GUIhelper.C:2374
LFATAL
#define LFATAL(msg)
Convenience macro for users to print out console or syslog messages, FATAL level.
jevois::getNumInstalledSPUs
size_t getNumInstalledSPUs()
Get the number of Hailo8 SPUs present on this system.
Definition: SysInfo.C:138
jevois::GUIhelper::drawText
void drawText(float x, float y, char const *txt, ImU32 col=IM_COL32(128, 255, 128, 255))
Draw text over an image.
Definition: GUIhelper.C:541
jevois::GUIhelper::GUIhelper
GUIhelper(std::string const &instance, bool conslock=false)
Constructor.
Definition: GUIhelper.C:43
jevois::GUIconsole
Simple console with coloring and completion.
Definition: GUIconsole.H:31
jevois::stringStartsWith
bool stringStartsWith(std::string const &str, std::string const &prefix)
Return true if str starts with prefix (including if both strings are equal)
Definition: Utils.C:294
jevois::ParameterSummary
ParameterSummary provides a summary about a parameter.
Definition: Parameter.H:83
jevois::GUIhelper::drawInputFrame
void drawInputFrame(char const *name, InputFrame const &frame, int &x, int &y, unsigned short &w, unsigned short &h, bool noalias=false, bool casync=false)
Draw the input video frame from the camera using zero-copy.
Definition: GUIhelper.C:335
jevois::GUIhelper::drawConsole
void drawConsole()
Definition: GUIhelper.C:1396
jevois::GUIhelper::compileModule
void compileModule()
Definition: GUIhelper.C:2646
jevois::module::SerStyle_Values
Style for standardized serial messages as defined in SerStyle_Values
Definition: Module.H:199
jevois::getNumInstalledVPUs
size_t getNumInstalledVPUs()
Get the number of Myriad-X VPUs present on this system.
Definition: SysInfo.C:112
jevois::RawImage::height
unsigned int height
Image height in pixels.
Definition: RawImage.H:146
jevois::GUIhelper::iinfo
void iinfo(jevois::InputFrame const &inframe, std::string const &fpscpu, unsigned short winw=0, unsigned short winh=0)
Display processing and video info at bottom of screen.
Definition: GUIhelper.C:584
jevois::GUIhelper::itsWindowTitle
std::string itsWindowTitle
Definition: GUIhelper.H:424
jevois::to_string
std::string to_string(T const &val)
Convert from type to string.
jevois::InputFrame
Exception-safe wrapper around a raw camera input frame.
Definition: InputFrame.H:50
jevois::GUIhelper::d2is
ImVec2 d2is(ImVec2 p, char const *name=nullptr)
Convert a 2D size from on-screen to within a rendered image.
Definition: GUIhelper.C:656
jevois::ParameterBase::summary
virtual const ParameterSummary summary() const =0
Get summary info about this parameter.
jevois::GUIhelper::itsCfgEditor
std::shared_ptr< GUIeditor > itsCfgEditor
Definition: GUIhelper.H:467
jevois::GUIhelper::i2ds
ImVec2 i2ds(ImVec2 p, char const *name=nullptr)
Convert a 2D size from within a rendered image to on-screen.
Definition: GUIhelper.C:437
jevois::RawImage::fmt
unsigned int fmt
Pixel format as a V4L2_PIX_FMT_XXX.
Definition: RawImage.H:147
Utils.H
jevois::Component::absolutePath
std::filesystem::path absolutePath(std::filesystem::path const &path="")
If given path is relative (not starting with /), prepend the Component path to it.
Definition: Component.C:530
jevois::GUIconsole::draw
void draw()
Render into ImGui.
Definition: GUIconsole.C:84
V
ImVec2 V
Definition: GUIhelper.C:2373
GUIeditor.H
jevois::getPythonExceptionString
std::string getPythonExceptionString(boost::python::error_already_set &)
Python exception translation to string so we can print the traceback to our serlog stream.
Definition: PythonException.C:146
jevois::GPUimage::i2d
ImVec2 i2d(ImVec2 const &p)
Convert coordinates of a point from within a rendered image to on-screen.
Definition: GPUimage.C:386
jevois::GUIhelper::iline
ImVec2 iline(int line=-1, char const *name=nullptr)
Get coordinates of the start of a given line of text to be drawn as overlay on top of an image.
Definition: GUIhelper.C:561
PythonException.H
jevois::ParameterSummary::name
std::string name
Plain name of the parameter.
Definition: Parameter.H:90
h
int h
Definition: GUIhelper.C:2373
jevois::GUIhelper::startCompilation
void startCompilation()
Compile a newly created module.
Definition: GUIhelper.C:2637
jevois::Engine::module
std::shared_ptr< Module > module() const
Get a pointer to our current module (may be null)
Definition: Engine.C:1434
jevois::async_little
std::future< std::invoke_result_t< std::decay_t< Function >, std::decay_t< Args >... > > async_little(Function &&f, Args &&... args)
Async execution using a thread pool.
jevois::GUIhelper::applyFillAlpha
ImU32 applyFillAlpha(ImU32 col) const
Definition: GUIhelper.C:553
jevois::StdModule
Base class for a module that supports standardized serial messages.
Definition: Module.H:232
jevois::Range::max
const T & max() const
Return the maximum value.
O
#define O(b)
Definition: GUIhelper.C:2378
jevois::GPUimage::d2i
ImVec2 d2i(ImVec2 const &p)
Convert coordinates of a point from on-screen to within a rendered image.
Definition: GPUimage.C:400
G
E G[999]
Definition: GUIhelper.C:2373
LINFO
#define LINFO(msg)
Convenience macro for users to print out console or syslog messages, INFO level.
Definition: Log.H:194
jevois::GPUimage::d2is
ImVec2 d2is(ImVec2 const &p)
Convert a 2D size from on-screen to within a rendered image.
Definition: GPUimage.C:407
jevois::GUIhelper::~GUIhelper
virtual ~GUIhelper()
Destructor.
Definition: GUIhelper.C:107
GUIhelper.H
FX
void FX(ImDrawList *d, V a, V b, V, ImVec4, F t)
Definition: GUIhelper.C:2379