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