JeVois  1.21
JeVois Smart Embedded Machine Vision Toolkit
Share this page:
Loading...
Searching...
No Matches
Pipeline.C
Go to the documentation of this file.
1// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2//
3// JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2021 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#include <jevois/DNN/Pipeline.H>
19#include <jevois/Debug/Log.H>
20#include <jevois/Util/Utils.H>
21#include <jevois/Util/Async.H>
24#include <jevois/DNN/Utils.H>
25#include <jevois/Core/Engine.H>
26
33
36
43
44#include <opencv2/core/utils/filesystem.hpp>
45
46#include <fstream>
47
48// ####################################################################################################
49
50// Simple class to hold a list of <name, value> pairs for our parameters, with updating the value of existing parameer
51// names if they are set several times (e.g., first set as a global, then set again for a particular network). Note that
52// here we do not check the validity of the parameters. This is delegated to Pipeline::setZooParam():
53namespace
54{
55 class ParHelper
56 {
57 public:
58 // ----------------------------------------------------------------------------------------------------
59 // Set a param from an entry in our yaml file
60 void set(cv::FileNode const & item, std::string const & zf, cv::FileNode const & node)
61 {
62 std::string k = item.name();
63 std::string v;
64 switch (item.type())
65 {
66 case cv::FileNode::INT: v = std::to_string((int)item); break;
67 case cv::FileNode::REAL: v = std::to_string((float)item); break;
68 case cv::FileNode::STRING: v = (std::string)item; break;
69 default:
70 if (&node == &item)
71 LFATAL("Invalid global zoo parameter " << k << " type " << item.type() << " in " << zf);
72 else
73 LFATAL("Invalid zoo parameter " << k << " type " << item.type() << " in " << zf << " node " << node.name());
74 }
75
76 // Update value if param already exists, or add new key,value pair:
77 for (auto & p : params) if (p.first == k) { p.second = v; return; }
78 params.emplace_back(std::make_pair(k, v));
79 }
80
81 // ----------------------------------------------------------------------------------------------------
82 // Get a value for entry subname under item if found, otherwise try our table of globals, otherwise empty
83 std::string pget(cv::FileNode & item, std::string const & subname)
84 {
85 std::string const v = (std::string)item[subname];
86 if (v.empty() == false) return v;
87 for (auto const & p : params) if (p.first == subname) return p.second;
88 return std::string();
89 }
90
91 // ----------------------------------------------------------------------------------------------------
92 // Un-set a previously set global
93 void unset(std::string const & name)
94 {
95 for (auto itr = params.begin(); itr != params.end(); ++itr)
96 if (itr->first == name) { params.erase(itr); return; }
97 }
98
99 // Ordering matters, so use a vector instead of map or unordered_map
100 std::vector<std::pair<std::string /* name */, std::string /* value */>> params;
101 };
102}
103
104// ####################################################################################################
105jevois::dnn::Pipeline::Pipeline(std::string const & instance) :
106 jevois::Component(instance), itsTpre("PreProc"), itsTnet("Network"), itsTpost("PstProc")
107{
108 itsAccelerators["TPU"] = jevois::getNumInstalledTPUs();
109 itsAccelerators["VPU"] = jevois::getNumInstalledVPUs();
110 itsAccelerators["NPU"] = jevois::getNumInstalledNPUs();
111 itsAccelerators["SPU"] = jevois::getNumInstalledSPUs();
112 itsAccelerators["OpenCV"] = 1; // OpenCV always available
113 itsAccelerators["ORT"] = 1; // ONNX runtime always available
114 itsAccelerators["Python"] = 1; // Python always available
115#ifdef JEVOIS_PLATFORM_PRO
116 itsAccelerators["VPUX"] = 1; // VPU emulation on CPU always available through OpenVino
117#endif
118 itsAccelerators["NPUX"] = 1; // NPU over Tim-VX always available since compiled into OpenCV
119
120 LINFO("Detected " <<
121 itsAccelerators["NPU"] << " JeVois-Pro NPUs, " <<
122 itsAccelerators["SPU"] << " Hailo8 SPUs, " <<
123 itsAccelerators["TPU"] << " Coral TPUs, " <<
124 itsAccelerators["VPU"] << " Myriad-X VPUs.");
125}
126
127// ####################################################################################################
129{
130 preproc::freeze(doit);
131 nettype::freeze(doit);
132 postproc::freeze(doit);
133
134 if (itsPreProcessor) itsPreProcessor->freeze(doit);
135 if (itsNetwork) itsNetwork->freeze(doit);
136 if (itsPostProcessor) itsPostProcessor->freeze(doit);
137}
138
139// ####################################################################################################
141{
142 // Freeze all params that users should not modify at runtime:
143 freeze(true);
144}
145
146// ####################################################################################################
148{
149 // If we have a network running async, make sure we wait here until it is done:
150 asyncNetWait();
151}
152
153// ####################################################################################################
155{
156 // Make sure network is not running as we die:
157 asyncNetWait();
158}
159
160// ####################################################################################################
162{
163 // If we were currently doing async processing, wait until network is done:
164 if (itsNetFut.valid())
165 while (true)
166 {
167 if (itsNetFut.wait_for(std::chrono::seconds(5)) == std::future_status::timeout)
168 LERROR("Still waiting for network to finish running...");
169 else break;
170 }
171
172 try { itsNetFut.get(); } catch (...) { }
173 itsOuts.clear();
174}
175
176// ####################################################################################################
177void jevois::dnn::Pipeline::onParamChange(pipeline::filter const &, jevois::dnn::pipeline::Filter const & val)
178{
179 // Reload the zoo file so that the filter can be applied to create the parameter def of pipe, but first we need this
180 // parameter to indeed be updated. So here we just set a flag and the update will occur in process(), after we run the
181 // current model one last time:
182 if (val != filter::get()) itsZooChanged = true;
183}
184
185// ####################################################################################################
186void jevois::dnn::Pipeline::onParamChange(pipeline::zooroot const &, std::string const & val)
187{
188 // Reload the zoo file, but first we need this parameter to indeed be updated. So here we just set a flag and the
189 // update will occur in process(), after we run the current model one last time:
190 if (val.empty() == false && val != zooroot::get()) itsZooChanged = true;
191}
192
193// ####################################################################################################
194void jevois::dnn::Pipeline::onParamChange(pipeline::benchmark const &, bool const & val)
195{
196 if (val)
197 {
198 statsfile::set("benchmark.html");
199 statsfile::freeze(true);
200 }
201 else
202 {
203 statsfile::freeze(false);
204 statsfile::reset();
205 }
206}
207
208// ####################################################################################################
209void jevois::dnn::Pipeline::onParamChange(pipeline::zoo const &, std::string const & val)
210{
211 // Just nuke everything:
212 itsPreProcessor.reset();
213 itsNetwork.reset();
214 itsPostProcessor.reset();
215 // Will get instantiated again when pipe param is set.
216
217 // Load zoo file:
218 std::vector<std::string> pipes;
219 scanZoo(jevois::absolutePath(zooroot::get(), val), filter::strget(), pipes, "");
220 LINFO("Found a total of " << pipes.size() << " valid pipelines.");
221
222 // Update the parameter def of pipe:
223 jevois::ParameterDef<std::string> newdef("pipe", "Pipeline to use, determined by entries in the zoo file and "
224 "by the current filter",
225 pipes[0], pipes, jevois::dnn::pipeline::ParamCateg);
226 pipe::changeParameterDef(newdef);
227
228 // Just changing the def does not change the param value, so change it now:
229 pipe::set(pipes[0]);
230
231 // Mark the zoo as not changed anymore, unless we are just starting the module and need to load a first net:
232 itsZooChanged = false;
233}
234
235// ####################################################################################################
236void jevois::dnn::Pipeline::scanZoo(std::filesystem::path const & zoofile, std::string const & filt,
237 std::vector<std::string> & pipes, std::string const & indent)
238{
239 LINFO(indent << "Scanning model zoo file " << zoofile << " with filter [" << filt << "]...");
240 int ntot = 0, ngood = 0;
241
242 bool has_vpu = false;
243 auto itr = itsAccelerators.find("VPU");
244 if (itr != itsAccelerators.end() && itr->second > 0) has_vpu = true;
245
246 // Scan the zoo file to update the parameter def of the pipe parameter:
247 cv::FileStorage fs(zoofile, cv::FileStorage::READ);
248 if (fs.isOpened() == false) LFATAL("Could not open zoo file " << zoofile);
249 cv::FileNode fn = fs.root();
250 ParHelper ph;
251
252 for (cv::FileNodeIterator fit = fn.begin(); fit != fn.end(); ++fit)
253 {
254 cv::FileNode item = *fit;
255
256 // Process include: directives recursively:
257 if (item.name() == "include")
258 {
259 scanZoo(jevois::absolutePath(zooroot::get(), (std::string)item), filt, pipes, indent + " ");
260 }
261 // Process includedir: directives (only one level of directory is scanned):
262 else if (item.name() == "includedir")
263 {
264 std::filesystem::path const dir = jevois::absolutePath(zooroot::get(), (std::string)item);
265 for (auto const & dent : std::filesystem::recursive_directory_iterator(dir))
266 if (dent.is_regular_file())
267 {
268 std::filesystem::path const path = dent.path();
269 std::filesystem::path const ext = path.extension();
270 if (ext == ".yml" || ext == ".yaml") scanZoo(path, filt, pipes, indent + " ");
271 }
272 }
273 // Unset a previously set global?
274 else if (item.name() == "unset")
275 {
276 ph.unset((std::string)item);
277 }
278 // Set a global:
279 else if (! item.isMap())
280 {
281 ph.set(item, zoofile, item);
282 }
283 // Map type (model definition):
284 else
285 {
286 ++ntot;
287
288 // As a prefix, we use OpenCV for OpenCV models on CPU/OpenCL backends, and VPU for InferenceEngine backend with
289 // Myriad target, VPUX for InferenceEngine/CPU (arm-compute OpenVino plugin, only works on platform), and NPUX for
290 // TimVX/NPU (NPU using TimVX OpenCV extension, uses NPU on platform or emulator on host):
291
292 // Set then get nettype to account for globals:
293 std::string typ = ph.pget(item, "nettype");
294
295 if (typ == "OpenCV")
296 {
297 std::string backend = ph.pget(item, "backend");
298 std::string target = ph.pget(item, "target");
299
300 if (backend == "InferenceEngine")
301 {
302 if (target == "Myriad")
303 {
304 if (has_vpu) typ = "VPU"; // run VPU models on VPU if MyriadX accelerator is present
305#ifdef JEVOIS_PLATFORM_PRO
306 else typ = "VPUX"; // emulate VPU models on CPU through ARM-Compute if MyriadX accelerator not present
307#else
308 else continue; // VPU emulation does not work on host...
309#endif
310 }
311 else if (target == "CPU") typ = "VPUX";
312 }
313 else if (backend == "TimVX" && target == "NPU") typ = "NPUX";
314 }
315
316 // Do not consider a model if we do not have the accelerator for it:
317 bool has_accel = false;
318 itr = itsAccelerators.find(typ);
319 if (itr != itsAccelerators.end() && itr->second > 0) has_accel = true;
320
321 // Add this pipe if it matches our filter and we have the accelerator for it:
322 if ((filt == "All" || typ == filt) && has_accel)
323 {
324 std::string const postproc = ph.pget(item, "postproc");
325 pipes.emplace_back(typ + ':' + postproc + ':' + item.name());
326 ++ngood;
327 }
328 }
329 }
330
331 LINFO(indent << "Found " << ntot << " pipelines, " << ngood << " passed the filter.");
332}
333
334// ####################################################################################################
335void jevois::dnn::Pipeline::onParamChange(pipeline::pipe const &, std::string const & val)
336{
337#ifdef JEVOIS_PRO
338 // Reset the data peekin on each pipe change:
339 itsShowDataPeek = false;
340 itsDataPeekOutIdx = 0;
341 itsDataPeekFreeze = false;
342 itsDataPeekStr.clear();
343#endif
344
345 if (val.empty()) return;
346 itsPipeThrew = false;
347 freeze(false);
348
349 // Clear any errors related to previous pipeline:
350 engine()->clearErrors();
351
352 // Find the desired pipeline, and set it up:
353 std::string const z = jevois::absolutePath(zooroot::get(), zoo::get());
354 std::vector<std::string> tok = jevois::split(val, ":");
355 if (selectPipe(z, tok) == false)
356 LFATAL("Could not find pipeline entry [" << val << "] in zoo file " << z << " and its includes");
357
358 freeze(true);
359}
360
361// ####################################################################################################
362bool jevois::dnn::Pipeline::selectPipe(std::string const & zoofile, std::vector<std::string> const & tok)
363{
364 // We might have frozen processing to Sync if we ran a NetworkPython previously, so unfreeze here:
365 processing::freeze(false);
366 processing::set(jevois::dnn::pipeline::Processing::Async);
367
368 // Check if we have a VPU, to use VPU vs VPUX:
369 bool has_vpu = false;
370 auto itr = itsAccelerators.find("VPU");
371 if (itr != itsAccelerators.end() && itr->second > 0) has_vpu = true;
372 bool vpu_emu = false;
373
374 // Clear any old stats:
375 itsPreStats.clear(); itsNetStats.clear(); itsPstStats.clear();
376 itsStatsWarmup = true; // warmup before computing new stats
377
378 // Open the zoo file:
379 cv::FileStorage fs(zoofile, cv::FileStorage::READ);
380 if (fs.isOpened() == false) LFATAL("Could not open zoo file " << zoofile);
381
382 // Find the desired pipeline:
383 ParHelper ph;
384 cv::FileNode fn = fs.root(), node;
385
386 for (cv::FileNodeIterator fit = fn.begin(); fit != fn.end(); ++fit)
387 {
388 cv::FileNode item = *fit;
389
390 // Process include: directives recursively, end the recursion if we found our pipe in there:
391 if (item.name() == "include")
392 {
393 if (selectPipe(jevois::absolutePath(zooroot::get(), (std::string)item), tok)) return true;
394 }
395
396 // Process includedir: directives (only one level of directory is scanned), end recursion if we found our pipe:
397 else if (item.name() == "includedir")
398 {
399 std::filesystem::path const dir = jevois::absolutePath(zooroot::get(), (std::string)item);
400 for (auto const & dent : std::filesystem::recursive_directory_iterator(dir))
401 if (dent.is_regular_file())
402 {
403 std::filesystem::path const path = dent.path();
404 std::filesystem::path const ext = path.extension();
405 if (ext == ".yml" || ext == ".yaml") if (selectPipe(path, tok)) return true;
406 }
407 }
408
409 // Unset a previously set global?
410 else if (item.name() == "unset")
411 {
412 ph.unset((std::string)item);
413 }
414 // Set a global:
415 else if (! item.isMap())
416 {
417 ph.set(item, zoofile, node);
418 }
419 // This is an entry for a pipeline with a bunch of params under it:
420 else
421 {
422 if (item.name() != tok.back()) continue;
423 if (tok.size() == 1) { node = item; break; }
424 if (tok.size() != 3) LFATAL("Malformed pipeline name: " << jevois::join(tok, ":"));
425
426 // Skip if postproc is no match:
427 std::string postproc = ph.pget(item, "postproc");
428 if (postproc != tok[1] && postproc::strget() != tok[1]) continue;
429
430 std::string nettype = ph.pget(item, "nettype");
431 std::string backend = ph.pget(item, "backend");
432 std::string target = ph.pget(item, "target");
433
434 if (tok[0] == "VPU")
435 {
436 if (nettype == "OpenCV" && backend == "InferenceEngine" && target == "Myriad")
437 { node = item; break; }
438 }
439 else if (tok[0] == "VPUX")
440 {
441 if (nettype == "OpenCV" && backend == "InferenceEngine")
442 {
443 if (target == "Myriad" && has_vpu == false) { vpu_emu = true; node = item; break; }
444 else if (target == "CPU") { node = item; break; }
445 }
446 }
447 else if (tok[0] == "NPUX")
448 {
449 if (nettype == "OpenCV" && backend == "TimVX" && target == "NPU")
450 { node = item; break; }
451 }
452 else
453 {
454 if (nettype == tok[0])
455 { node = item; break; }
456 }
457 }
458 }
459
460 // If the spec was not a match with any entries in the file, return false:
461 if (node.empty()) return false;
462
463 // Found the pipe. First nuke our current pre/net/post:
464 asyncNetWait();
465 itsPreProcessor.reset(); removeSubComponent("preproc", false);
466 itsNetwork.reset(); removeSubComponent("network", false);
467 itsPostProcessor.reset(); removeSubComponent("postproc", false);
468
469 // Then set all the global parameters of the current file:
470 //for (auto const & pp : ph.params)
471 // setZooParam(pp.first, pp.second, zoofile, fs.root());
472
473 // Then iterate over all pipeline params and set them: first update our table, then set params from the whole table:
474 for (cv::FileNodeIterator fit = node.begin(); fit != node.end(); ++fit)
475 ph.set(*fit, zoofile, node);
476
477 for (auto const & pp : ph.params)
478 {
479 if (vpu_emu && pp.first == "target") setZooParam(pp.first, "CPU", zoofile, node);
480 else setZooParam(pp.first, pp.second, zoofile, node);
481 }
482
483 // Running a python net async segfaults instantly if we are also concurrently running pre or post processing in
484 // python, as python is not re-entrant... so force sync here:
485 if (dynamic_cast<jevois::dnn::NetworkPython *>(itsNetwork.get()) &&
486 (dynamic_cast<jevois::dnn::PreProcessorPython *>(itsPreProcessor.get()) ||
487 dynamic_cast<jevois::dnn::PostProcessorPython *>(itsPostProcessor.get())))
488 {
489 if (processing::get() != jevois::dnn::pipeline::Processing::Sync)
490 {
491 LERROR("Network of type Python cannot run Async if pre- or post- processor are also Python "
492 "-- FORCING Sync processing");
493 processing::set(jevois::dnn::pipeline::Processing::Sync);
494 }
495 processing::freeze(true);
496 }
497
498 return true;
499}
500
501// ####################################################################################################
502void jevois::dnn::Pipeline::setZooParam(std::string const & k, std::string const & v,
503 std::string const & zf, cv::FileNode const & node)
504{
505 // The zoo file may contain extra params, like download URL, etc. To ignore those while still catching invalid
506 // values on our parameters, we first check whether the parameter exists, and, if so, try to set it:
507 bool hasparam = false;
508 try { getParamStringUnique(k); hasparam = true; } catch (...) { }
509
510 if (hasparam)
511 {
512 LINFO("Setting ["<<k<<"] to ["<<v<<']');
513
514 try { setParamStringUnique(k, v); }
515 catch (std::exception const & e)
516 { LFATAL("While parsing [" << node.name() << "] in model zoo file " << zf << ": " << e.what()); }
517 catch (...)
518 { LFATAL("While parsing [" << node.name() << "] in model zoo file " << zf << ": unknown error"); }
519 }
520 else if (paramwarn::get())
521 engine()->reportError("WARNING: Unused parameter [" + k + "] in " + zf + " node [" + node.name() + "]");
522}
523
524// ####################################################################################################
525void jevois::dnn::Pipeline::onParamChange(pipeline::preproc const &, pipeline::PreProc const & val)
526{
527 itsPreProcessor.reset(); removeSubComponent("preproc", false);
528
529 switch (val)
530 {
531 case jevois::dnn::pipeline::PreProc::Blob:
532 itsPreProcessor = addSubComponent<jevois::dnn::PreProcessorBlob>("preproc");
533 break;
534
535 case jevois::dnn::pipeline::PreProc::Python:
536 itsPreProcessor = addSubComponent<jevois::dnn::PreProcessorPython>("preproc");
537 break;
538 }
539
540 if (itsPreProcessor) LINFO("Instantiated pre-processor of type " << itsPreProcessor->className());
541 else LINFO("No pre-processor");
542}
543
544// ####################################################################################################
545void jevois::dnn::Pipeline::onParamChange(pipeline::nettype const &, pipeline::NetType const & val)
546{
547 asyncNetWait(); // If currently processing async net, wait until done
548
549 itsNetwork.reset(); removeSubComponent("network", false);
550
551 switch (val)
552 {
553 case jevois::dnn::pipeline::NetType::OpenCV:
554 itsNetwork = addSubComponent<jevois::dnn::NetworkOpenCV>("network");
555 break;
556
557#ifdef JEVOIS_PRO
558
559 case jevois::dnn::pipeline::NetType::ORT:
560 itsNetwork = addSubComponent<jevois::dnn::NetworkONNX>("network");
561 break;
562
563 case jevois::dnn::pipeline::NetType::NPU:
564#ifdef JEVOIS_PLATFORM
565 itsNetwork = addSubComponent<jevois::dnn::NetworkNPU>("network");
566#else // JEVOIS_PLATFORM
567 LFATAL("NPU network is only supported on JeVois-Pro Platform");
568#endif
569 break;
570
571 case jevois::dnn::pipeline::NetType::SPU:
572 itsNetwork = addSubComponent<jevois::dnn::NetworkHailo>("network");
573 break;
574
575 case jevois::dnn::pipeline::NetType::TPU:
576 itsNetwork = addSubComponent<jevois::dnn::NetworkTPU>("network");
577 break;
578#endif
579
580 case jevois::dnn::pipeline::NetType::Python:
581 itsNetwork = addSubComponent<jevois::dnn::NetworkPython>("network");
582 break;
583 }
584
585 if (itsNetwork) LINFO("Instantiated network of type " << itsNetwork->className());
586 else LINFO("No network");
587
588 // We already display a "loading..." message while the network is loading, but some OpenCV networks take a long time
589 // to process their first frame after they are loaded (e.g., YuNet initializes all the anchors on first frame). So
590 // here we set some placeholder text that will appear after the network is loaded and is processing the first frame:
591 itsInputAttrs.clear();
592 itsNetInfo.clear();
593 itsNetInfo.emplace_back("* Input Tensors");
594 itsNetInfo.emplace_back("Initializing network...");
595 itsNetInfo.emplace_back("* Network");
596 itsNetInfo.emplace_back("Initializing network...");
597 itsNetInfo.emplace_back("* Output Tensors");
598 itsNetInfo.emplace_back("Initializing network...");
599 itsAsyncNetInfo = itsNetInfo;
600 itsAsyncNetworkTime = "Network: -";
601 itsAsyncNetworkSecs = 0.0;
602}
603
604// ####################################################################################################
605void jevois::dnn::Pipeline::onParamChange(pipeline::postproc const &, pipeline::PostProc const & val)
606{
607 asyncNetWait(); // If currently processing async net, wait until done
608
609 itsPostProcessor.reset(); removeSubComponent("postproc", false);
610
611 switch (val)
612 {
613 case jevois::dnn::pipeline::PostProc::Classify:
614 itsPostProcessor = addSubComponent<jevois::dnn::PostProcessorClassify>("postproc");
615 break;
616 case jevois::dnn::pipeline::PostProc::Detect:
617 itsPostProcessor = addSubComponent<jevois::dnn::PostProcessorDetect>("postproc");
618 break;
619 case jevois::dnn::pipeline::PostProc::Segment:
620 itsPostProcessor = addSubComponent<jevois::dnn::PostProcessorSegment>("postproc");
621 break;
622 case jevois::dnn::pipeline::PostProc::YuNet:
623 itsPostProcessor = addSubComponent<jevois::dnn::PostProcessorYuNet>("postproc");
624 break;
625 case jevois::dnn::pipeline::PostProc::Python:
626 itsPostProcessor = addSubComponent<jevois::dnn::PostProcessorPython>("postproc");
627 break;
628 case jevois::dnn::pipeline::PostProc::Stub:
629 itsPostProcessor = addSubComponent<jevois::dnn::PostProcessorStub>("postproc");
630 break;
631 }
632
633 if (itsPostProcessor) LINFO("Instantiated post-processor of type " << itsPostProcessor->className());
634 else LINFO("No post-processor");
635}
636
637// ####################################################################################################
639{
640 return itsPreProcessor && itsNetwork && itsNetwork->ready() && itsPostProcessor;
641}
642
643// ####################################################################################################
645{
646 if (itsNetFut.valid() && itsNetFut.wait_for(std::chrono::milliseconds(2)) == std::future_status::ready)
647 {
648 itsOuts = itsNetFut.get();
649 itsNetInfo.clear();
650 std::swap(itsNetInfo, itsAsyncNetInfo);
651 itsProcTimes[1] = itsAsyncNetworkTime;
652 itsProcSecs[1] = itsAsyncNetworkSecs;
653 return true;
654 }
655 return false;
656}
657
658// ####################################################################################################
660 jevois::OptGUIhelper * helper, bool idle)
661{
662 // Reload the zoo file if filter has changed:
663 if (itsZooChanged) zoo::set(zoo::get());
664
665 // If the pipeline is throwing exception at any stage, do not do anything here, itsPipeThrew is cleared when selecting
666 // a new pipe:
667 if (itsPipeThrew) return;
668
669 bool const ovl = overlay::get();
670 itsOutImgY = 5; // y text position when using outimg text drawings
671 bool refresh_data_peek = false; // Will be true after each post-processing is actually run
672
673#ifdef JEVOIS_PRO
674 // Open an info window if using GUI and not idle:
675 if (helper && idle == false)
676 {
677 // Set window size applied only on first use ever, otherwise from imgui.ini:
678 ImGui::SetNextWindowPos(ImVec2(24, 159), ImGuiCond_FirstUseEver);
679 ImGui::SetNextWindowSize(ImVec2(464, 877), ImGuiCond_FirstUseEver);
680
681 // Open the window:
682 ImGui::Begin((instanceName() + ':' + getParamStringUnique("pipe")).c_str());
683 }
684#else
685 (void)helper; // avoid compiler warning
686#endif
687
688 // If we want an overlay, show network name on first line:
689 if (ovl)
690 {
691 if (outimg)
692 {
693 jevois::rawimage::writeText(*outimg, instanceName() + ':' + getParamStringUnique("pipe"),
694 5, itsOutImgY, jevois::yuyv::White);
695 itsOutImgY += 11;
696 }
697
698#ifdef JEVOIS_PRO
699 if (helper) helper->itext(instanceName() + ':' + getParamStringUnique("pipe"));
700#endif
701 }
702
703 // If network is not ready, inform user. Be careful that ready() may throw, eg, if bad network name was given and
704 // network could not be loaded:
705 try
706 {
707 if (ready() == false)
708 {
709 char const * msg = itsNetwork ? "Loading network..." : "No network selected...";
710
711 if (outimg)
712 {
713 jevois::rawimage::writeText(*outimg, msg, 5, itsOutImgY, jevois::yuyv::White);
714 itsOutImgY += 11;
715 }
716
717#ifdef JEVOIS_PRO
718 if (helper)
719 {
720 if (idle == false) ImGui::TextUnformatted(msg);
721 if (ovl) helper->itext(msg);
722 }
723#endif
724
725 itsProcTimes = { "PreProc: -", "Network: -", "PstProc: -" };
726 itsProcSecs = { 0.0, 0.0, 0.0 };
727 }
728 else
729 {
730 // Network is ready, run processing, either single-thread (Sync) or threaded (Async):
731 switch (processing::get())
732 {
733 // --------------------------------------------------------------------------------
734 case jevois::dnn::pipeline::Processing::Sync:
735 {
736 asyncNetWait(); // If currently processing async net, wait until done
737
738 // Pre-process:
739 itsTpre.start();
740 if (itsInputAttrs.empty()) itsInputAttrs = itsNetwork->inputShapes();
741 itsBlobs = itsPreProcessor->process(inimg, itsInputAttrs);
742 itsProcTimes[0] = itsTpre.stop(&itsProcSecs[0]);
743 itsPreProcessor->sendreport(mod, outimg, helper, ovl, idle);
744
745 // Network forward pass:
746 itsNetInfo.clear();
747 itsTnet.start();
748 itsOuts = itsNetwork->process(itsBlobs, itsNetInfo);
749 itsProcTimes[1] = itsTnet.stop(&itsProcSecs[1]);
750
751 // Show network info:
752 showInfo(itsNetInfo, mod, outimg, helper, ovl, idle);
753
754 // Post-Processing:
755 itsTpost.start();
756 itsPostProcessor->process(itsOuts, itsPreProcessor.get());
757 itsProcTimes[2] = itsTpost.stop(&itsProcSecs[2]);
758 itsPostProcessor->report(mod, outimg, helper, ovl, idle);
759 refresh_data_peek = true;
760 }
761 break;
762
763 // --------------------------------------------------------------------------------
764 case jevois::dnn::pipeline::Processing::Async:
765 {
766 // We are going to run pre-processing and post-processing synchronously, and network in a thread. One small
767 // complication is that we are going to run post-processing on every frame so that the drawings do not
768 // flicker. We will keep post-processing the same results until new results replace them.
769
770 // Are we running the network, and is it done? If so, get the outputs:
771 bool needpost = checkAsyncNetComplete();
772
773 // If we are not running a network, start it:
774 if (itsNetFut.valid() == false)
775 {
776 // Pre-process in the current thread:
777 itsTpre.start();
778 if (itsInputAttrs.empty()) itsInputAttrs = itsNetwork->inputShapes();
779 itsBlobs = itsPreProcessor->process(inimg, itsInputAttrs);
780 itsProcTimes[0] = itsTpre.stop(&itsProcSecs[0]);
781
782 // Network forward pass in a thread:
783 itsNetFut =
784 jevois::async([this]()
785 {
786 itsTnet.start();
787 std::vector<cv::Mat> outs = itsNetwork->process(itsBlobs, itsAsyncNetInfo);
788 itsAsyncNetworkTime = itsTnet.stop(&itsAsyncNetworkSecs);
789
790 // OpenCV DNN seems to be re-using and overwriting the same output matrices,
791 // so we need to make a deep copy of the outputs if the network type is OpenCV:
792 if (dynamic_cast<jevois::dnn::NetworkOpenCV *>(itsNetwork.get()) == nullptr)
793 return outs;
794
795 std::vector<cv::Mat> outscopy;
796 for (cv::Mat const & m : outs) outscopy.emplace_back(m.clone());
797 return outscopy;
798 });
799 }
800
801 // Report pre-processing results on every frame:
802 itsPreProcessor->sendreport(mod, outimg, helper, ovl, idle);
803
804 // Show network info on every frame:
805 showInfo(itsNetInfo, mod, outimg, helper, ovl, idle);
806
807 // Run post-processing if needed:
808 if (needpost && itsOuts.empty() == false)
809 {
810 itsTpost.start();
811 itsPostProcessor->process(itsOuts, itsPreProcessor.get());
812 itsProcTimes[2] = itsTpost.stop(&itsProcSecs[2]);
813 refresh_data_peek = true;
814 }
815
816 // Report/draw post-processing results on every frame:
817 itsPostProcessor->report(mod, outimg, helper, ovl, idle);
818 }
819 break;
820 }
821
822 // Update our rolling average of total processing time:
823 itsSecsSum += itsProcSecs[0] + itsProcSecs[1] + itsProcSecs[2];
824 if (++itsSecsSumNum == 20) { itsSecsAvg = itsSecsSum / itsSecsSumNum; itsSecsSum = 0.0; itsSecsSumNum = 0; }
825
826 // If computing benchmarking stats, update them now:
827 if (statsfile::get().empty() == false && itsOuts.empty() == false)
828 {
829 static std::vector<std::string> pipelines;
830 static bool statswritten = false;
831 static size_t benchpipe = 0;
832
833 if (benchmark::get())
834 {
835 if (pipelines.empty())
836 {
837 // User just turned on benchmark mode. List all pipes and start iterating over them:
838 // Valid values string format is List:[A|B|C] where A, B, C are replaced by the actual elements.
839 std::string pipes = pipe::def().validValuesString();
840 size_t const idx = pipes.find('[');
841 pipes = pipes.substr(idx + 1, pipes.length() - idx - 2); // risky code but we control the string's contents
842 pipelines = jevois::split(pipes, "\\|");
843 benchpipe = 0;
844 statswritten = false;
845 pipe::set(pipelines[benchpipe]);
846#ifdef JEVOIS_PRO
847 if (helper)
848 {
849 helper->reportError("Starting DNN benchmark...");
850 helper->reportError("Benchmarking: " +pipelines[benchpipe]);
851 }
852#endif
853 }
854 else
855 {
856 // Switch to the next pipeline after enough stats have been written:
857 if (statswritten)
858 {
859 ++benchpipe;
860 statswritten = false;
861 if (benchpipe >= pipelines.size())
862 {
863 pipelines.clear();
864 benchmark::set(false);
865#ifdef JEVOIS_PRO
866 if (helper) helper->reportError("DNN benchmark complete.");
867#endif
868 }
869 else
870 {
871 pipe::set(pipelines[benchpipe]);
872#ifdef JEVOIS_PRO
873 if (helper) helper->reportError("Benchmarking: " +pipelines[benchpipe]);
874#endif
875 }
876 }
877 }
878 }
879 else pipelines.clear();
880
881 itsPreStats.push_back(itsProcSecs[0]);
882 itsNetStats.push_back(itsProcSecs[1]);
883 itsPstStats.push_back(itsProcSecs[2]);
884
885 // Discard data for a few warmup frames after we start a new net:
886 if (itsStatsWarmup && itsPreStats.size() == 200)
887 { itsStatsWarmup = false; itsPreStats.clear(); itsNetStats.clear(); itsPstStats.clear(); }
888
889 if (itsPreStats.size() == 500)
890 {
891 // Compute totals:
892 std::vector<double> tot;
893 for (size_t i = 0; i < itsPreStats.size(); ++i)
894 tot.emplace_back(itsPreStats[i] + itsNetStats[i] + itsPstStats[i]);
895
896 // Append to stats file:
897 std::string const fn = jevois::absolutePath(JEVOIS_SHARE_PATH, statsfile::get());
898 std::ofstream ofs(fn, std::ios_base::app);
899 if (ofs.is_open())
900 {
901 ofs << "<tr><td class=jvpipe>" << pipe::get() << " </td>";
902
903 std::vector<std::string> insizes;
904 for (cv::Mat const & m : itsBlobs)
905 insizes.emplace_back(jevois::replaceAll(jevois::dnn::shapestr(m), " ", "&nbsp;"));
906 ofs << "<td class=jvnetin>" << jevois::join(insizes, ", ") << "</td>";
907
908 std::vector<std::string> outsizes;
909 for (cv::Mat const & m : itsOuts)
910 outsizes.emplace_back(jevois::replaceAll(jevois::dnn::shapestr(m), " ", "&nbsp;"));
911 ofs << "<td class=jvnetout>" << jevois::join(outsizes, ", ") << "</td>";
912
913 ofs <<
914 "<td class=jvprestats>" << jevois::replaceAll(jevois::secs2str(itsPreStats), " ", "&nbsp;") << "</td>"
915 "<td class=jvnetstats>" << jevois::replaceAll(jevois::secs2str(itsNetStats), " ", "&nbsp;") << "</td>"
916 "<td class=jvpststats>" << jevois::replaceAll(jevois::secs2str(itsPstStats), " ", "&nbsp;") << "</td>"
917 "<td class=jvtotstats>" << jevois::replaceAll(jevois::secs2str(tot), " ", "&nbsp;") << "</td>";
918
919 // Finally report average fps:
920 double avg = 0.0;
921 for (double t : tot) avg += t;
922 avg /= tot.size();
923 if (avg) avg = 1.0 / avg; // from s/frame to frames/s
924 ofs << "<td class=jvfps>" << std::fixed << std::showpoint << std::setprecision(1) <<
925 avg << "&nbsp;fps</td></tr>" << std::endl;
926
927 // Ready for next round:
928 itsPreStats.clear();
929 itsNetStats.clear();
930 itsPstStats.clear();
931 LINFO("Network stats appended to " << fn);
932 statswritten = true;
933 }
934 }
935 }
936 }
937 }
938 catch (...)
939 {
940 itsPipeThrew = true;
941
942#ifdef JEVOIS_PRO
943 if (helper) helper->reportAndIgnoreException(instanceName());
944 else jevois::warnAndIgnoreException(instanceName());
945#else
946 jevois::warnAndIgnoreException(instanceName());
947#endif
948 }
949
950#ifdef JEVOIS_PRO
951 // Report processing times and close info window if we opened it:
952 if (helper)
953 {
954 std::string total;
955 if (idle == false || ovl) total = jevois::secs2str(itsSecsAvg);
956
957 if (idle == false)
958 {
959 // Show processing times:
960 if (ImGui::CollapsingHeader("Processing Times", ImGuiTreeNodeFlags_DefaultOpen))
961 {
962 for (std::string const & s : itsProcTimes) ImGui::TextUnformatted(s.c_str());
963 ImGui::Text("OVERALL: %s/inference", total.c_str());
964 }
965 ImGui::Separator();
966
967 // Show a button to allow users to peek output data:
968 if (ImGui::Button("Peek output data")) itsShowDataPeek = true;
969
970 // Done with this window:
971 ImGui::End();
972
973 // Allow user to peek into output data:
974 showDataPeekWindow(helper, refresh_data_peek);
975 }
976
977 if (ovl)
978 {
979 for (std::string const & s : itsProcTimes) helper->itext(s);
980 helper->itext("OVERALL: " + total + "/inference");
981 }
982 }
983#else
984 (void)refresh_data_peek; // prevent compiler warning
985#endif
986
987 // Report processing times to outimg if present:
988 if (outimg && ovl)
989 {
990 for (std::string const & s : itsProcTimes)
991 {
992 jevois::rawimage::writeText(*outimg, s, 5, itsOutImgY, jevois::yuyv::White);
993 itsOutImgY += 11;
994 }
995 jevois::rawimage::writeText(*outimg, "OVERALL: " + jevois::secs2str(itsSecsAvg) + "/inference",
996 5, itsOutImgY, jevois::yuyv::White);
997 itsOutImgY += 11;
998 }
999}
1000
1001// ####################################################################################################
1002void jevois::dnn::Pipeline::showInfo(std::vector<std::string> const & info, jevois::StdModule *,
1003 jevois::RawImage * outimg, jevois::OptGUIhelper * helper, bool ovl, bool idle)
1004{
1005 bool show = true;
1006
1007 for (std::string const & s : info)
1008 {
1009 // On JeVois Pro, display info in the GUI:
1010#ifdef JEVOIS_PRO
1011 if (helper && idle == false)
1012 {
1013 // Create collapsible header and get its collapsed status:
1014 if (jevois::stringStartsWith(s, "* "))
1015 show = ImGui::CollapsingHeader(s.c_str() + 2, ImGuiTreeNodeFlags_DefaultOpen);
1016 else if (show)
1017 {
1018 // If header not collapsed, show data:
1019 if (jevois::stringStartsWith(s, "- ")) ImGui::BulletText("%s", s.c_str() + 2);
1020 else ImGui::TextUnformatted(s.c_str());
1021 }
1022 }
1023#else
1024 (void)idle; (void)show; (void)helper; // avoid warning
1025#endif
1026
1027 if (outimg && ovl)
1028 {
1029 jevois::rawimage::writeText(*outimg, s, 5, itsOutImgY, jevois::yuyv::White);
1030 itsOutImgY += 11;
1031 }
1032 }
1033}
1034
1035#ifdef JEVOIS_PRO
1036// ####################################################################################################
1038{
1039 // Do not show anything if user closed the window:
1040 if (itsShowDataPeek == false) return;
1041
1042 // Set window size applied only on first use ever, otherwise from imgui.ini:
1043 ImGui::SetNextWindowPos(ImVec2(100, 50), ImGuiCond_FirstUseEver);
1044 ImGui::SetNextWindowSize(ImVec2(900, 600), ImGuiCond_FirstUseEver);
1045
1046 // Light blue window background:
1047 ImGui::PushStyleColor(ImGuiCol_WindowBg, 0xf0ffe0e0);
1048
1049 // Open the window:
1050 ImGui::Begin("DNN Output Peek", &itsShowDataPeek, ImGuiWindowFlags_HorizontalScrollbar);
1051
1052 // Draw a combo to select which output:
1053 std::vector<std::string> outspecs;
1054 for (size_t i = 0; cv::Mat const & out : itsOuts)
1055 outspecs.emplace_back("Out " + std::to_string(i++) + ": " + jevois::dnn::shapestr(out));
1056 if (helper->combo("##dataPeekOutSelect", outspecs, itsDataPeekOutIdx)) itsDataPeekFreeze = false;
1057
1058 ImGui::SameLine(); ImGui::TextUnformatted(" "); ImGui::SameLine();
1059 helper->toggleButton("Freeze", &itsDataPeekFreeze);
1060 ImGui::Separator();
1061
1062 // Draw the data:
1063 if ( (itsDataPeekFreeze && itsDataPeekStr.empty() == false) || refresh == false)
1064 ImGui::TextUnformatted(itsDataPeekStr.c_str());
1065 else
1066 {
1067 // OpenCV Mat::operator<< cannot handle >2D, try to collapse any dimensions with size 1:
1068 cv::Mat const & out = itsOuts[itsDataPeekOutIdx];
1069 std::vector<int> newsz;
1070 cv::MatSize const & ms = out.size; int const nd = ms.dims();
1071 for (int i = 0; i < nd; ++i) if (ms[i] > 1) newsz.emplace_back(ms[i]);
1072 cv::Mat const out2(newsz, out.type(), out.data);
1073
1074 try
1075 {
1076 std::ostringstream oss;
1077 if (newsz.size() > 3)
1078 throw "too many dims";
1079 else if (newsz.size() == 3)
1080 {
1081 cv::Range ranges[3];
1082 ranges[2] = cv::Range::all();
1083 ranges[1] = cv::Range::all();
1084 for (int i = 0; i < newsz[0]; ++i)
1085 {
1086 oss << "-------------------------------------------------------------------------------\n";
1087 oss << "Third dimension index = " << i << ":\n";
1088 oss << "-------------------------------------------------------------------------------\n\n";
1089 ranges[0] = cv::Range(i, i+1);
1090 cv::Mat slice = out2(ranges); // still 3D but with 1 as 3D dimension...
1091 cv::Mat slice2d(cv::Size(newsz[2], newsz[1]), slice.type(), slice.data); // Now 2D
1092 oss << slice2d << "\n\n";
1093 }
1094 }
1095 else
1096 oss << out2;
1097
1098 itsDataPeekStr = oss.str();
1099 }
1100 catch (...) { itsDataPeekStr = "Sorry, cannot display this type of tensor..."; }
1101
1102 ImGui::TextUnformatted(itsDataPeekStr.c_str());
1103
1104 if (out2.total() > 10000)
1105 {
1106 helper->reportError("Large data peek - Freezing data display\n"
1107 "Click the Freeze button to refresh once");
1108 itsDataPeekFreeze = true;
1109 }
1110 }
1111
1112 // Done with this window:
1113 ImGui::End();
1114 ImGui::PopStyleColor();
1115}
1116#endif
1117
#define JEVOIS_SHARE_PATH
Base path for shared files (e.g., neural network weights, etc)
Definition Config.H:82
A component of a model hierarchy.
Definition Component.H:182
void clearErrors()
Clear all errors currently displayed in the JeVois-Pro GUI.
Definition Engine.C:1400
void reportError(std::string const &err)
Definition Engine.C:1391
Helper class to assist modules in creating graphical and GUI elements.
Definition GUIhelper.H:133
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 reportAndIgnoreException(std::string const &prefix="")
Report current exception in a modal dialog, then ignore it.
Definition GUIhelper.C:2609
void reportError(std::string const &err)
Report an error in an overlay window.
Definition GUIhelper.C:2576
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
bool toggleButton(char const *name, bool *val)
Helper to draw a toggle button.
Definition GUIhelper.C:2700
A Parameter Definition.
A raw image as coming from a V4L2 Camera and/or being sent out to a USB Gadget.
Definition RawImage.H:111
Base class for a module that supports standardized serial messages.
Definition Module.H:234
Wrapper around an OpenCV DNN neural network.
Wrapper around an DNN neural network invoked through python.
bool checkAsyncNetComplete()
Definition Pipeline.C:644
void showInfo(std::vector< std::string > const &info, jevois::StdModule *mod, jevois::RawImage *outimg, jevois::OptGUIhelper *helper, bool ovl, bool idle)
Definition Pipeline.C:1002
void preUninit() override
Called before all sub-Components are uninit()ed.
Definition Pipeline.C:147
void showDataPeekWindow(jevois::GUIhelper *helper, bool refresh)
Definition Pipeline.C:1037
void process(jevois::RawImage const &inimg, jevois::StdModule *mod, jevois::RawImage *outimg, jevois::OptGUIhelper *helper, bool idle=false)
Process an input image, send results to serial/image/gui.
Definition Pipeline.C:659
void onParamChange(pipeline::zooroot const &param, std::string const &val) override
Definition Pipeline.C:186
virtual ~Pipeline()
Destructor.
Definition Pipeline.C:154
bool ready() const
Returns true when all three of preproc, net, and postproc are ready.
Definition Pipeline.C:638
Pipeline(std::string const &instance)
Constructor.
Definition Pipeline.C:105
void freeze(bool doit)
Freeze/unfreeze parameters that users should not change while running.
Definition Pipeline.C:128
void postInit() override
Called after all sub-Components are init()ed.
Definition Pipeline.C:140
Post-Processor for neural network pipeline.
Pre-Processor for neural network pipeline written in python.
#define LFATAL(msg)
Convenience macro for users to print out console or syslog messages, FATAL level.
Definition Log.H:230
std::string warnAndIgnoreException(std::string const &prefix="")
Convenience function to catch an exception, issue some LERROR (depending on type),...
Definition Log.C:236
#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 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 writeText(RawImage &img, std::string const &txt, int x, int y, unsigned int col, Font font=Font6x10)
Write some text in an image.
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 secs2str(double secs)
Report a duration given in seconds with variable units (ns, us, ms, or s), with precision of 2 decima...
Definition Utils.C:478
std::filesystem::path absolutePath(std::filesystem::path const &root, std::filesystem::path const &path)
Compute an absolute path from two paths.
Definition Utils.C:385
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::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::string replaceAll(std::string const &str, std::string const &from, std::string const &to)
Replace all instances of 'from' with 'to'.
Definition Utils.C:361
unsigned short constexpr White
YUYV color value.
Definition RawImage.H:59
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
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