ChunkedImageSequencePlaybackStrategy.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of ArmarX.
3  *
4  * ArmarX is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * ArmarX is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * @package visionx::imrec
17  * @author Christian R. G. Dreher <christian.dreher@student.kit.edu>
18  * @date 2018
19  * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20  * GNU General Public License
21  */
22 
23 
24 // Fallback FPS to be used if the FPS are not to be found in the metadata.csv (Must not be '0'!)
25 #define FALLBACK_FPS 30
26 
27 
29 
30 
31 // STD/STL
32 #include <cstring>
33 #include <filesystem>
34 #include <fstream>
35 
36 // Boost
37 #include <boost/algorithm/string/predicate.hpp>
38 #include <boost/lexical_cast.hpp>
39 #include <boost/tokenizer.hpp>
40 
41 // OpenCV 2
42 #include <opencv2/core/core.hpp>
43 #include <opencv2/highgui/highgui.hpp>
44 #include <opencv2/imgproc/imgproc.hpp>
45 
46 // ArmarX
49 
51 {
52  this->playingBack = false;
53  this->currentFrame = 0;
54  this->frameCount = 0;
55  this->metadataPath = "/dev/null";
56 }
57 
59  const std::filesystem::path& filePath)
60 {
61  this->playingBack = false;
62  this->currentFrame = 0;
63  this->frameCount = 0;
64  this->startPlayback(filePath);
65 }
66 
69 {
70  this->stopPlayback();
71 }
72 
73 bool
75 {
76  return this->playingBack;
77 }
78 
79 unsigned int
81 {
82  return this->fps;
83 }
84 
85 unsigned int
87 {
88  return this->frameCount;
89 }
90 
91 unsigned int
93 {
94  return this->frameHeight;
95 }
96 
97 unsigned int
99 {
100  return this->frameWidth;
101 }
102 
103 void
105 {
106  this->currentFrame = frame;
107 }
108 
109 unsigned int
111 {
112  return this->currentFrame;
113 }
114 
115 bool
117 {
118  return this->currentFrame < this->frameCount;
119 }
120 
121 static std::string
122 getMetadataVersion(std::map<std::string, std::tuple<std::string, std::string>> metadata)
123 {
124  if (auto it = metadata.find("metadata_version"); it != metadata.end())
125  {
126  ARMARX_CHECK_EQUAL(std::get<0>(it->second), "string");
127  return std::get<1>(it->second);
128  }
129  else
130  {
131  return "1";
132  }
133 }
134 
135 void
137  const std::filesystem::path& filePath)
138 {
139  // Find canonical path to metadata.csv given the parameter
140  if (std::filesystem::is_directory(filePath))
141  {
142  this->metadataPath = std::filesystem::canonical(filePath);
143  }
144  else
145  {
146  this->metadataPath = std::filesystem::canonical(filePath.parent_path());
147  }
148 
149  // Init metadata member from metadata.csv
150  this->initMetadata();
151 
152  // Init chunked image sequence recording related variables required to process the frames
153  this->initFps();
154  this->initExtension();
155  this->initFramesPerChunk();
156  this->initFrameCount();
157  this->initFrameHeight();
158  this->initFrameWidth();
159 
160  // Build path for each individual frame
161  std::stringstream errors;
162  for (unsigned int currentFrameNumber = 0; currentFrameNumber < this->frameCount;
163  ++currentFrameNumber)
164  {
165  const unsigned int currentChunkNumber = currentFrameNumber / framesPerChunk;
166  const std::filesystem::path currentChunk("chunk_" + std::to_string(currentChunkNumber));
167 
168  const std::string metadataVersion = getMetadataVersion(metadata);
169  std::filesystem::path frameFile = "";
170  if (metadataVersion == "1")
171  {
172  frameFile = std::filesystem::path("frame_" + std::to_string(currentFrameNumber));
173  }
174  else
175  {
176  const std::string frameName = "frame_" + std::to_string(currentFrameNumber);
177  if (auto it = this->metadata.find(frameName); it != this->metadata.end())
178  {
179  frameFile = std::filesystem::path(std::get<1>(it->second));
180  }
181  else
182  {
183  errors << "\nFilename for frame " << currentFrameNumber << " in chunk "
184  << currentChunkNumber << " is missing!";
185  }
186  }
187  if (not frameFile.empty())
188  {
189  frameFile = frameFile.replace_extension(this->extension);
190  const std::filesystem::path currentFramePath =
191  std::filesystem::canonical(this->metadataPath / currentChunk / frameFile);
192 
193  this->framePaths.push_back(currentFramePath);
194  }
195  }
196  if (not errors.str().empty())
197  {
198  ARMARX_ERROR << "The following errors occured when starting the playback: " << errors.str();
199  }
200 
201  this->playingBack = true;
202 }
203 
204 bool
206 {
207  // Return failure on invalid frame
208  if (this->currentFrame >= this->frameCount)
209  {
210  return false;
211  }
212 
213  // Load frame with OpenCV
214  cv::Mat frame;
215  this->getNextFrame(frame);
216 
217  // Convert to RGB and write data to buffer
218  cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
219  std::memcpy(buffer,
220  frame.data,
221  this->frameHeight * this->frameWidth * static_cast<unsigned int>(frame.channels()));
222 
223  // Return success
224  return true;
225 }
226 
227 bool
229 {
230  return this->getNextFrame(buffer.pixels);
231 }
232 
233 bool
235 {
236  const std::string path = this->framePaths[this->currentFrame++].string();
237  buffer = cv::imread(path);
238 
239  // Return failure if OpenCV encountered an error
240  if (buffer.data == nullptr)
241  {
242  return false;
243  }
244 
245  // Check if the dimensions fit the expected image size
246  if (static_cast<unsigned int>(buffer.size().height) != this->frameHeight or
247  static_cast<unsigned int>(buffer.size().width) != this->frameWidth)
248  {
249  return false;
250  }
251 
252  return true;
253 }
254 
255 void
257 {
258  this->playingBack = false;
259 }
260 
261 void
262 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initMetadata()
263 {
264  // Read contents
265  std::ifstream metadatacsv((this->metadataPath / "metadata.csv").string());
266  std::string line;
267  while (std::getline(metadatacsv, line))
268  {
269  boost::tokenizer<boost::escaped_list_separator<char>> tokenizer(line);
270  std::vector<std::string> tokens(tokenizer.begin(), tokenizer.end());
271 
272  const std::string varName = tokens[0];
273  const std::string varType = tokens[1];
274  const std::string varValue = tokens[2];
275 
276  this->metadata[varName] = std::make_tuple(varType, varValue);
277  }
278 }
279 
280 void
281 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFps()
282 {
283  unsigned int candidate = 0;
284 
285  if (auto it = this->metadata.find("fps"); it != this->metadata.end())
286  {
287  candidate = this->fps = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
288 
289  ARMARX_CHECK_GREATER(candidate, 0) << "FPS cannot be '0'";
290  }
291  else
292  {
293  // This is not worth a warning.
294  ARMARX_INFO << "No 'fps' entry found in the metadata.csv. Assuming '" << FALLBACK_FPS
295  << "' as default. "
296  << "FPS cannot be derived from an image sequence and are expected to be "
297  "manifested in the metadata.csv file. "
298  << "If the assumed value does not reflect the actual FPS, update the 'fps' "
299  "variable in the metadata.csv";
300 
301  candidate = FALLBACK_FPS; // unchecked, but must not be 0
302  this->updateMetadata("fps", "unsigned int", std::to_string(candidate));
303  }
304 
305  this->fps = candidate;
306 }
307 
308 void
309 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initExtension()
310 {
311  std::string candidate = "";
312 
313  if (auto it = this->metadata.find("extension"); it != this->metadata.end())
314  {
315  candidate = std::get<1>(it->second);
316 
317  ARMARX_CHECK_NOT_EQUAL(candidate, "") << "File extension cannot be empty";
318  }
319  else
320  {
322  << "No 'extension' entry found in the metadata.csv. Trying to derive the value now...";
323 
324  const std::filesystem::path probeChunk = this->metadataPath / "chunk_0";
325 
326  for (const std::filesystem::directory_entry& de :
327  boost::make_iterator_range(std::filesystem::directory_iterator(probeChunk), {}))
328  {
329  const std::string currentStem = de.path().stem().string();
330  const std::string currentExtension = de.path().extension().string();
331  const std::string frameTemplate = "frame_";
332 
333  if (boost::algorithm::starts_with(currentStem, frameTemplate) and
334  boost::algorithm::starts_with(currentExtension, ".") and currentExtension != ".")
335  {
336  candidate = currentExtension;
337  break;
338  }
339  }
340 
341  ARMARX_CHECK_NOT_EQUAL(candidate, "")
342  << "Could not determine the file extension. Update the metadata.csv file manually for "
343  "the variable "
344  "'extension' (type 'string') with the corresponding value";
345 
346  ARMARX_INFO << "Determined 'extension' to be '" << candidate << "'";
347 
348  this->updateMetadata("extension", "string", candidate);
349  }
350 
351  this->extension = candidate;
352 }
353 
354 void
355 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFramesPerChunk()
356 {
357  unsigned int candidate = 0;
358 
359  if (auto it = this->metadata.find("frames_per_chunk"); it != this->metadata.end())
360  {
361  candidate = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
362 
363  ARMARX_CHECK_GREATER(candidate, 0) << "Amount of frames per chunk cannot be '0'";
364  }
365  else
366  {
367  ARMARX_WARNING << "No 'frames_per_chunk' entry found in the metadata.csv. Trying to derive "
368  "the value now...";
369 
370  candidate = 0;
371  std::filesystem::path file =
372  std::filesystem::path("frame_0").replace_extension(this->extension);
373  while (std::filesystem::exists(this->metadataPath / "chunk_0" / file))
374  {
375  file = std::filesystem::path("frame_" + std::to_string(++candidate))
376  .replace_extension(this->extension);
377  }
378 
379  ARMARX_CHECK_GREATER(candidate, 0)
380  << "Could not determine the amount of frames per chunk. Update the metadata.csv file "
381  "manually for the variable "
382  "'frames_per_chunk' (type 'unsigned int') with the corresponding value.";
383 
384  ARMARX_INFO << "Determined 'frames_per_chunk' to be '" << candidate << "'";
385 
386  this->updateMetadata("frames_per_chunk", "unsigned int", std::to_string(candidate));
387  }
388 
389  this->framesPerChunk = candidate;
390 }
391 
392 void
393 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFrameCount()
394 {
395  unsigned int candidate = 0;
396 
397  if (auto it = this->metadata.find("frame_count"); it != this->metadata.end())
398  {
399  candidate = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
400 
401  ARMARX_CHECK_GREATER(candidate, 0) << "Amount of frames cannot be '0'";
402  }
403  else
404  {
405  ARMARX_WARNING << "No 'frame_count' entry found in the metadata.csv. Trying to derive the "
406  "value now...";
407 
408  candidate = 0;
409  std::filesystem::path chunk = "chunk_0";
410  unsigned int chunkNumber = 0;
411  std::filesystem::path file =
412  std::filesystem::path("frame_0").replace_extension(this->extension);
413  while (std::filesystem::exists(this->metadataPath / chunk))
414  {
415  while (std::filesystem::exists(this->metadataPath / chunk / file))
416  {
417  file = std::filesystem::path("frame_" + std::to_string(++candidate))
418  .replace_extension(this->extension);
419  }
420 
421  chunk = "chunk_" + std::to_string(++chunkNumber);
422  }
423 
424  ARMARX_CHECK_GREATER(candidate, 0)
425  << "Could not determine the amount of frames. Update the metadata.csv file manually "
426  "for the variable "
427  "'frame_count' (type 'unsigned int') with the corresponding value";
428 
429  ARMARX_INFO << "Determined 'frame_count' to be '" << candidate << "'";
430 
431  this->updateMetadata("frame_count", "unsigned int", std::to_string(candidate));
432  }
433 
434  this->frameCount = candidate;
435 }
436 
437 void
438 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFrameHeight()
439 {
440  unsigned int candidate = 0;
441 
442  if (auto it = this->metadata.find("frame_height"); it != this->metadata.end())
443  {
444  candidate = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
445 
446  ARMARX_CHECK_GREATER(candidate, 0) << "Frame height cannot be '0'";
447  }
448  else
449  {
450  ARMARX_WARNING << "No 'frame_height' entry found in the metadata.csv. Trying to derive the "
451  "value now...";
452 
453  const std::filesystem::path chunk = "chunk_0";
454  const std::filesystem::path frame =
455  std::filesystem::path("frame_0").replace_extension(this->extension);
456  const std::filesystem::path probe = this->metadataPath / chunk / frame;
457  cv::Mat frameImage = cv::imread(probe.string());
458 
459  ARMARX_CHECK_NOT_NULL(frameImage.data)
460  << "Failed loading first frame. Image file corrupted?";
461 
462  candidate = static_cast<unsigned int>(frameImage.size().height);
463 
464  ARMARX_CHECK_GREATER(candidate, 0)
465  << "Could not determine the frame height. Update the metadata.csv file manually for "
466  "the variable "
467  "'frame_height' (type 'unsigned int') with the corresponding value";
468 
469  ARMARX_INFO << "Determined 'frame_height' to be '" << candidate << "'";
470 
471  this->updateMetadata("frame_height", "unsigned int", std::to_string(candidate));
472  }
473 
474  this->frameHeight = candidate;
475 }
476 
477 void
478 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFrameWidth()
479 {
480  unsigned int candidate = 0;
481 
482  if (auto it = this->metadata.find("frame_width"); it != this->metadata.end())
483  {
484  candidate = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
485 
486  ARMARX_CHECK_GREATER(candidate, 0) << "Frame width cannot be '0'";
487  }
488  else
489  {
490  ARMARX_WARNING << "No 'frame_width' entry found in the metadata.csv. Trying to derive the "
491  "value now...";
492 
493  const std::filesystem::path chunk = "chunk_0";
494  const std::filesystem::path frame =
495  std::filesystem::path("frame_0").replace_extension(this->extension);
496  const std::filesystem::path probe = this->metadataPath / chunk / frame;
497  cv::Mat frameImage = cv::imread(probe.string());
498 
499  ARMARX_CHECK_NOT_NULL(frameImage.data)
500  << "Failed loading first frame. Image file corrupted?";
501 
502  candidate = static_cast<unsigned int>(frameImage.size().width);
503 
504  ARMARX_CHECK_GREATER(candidate, 0)
505  << "Could not determine the frame width. Update the metadata.csv file manually for the "
506  "variable "
507  "'frame_width' (type 'unsigned int') with the corresponding value";
508 
509  ARMARX_INFO << "Determined 'frame_width' to be '" << candidate << "'";
510 
511  this->updateMetadata("frame_width", "unsigned int", std::to_string(candidate));
512  }
513 
514  this->frameWidth = candidate;
515 }
516 
517 void
518 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::updateMetadata(
519  const std::string& varName,
520  const std::string& varType,
521  const std::string& varValue)
522 {
523  if (this->metadata.count(varName) == 0)
524  {
525  ARMARX_INFO << "Updating metadata.csv file with missing variable '" << varName
526  << "' (type '" << varType << "') with value '" << varValue << "'";
527 
528  std::ofstream metadatacsv((this->metadataPath / "metadata.csv").string(),
529  std::ios::out | std::ios::app);
530  metadatacsv << varName << "," << varType << "," << varValue << "\n";
531  }
532 }
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getCurrentFrame
virtual unsigned int getCurrentFrame() const override
Gets the current frame index of the playback.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:110
FALLBACK_FPS
#define FALLBACK_FPS
Definition: ChunkedImageSequencePlaybackStrategy.cpp:25
ARMARX_CHECK_NOT_EQUAL
#define ARMARX_CHECK_NOT_EQUAL(lhs, rhs)
This macro evaluates whether lhs is inequal (!=) rhs and if it turns out to be false it will throw an...
Definition: ExpressionException.h:137
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getFrameHeight
virtual unsigned int getFrameHeight() const override
Gets the height of a frame in pixel.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:92
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::stopPlayback
virtual void stopPlayback() override
Stops the playback.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:256
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::~ChunkedImageSequencePlaybackStrategy
virtual ~ChunkedImageSequencePlaybackStrategy() override
Destructor.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:68
ARMARX_CHECK_NOT_NULL
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
Definition: ExpressionException.h:206
ARMARX_CHECK_GREATER
#define ARMARX_CHECK_GREATER(lhs, rhs)
This macro evaluates whether lhs is greater (>) than rhs and if it turns out to be false it will thro...
Definition: ExpressionException.h:116
armarx::starts_with
bool starts_with(const std::string &haystack, const std::string &needle)
Definition: StringHelpers.cpp:47
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getNextFrame
virtual bool getNextFrame(void *buffer) override
Writes the next frame into a buffer of any form (RGB)
Definition: ChunkedImageSequencePlaybackStrategy.cpp:205
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::isPlayingBack
virtual bool isPlayingBack() const override
Indicates whether the instance is configured to be able to play back.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:74
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::ChunkedImageSequencePlaybackStrategy
ChunkedImageSequencePlaybackStrategy()
Default constructor to manually setup later.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:50
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::hasNextFrame
virtual bool hasNextFrame() const override
Indicates whether the recording has a consecutive frame.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:116
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getFps
virtual unsigned int getFps() const override
Gets the amount of frames per second of the recording.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:80
ARMARX_ERROR
#define ARMARX_ERROR
Definition: Logging.h:196
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::setCurrentFrame
virtual void setCurrentFrame(unsigned int frame) override
Sets the frame from there the playback should resume afterwards (seek)
Definition: ChunkedImageSequencePlaybackStrategy.cpp:104
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getFrameWidth
virtual unsigned int getFrameWidth() const override
Gets the width of a frame in pixel.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:98
armarx::to_string
const std::string & to_string(const std::string &s)
Definition: StringHelpers.h:41
ExpressionException.h
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::startPlayback
virtual void startPlayback(const std::filesystem::path &filePath) override
Starts the playback.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:136
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:181
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getFrameCount
virtual unsigned int getFrameCount() const override
Gets the total amout of frames in the recording.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:86
Logging.h
ARMARX_CHECK_EQUAL
#define ARMARX_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
Definition: ExpressionException.h:130
ARMARX_WARNING
#define ARMARX_WARNING
Definition: Logging.h:193
ChunkedImageSequencePlaybackStrategy.h