JeVois  1.3
JeVois Smart Embedded Machine Vision Toolkit
Share this page:
VideoMapping.C
Go to the documentation of this file.
1 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2016 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 
19 #include <jevois/Debug/Log.H>
20 #include <jevois/Util/Utils.H>
21 
22 #include <fstream>
23 #include <sstream>
24 #include <algorithm>
25 #include <cmath>
26 
27 #define PERROR(x) LERROR("In file " << JEVOIS_ENGINE_CONFIG_FILE << ':' << linenum << ": " << x)
28 
29 // ####################################################################################################
30 std::string jevois::VideoMapping::sopath() const
31 {
32  if (ispython) return JEVOIS_MODULE_PATH "/" + vendor + '/' + modulename + '/' + modulename + ".py";
33  else return JEVOIS_MODULE_PATH "/" + vendor + '/' + modulename + '/' + modulename + ".so";
34 }
35 
36 // ####################################################################################################
37 unsigned int jevois::VideoMapping::osize() const
38 { return jevois::v4l2ImageSize(ofmt, ow, oh); }
39 
40 // ####################################################################################################
41 unsigned int jevois::VideoMapping::csize() const
42 { return jevois::v4l2ImageSize(cfmt, cw, ch); }
43 
44 // ####################################################################################################
45 float jevois::VideoMapping::uvcToFps(unsigned int interval)
46 {
47  // Let's round it off to the nearest 1/100Hz:
48  return float(1000000000U / interval) * 0.01F;
49 }
50 
51 // ####################################################################################################
52 unsigned int jevois::VideoMapping::fpsToUvc(float fps)
53 {
54  return (unsigned int)(10000000.0F / fps + 0.499F);
55 }
56 
57 // ####################################################################################################
58 float jevois::VideoMapping::v4l2ToFps(struct v4l2_fract const & interval)
59 {
60  // Let's round it off to the nearest 1/100Hz:
61  return float(interval.denominator * 100U / interval.numerator) * 0.01F;
62 }
63 
64 // ####################################################################################################
65 struct v4l2_fract jevois::VideoMapping::fpsToV4l2(float fps)
66 {
67  return { 100U, (unsigned int)(fps * 100.0F) };
68 }
69 
70 // ####################################################################################################
71 std::string jevois::VideoMapping::ostr() const
72 {
73  std::ostringstream ss;
74  ss << jevois::fccstr(ofmt) << ' ' << ow << 'x' << oh << " @ " << ofps << "fps";
75  return ss.str();
76 }
77 
78 // ####################################################################################################
79 std::string jevois::VideoMapping::cstr() const
80 {
81  std::ostringstream ss;
82  ss << jevois::fccstr(cfmt) << ' ' << cw << 'x' << ch << " @ " << cfps << "fps";
83  return ss.str();
84 }
85 
86 // ####################################################################################################
87 std::string jevois::VideoMapping::str() const
88 {
89  std::ostringstream ss;
90 
91  ss << "OUT: " << this->ostr() << " CAM: " << this->cstr();
92  //ss << " (uvc " << uvcformat << '/' << uvcframe << '/' << jevois::VideoMapping::fpsToUvc(ofps) << ')';
93  ss << " MOD: " << this->vendor << ':' << this->modulename;
94  //ss << ' ' << this->sopath();
95  return ss.str();
96 }
97 
98 // ####################################################################################################
100 {
101  return (ofmt == other.ofmt && ow == other.ow && oh == other.oh && std::abs(ofps - other.ofps) < 0.01F &&
102  cfmt == other.cfmt && cw == other.cw && ch == other.ch && std::abs(cfps - other.cfps) < 0.01F);
103 }
104 
105 // ####################################################################################################
107 {
108  return (hasSameSpecsAs(other) && vendor == other.vendor && modulename == other.modulename &&
109  ispython == other.ispython);
110 }
111 
112 // ####################################################################################################
113 std::ostream & jevois::operator<<(std::ostream & out, jevois::VideoMapping const & m)
114 {
115  out << jevois::fccstr(m.ofmt) << ' ' << m.ow << ' ' << m.oh << ' ' << m.ofps << ' '
116  << jevois::fccstr(m.cfmt) << ' ' << m.cw << ' ' << m.ch << ' ' << m.cfps << ' '
117  << m.vendor << ' ' << m.modulename;
118  return out;
119 }
120 
121 // ####################################################################################################
122 std::istream & jevois::operator>>(std::istream & in, jevois::VideoMapping & m)
123 {
124  std::string of, cf;
125  in >> of >> m.ow >> m.oh >> m.ofps >> cf >> m.cw >> m.ch >> m.cfps >> m.vendor >> m.modulename;
126 
127  m.ofmt = jevois::strfcc(of);
128  m.cfmt = jevois::strfcc(cf);
129 
130  return in;
131 }
132 
133 // ####################################################################################################
134 std::vector<jevois::VideoMapping> jevois::loadVideoMappings(size_t & defidx, bool checkso)
135 {
136  std::ifstream ifs(JEVOIS_ENGINE_CONFIG_FILE);
137  if (ifs.is_open() == false) LFATAL("Could not open [" << JEVOIS_ENGINE_CONFIG_FILE << ']');
138  return jevois::videoMappingsFromStream(ifs, defidx, checkso);
139 }
140 
141 // ####################################################################################################
142 std::vector<jevois::VideoMapping> jevois::videoMappingsFromStream(std::istream & is, size_t & defidx, bool checkso)
143 {
144  size_t linenum = 1;
145  std::vector<jevois::VideoMapping> mappings;
146  jevois::VideoMapping defmapping = { };
147 
148  for (std::string line; std::getline(is, line); ++linenum)
149  {
150  std::vector<std::string> tok = jevois::split(line);
151  if (tok.empty()) continue; // skip blank lines
152  if (tok.size() == 1 && tok[0].empty()) continue; // skip blank lines
153  if (tok[0][0] == '#') continue; // skip comments
154  if (tok.size() < 10) { PERROR("Found " << tok.size() << " tokens instead of >= 10 -- SKIPPING"); continue; }
155 
157  try
158  {
159  m.ofmt = jevois::strfcc(tok[0]);
160  m.ow = std::stoi(tok[1]);
161  m.oh = std::stoi(tok[2]);
162  m.ofps = std::stof(tok[3]);
163 
164  m.cfmt = jevois::strfcc(tok[4]);
165  m.cw = std::stoi(tok[5]);
166  m.ch = std::stoi(tok[6]);
167  m.cfps = std::stof(tok[7]);
168  }
169  catch (std::exception const & e) { PERROR("Skipping entry because of parsing error: " << e.what()); }
170  catch (...) { PERROR("Skipping entry because of parsing errors"); }
171 
172  m.vendor = tok[8];
173  m.modulename = tok[9];
174 
175  // First assume that it is a C++ compiled module and check for the .so file:
176  m.ispython = false;
177  if (checkso)
178  {
179  std::string sopath = m.sopath();
180  std::ifstream testifs(sopath);
181  if (testifs.is_open() == false)
182  {
183  // Could not find the .so, maybe it is a python module:
184  m.ispython = true; sopath = m.sopath();
185  std::ifstream testifs2(sopath);
186  if (testifs2.is_open() == false)
187  {
188  PERROR("Could not open module " << sopath << "|.so -- SKIPPING");
189  continue;
190  }
191  }
192  }
193 
194  // Handle optional star for default mapping. We tolerate several and pick the first one:
195  if (tok.size() > 10)
196  {
197  if (tok[10] == "*")
198  {
199  if (defmapping.cfmt == 0) defmapping = m;
200  if (tok.size() > 11 && tok[11][0] != '#') PERROR("Extra garbage after 11th token ignored");
201  }
202  else if (tok[10][0] != '#') PERROR("Extra garbage after 10th token ignored");
203  }
204 
205  mappings.push_back(m);
206  }
207 
208  // Sort the array:
209  std::sort(mappings.begin(), mappings.end(),
210  [=](jevois::VideoMapping const & a, jevois::VideoMapping const & b)
211  {
212  // Return true if a should be ordered before b:
213  if (a.ofmt < b.ofmt) return true;
214  if (a.ofmt == b.ofmt) {
215  if (a.ow > b.ow) return true;
216  if (a.ow == b.ow) {
217  if (a.oh > b.oh) return true;
218  if (a.oh == b.oh) {
219  if (a.ofps > b.ofps) return true;
220  if (std::abs(a.ofps - b.ofps) < 0.01F) {
221  // The two output modes are identical. Warn unless the output format is NONE. We will adjust the
222  // framerates later to distinguish the offenders:
223  if (a.ofmt != 0)
224  PERROR("WARNING: Two modes have identical output format: " <<
225  jevois::fccstr(a.ofmt) << ' ' << a.ow << 'x' << a.oh << " @ " << a.ofps << "fps");
226 
227  // All right, all USB stuff being equal, just sort according to the camera format:
228  if (a.cfmt < b.cfmt) return true;
229  if (a.cfmt == b.cfmt) {
230  if (a.cw > b.cw) return true;
231  if (a.cw == b.cw) {
232  if (a.ch > b.ch) return true;
233  if (a.ch == b.ch) {
234  if (a.cfps > b.cfps) return true;
235  // it's ok to have duplicates here since either those are NONE USB modes that are selected
236  // manually, or we will adjust below
237  }
238  }
239  }
240  }
241  }
242  }
243  }
244  return false;
245  });
246 
247  // We need at least one mapping to work, and we need at least one with UVC output too keep hosts happy:
248  if (mappings.empty() || mappings.back().ofmt == 0)
249  {
250  PERROR("No valid video mapping with UVC output found -- INSERTING A DEFAULT ONE");
252  m.ofmt = V4L2_PIX_FMT_YUYV; m.ow = 640; m.oh = 480; m.ofps = 30.0F;
253  m.cfmt = V4L2_PIX_FMT_YUYV; m.cw = 640; m.ch = 480; m.cfps = 30.0F;
254  m.vendor = "JeVois"; m.modulename = "PassThrough"; m.ispython = false;
255 
256  // We are guaranteed that this will not create a duplicate output mapping since either mappings was empty or it had
257  // no USB-out modes:
258  mappings.push_back(m);
259  }
260 
261  // If we had duplicate output formats, discard full exact duplicates (including same module), and otherwise adjust
262  // framerates slightly: In the sorting above, we ordered by decreasing ofps (all else being equal). Here we are going
263  // to decrease ofps on the second mapping when we hit a match. We need to beware that this should propagate down to
264  // subsequent matching mappings while preserving the ordering:
265  auto a = mappings.begin(), b = a + 1;
266  while (b != mappings.end())
267  {
268  // Discard exact duplicates, adjust frame rates for matching specs but different modules:
269  if (a->isSameAs(*b)) { b = mappings.erase(b); continue; }
270  else if (b->ofmt != 0 && a->ofmt == b->ofmt && a->ow == b->ow && a->oh == b->oh)
271  {
272  if (std::abs(a->ofps - b->ofps) < 0.01F) b->ofps -= 1.0F; // equal fps, decrease b.ofps by 1fps
273  else if (b->ofps > a->ofps) b->ofps = a->ofps - 1.0F; // got out of order because of a previous decrease
274  }
275  a = b; ++b;
276  }
277 
278  // Find back our default mapping index in the sorted array:
279  if (defmapping.cfmt == 0)
280  {
281  LERROR("No default video mapping provided, using first one with UVC output");
282  for (size_t i = 0; i < mappings.size(); ++i) if (mappings[i].ofmt) { defidx = i; break; }
283  }
284  else
285  {
286  // Default was set, find its index after sorting:
287  for (size_t i = 0; i < mappings.size(); ++i)
288  if (mappings[i].ofmt == defmapping.ofmt && mappings[i].ow == defmapping.ow &&
289  mappings[i].oh == defmapping.oh && mappings[i].ofps == defmapping.ofps)
290  { defidx = i; break; }
291  }
292 
293  // Now that everything is sorted, compute our UVC format and frame indices, those are 1-based, and frame is reset each
294  // time format changes. Note that all the intervals will be passed as a list to the USB host for a given format and
295  // frame combination, so all the mappings that have identical pixel format and frame size here receive the same
296  // uvcformat and uvcframe numbers. Note that we skip over all the NONE ofmt modes here:
297  unsigned int ofmt = ~0U, ow = ~0U, oh = ~0U, iformat = 0, iframe = 0;
298  for (jevois::VideoMapping & m : mappings)
299  {
300  if (m.ofmt == 0) { m.uvcformat = 0; m.uvcframe = 0; LDEBUG(m.str()); continue; }
301  if (m.ofmt != ofmt) { ofmt = m.ofmt; ow = ~0U; oh = ~0U; ++iformat; iframe = 0; } // Switch to the next format
302  if (m.ow != ow || m.oh != oh) { ow = m.ow; oh = m.oh; ++iframe; } // Switch to the next frame size
303  m.uvcformat = iformat; m.uvcframe = iframe;
304  LDEBUG(m.str());
305  }
306 
307  return mappings;
308 }
309 
310 // ####################################################################################################
311 bool jevois::VideoMapping::match(unsigned int oformat, unsigned int owidth, unsigned int oheight,
312  float oframespersec) const
313 {
314  if (ofmt == oformat && ow == owidth && oh == oheight && (std::abs(ofps - oframespersec) < 0.1F)) return true;
315  return false;
316 }
317 
#define LDEBUG(msg)
Convenience macro for users to print out console or syslog messages, DEBUG level. ...
Definition: Log.H:155
float ofps
output frame rate in frames/sec
Definition: VideoMapping.H:46
unsigned int uvcformat
USB-UVC format number (1-based)
Definition: VideoMapping.H:53
unsigned int osize() const
Return the size in bytes of an output image.
Definition: VideoMapping.C:37
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:113
static struct v4l2_fract fpsToV4l2(float fps)
Convert from fps to V4L2 interval.
Definition: VideoMapping.C:65
unsigned int ofmt
output pixel format, or 0 for no output over USB
Definition: VideoMapping.H:43
std::string cstr() const
Convenience function to print out FCC WxH @ fps, for the input (camera) format.
Definition: VideoMapping.C:79
std::string modulename
Name of the Module that will process this mapping.
Definition: VideoMapping.H:58
#define LERROR(msg)
Convenience macro for users to print out console or syslog messages, ERROR level. ...
Definition: Log.H:193
unsigned int ch
camera height
Definition: VideoMapping.H:50
static float uvcToFps(unsigned int interval)
Convert from USB/UVC interval to fps.
Definition: VideoMapping.C:45
std::string ostr() const
Convenience function to print out FCC WxH @ fps, for the output (UVC) format.
Definition: VideoMapping.C:71
Simple struct to hold video mapping definitions for the processing Engine.
Definition: VideoMapping.H:41
std::string sopath() const
Return the full absolute path and file name of the module's .so or .py file.
Definition: VideoMapping.C:30
std::string fccstr(unsigned int fcc)
Convert a V4L2 four-cc code (V4L2_PIX_FMT_...) to a 4-char string.
Definition: Utils.C:37
unsigned int uvcframe
USB UVC frame number (1-based)
Definition: VideoMapping.H:54
unsigned int oh
output height
Definition: VideoMapping.H:45
static float v4l2ToFps(struct v4l2_fract const &interval)
Convert from V4L2 interval to fps.
Definition: VideoMapping.C:58
#define LFATAL(msg)
Convenience macro for users to print out console or syslog messages, FATAL level. ...
Definition: Log.H:212
bool isSameAs(VideoMapping const &other) const
Equality operator for specs and also vendor or module name.
Definition: VideoMapping.C:106
std::string str() const
Convenience function to print out the whole mapping in a human-friendly way.
Definition: VideoMapping.C:87
unsigned int v4l2ImageSize(unsigned int fcc, unsigned int width, unsigned int height)
Return the image size in bytes for a given V4L2_PIX_FMT_..., width, height.
Definition: Utils.C:79
unsigned int cfmt
camera pixel format
Definition: VideoMapping.H:48
unsigned int cw
camera width
Definition: VideoMapping.H:49
#define PERROR(x)
Definition: VideoMapping.C:27
bool hasSameSpecsAs(VideoMapping const &other) const
Equality operator for specs but not vendor or module name.
Definition: VideoMapping.C:99
bool ispython
True if the module is written in Python; affects behavior of sopath() only.
Definition: VideoMapping.H:60
bool match(unsigned int oformat, unsigned int owidth, unsigned int oheight, float oframespersec) const
Return true if this VideoMapping's output format is a match to the given output parameters.
Definition: VideoMapping.C:311
static unsigned int fpsToUvc(float fps)
Convert from fps to USB/UVC interval.
Definition: VideoMapping.C:52
unsigned int ow
output width
Definition: VideoMapping.H:44
std::string vendor
Module creator name, used as a directory to organize the modules.
Definition: VideoMapping.H:56
unsigned int strfcc(std::string const &str)
Convert a JeVois video format string to V4L2 four-cc code (V4L2_PIX_FMT_...)
Definition: Utils.C:50
unsigned int csize() const
Return the size in bytes of a camera image.
Definition: VideoMapping.C:41
float cfps
camera frame rate in frames/sec
Definition: VideoMapping.H:51