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