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 
50 
52 {
53  this->playingBack = false;
54  this->currentFrame = 0;
55  this->frameCount = 0;
56  this->metadataPath = "/dev/null";
57 }
58 
59 
61 {
62  this->playingBack = false;
63  this->currentFrame = 0;
64  this->frameCount = 0;
65  this->startPlayback(filePath);
66 }
67 
68 
70 {
71  this->stopPlayback();
72 }
73 
74 
75 bool
77 {
78  return this->playingBack;
79 }
80 
81 
82 unsigned int
84 {
85  return this->fps;
86 }
87 
88 
89 unsigned int
91 {
92  return this->frameCount;
93 }
94 
95 
96 unsigned int
98 {
99  return this->frameHeight;
100 }
101 
102 
103 unsigned int
105 {
106  return this->frameWidth;
107 }
108 
109 
110 void
112 {
113  this->currentFrame = frame;
114 }
115 
116 
117 unsigned int
119 {
120  return this->currentFrame;
121 }
122 
123 
124 bool
126 {
127  return this->currentFrame < this->frameCount;
128 }
129 
130 static
131 std::string
132 getMetadataVersion(std::map<std::string, std::tuple<std::string, std::string>> metadata)
133 {
134  if (auto it = metadata.find("metadata_version"); it != metadata.end())
135  {
136  ARMARX_CHECK_EQUAL(std::get<0>(it->second), "string");
137  return std::get<1>(it->second);
138  }
139  else
140  {
141  return "1";
142  }
143 }
144 
145 
146 void
148 {
149  // Find canonical path to metadata.csv given the parameter
150  if (std::filesystem::is_directory(filePath))
151  {
152  this->metadataPath = std::filesystem::canonical(filePath);
153  }
154  else
155  {
156  this->metadataPath = std::filesystem::canonical(filePath.parent_path());
157  }
158 
159  // Init metadata member from metadata.csv
160  this->initMetadata();
161 
162  // Init chunked image sequence recording related variables required to process the frames
163  this->initFps();
164  this->initExtension();
165  this->initFramesPerChunk();
166  this->initFrameCount();
167  this->initFrameHeight();
168  this->initFrameWidth();
169 
170  // Build path for each individual frame
171  std::stringstream errors;
172  for (unsigned int currentFrameNumber = 0; currentFrameNumber < this->frameCount; ++currentFrameNumber)
173  {
174  const unsigned int currentChunkNumber = currentFrameNumber / framesPerChunk;
175  const std::filesystem::path currentChunk("chunk_" + std::to_string(currentChunkNumber));
176 
177  const std::string metadataVersion = getMetadataVersion(metadata);
178  std::filesystem::path frameFile = "";
179  if (metadataVersion == "1")
180  {
181  frameFile = std::filesystem::path("frame_" + std::to_string(currentFrameNumber));
182  }
183  else
184  {
185  const std::string frameName = "frame_" + std::to_string(currentFrameNumber);
186  if (auto it = this->metadata.find(frameName); it != this->metadata.end())
187  {
188  frameFile = std::filesystem::path(std::get<1>(it->second));
189  }
190  else
191  {
192  errors << "\nFilename for frame " << currentFrameNumber << " in chunk " << currentChunkNumber << " is missing!";
193  }
194  }
195  if (not frameFile.empty())
196  {
197  frameFile = frameFile.replace_extension(this->extension);
198  const std::filesystem::path currentFramePath =
199  std::filesystem::canonical(this->metadataPath / currentChunk / frameFile);
200 
201  this->framePaths.push_back(currentFramePath);
202  }
203  }
204  if (not errors.str().empty())
205  {
206  ARMARX_ERROR << "The following errors occured when starting the playback: " << errors.str();
207  }
208 
209  this->playingBack = true;
210 }
211 
212 
213 bool
215 {
216  // Return failure on invalid frame
217  if (this->currentFrame >= this->frameCount)
218  {
219  return false;
220  }
221 
222  // Load frame with OpenCV
223  cv::Mat frame;
224  this->getNextFrame(frame);
225 
226  // Convert to RGB and write data to buffer
227  cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
228  std::memcpy(buffer, frame.data, this->frameHeight * this->frameWidth * static_cast<unsigned int>(frame.channels()));
229 
230  // Return success
231  return true;
232 }
233 
234 
235 bool
237 {
238  return this->getNextFrame(buffer.pixels);
239 }
240 
241 
242 bool
244 {
245  const std::string path = this->framePaths[this->currentFrame++].string();
246  buffer = cv::imread(path);
247 
248  // Return failure if OpenCV encountered an error
249  if (buffer.data == nullptr)
250  {
251  return false;
252  }
253 
254  // Check if the dimensions fit the expected image size
255  if (static_cast<unsigned int>(buffer.size().height) != this->frameHeight
256  or static_cast<unsigned int>(buffer.size().width) != this->frameWidth)
257  {
258  return false;
259  }
260 
261  return true;
262 }
263 
264 
265 void
267 {
268  this->playingBack = false;
269 }
270 
271 
272 void
273 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initMetadata()
274 {
275  // Read contents
276  std::ifstream metadatacsv((this->metadataPath / "metadata.csv").string());
277  std::string line;
278  while (std::getline(metadatacsv, line))
279  {
280  boost::tokenizer<boost::escaped_list_separator<char>> tokenizer(line);
281  std::vector<std::string> tokens(tokenizer.begin(), tokenizer.end());
282 
283  const std::string varName = tokens[0];
284  const std::string varType = tokens[1];
285  const std::string varValue = tokens[2];
286 
287  this->metadata[varName] = std::make_tuple(varType, varValue);
288  }
289 }
290 
291 
292 void
293 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFps()
294 {
295  unsigned int candidate = 0;
296 
297  if (auto it = this->metadata.find("fps"); it != this->metadata.end())
298  {
299  candidate = this->fps = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
300 
301  ARMARX_CHECK_GREATER(candidate, 0) << "FPS cannot be '0'";
302  }
303  else
304  {
305  // This is not worth a warning.
306  ARMARX_INFO << "No 'fps' entry found in the metadata.csv. Assuming '" << FALLBACK_FPS << "' as default. "
307  << "FPS cannot be derived from an image sequence and are expected to be manifested in the metadata.csv file. "
308  << "If the assumed value does not reflect the actual FPS, update the 'fps' variable in the metadata.csv";
309 
310  candidate = FALLBACK_FPS; // unchecked, but must not be 0
311  this->updateMetadata("fps", "unsigned int", std::to_string(candidate));
312  }
313 
314  this->fps = candidate;
315 }
316 
317 
318 void
319 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initExtension()
320 {
321  std::string candidate = "";
322 
323  if (auto it = this->metadata.find("extension"); it != this->metadata.end())
324  {
325  candidate = std::get<1>(it->second);
326 
327  ARMARX_CHECK_NOT_EQUAL(candidate, "") << "File extension cannot be empty";
328  }
329  else
330  {
331  ARMARX_WARNING << "No 'extension' entry found in the metadata.csv. Trying to derive the value now...";
332 
333  const std::filesystem::path probeChunk = this->metadataPath / "chunk_0";
334 
335  for (const std::filesystem::directory_entry& de : boost::make_iterator_range(std::filesystem::directory_iterator(probeChunk), {}))
336  {
337  const std::string currentStem = de.path().stem().string();
338  const std::string currentExtension = de.path().extension().string();
339  const std::string frameTemplate = "frame_";
340 
341  if (boost::algorithm::starts_with(currentStem, frameTemplate) and boost::algorithm::starts_with(currentExtension, ".") and currentExtension != ".")
342  {
343  candidate = currentExtension;
344  break;
345  }
346  }
347 
348  ARMARX_CHECK_NOT_EQUAL(candidate, "") << "Could not determine the file extension. Update the metadata.csv file manually for the variable "
349  "'extension' (type 'string') with the corresponding value";
350 
351  ARMARX_INFO << "Determined 'extension' to be '" << candidate << "'";
352 
353  this->updateMetadata("extension", "string", candidate);
354  }
355 
356  this->extension = candidate;
357 }
358 
359 
360 void
361 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFramesPerChunk()
362 {
363  unsigned int candidate = 0;
364 
365  if (auto it = this->metadata.find("frames_per_chunk"); it != this->metadata.end())
366  {
367  candidate = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
368 
369  ARMARX_CHECK_GREATER(candidate, 0) << "Amount of frames per chunk cannot be '0'";
370  }
371  else
372  {
373  ARMARX_WARNING << "No 'frames_per_chunk' entry found in the metadata.csv. Trying to derive the value now...";
374 
375  candidate = 0;
376  std::filesystem::path file = std::filesystem::path("frame_0").replace_extension(this->extension);
377  while (std::filesystem::exists(this->metadataPath / "chunk_0" / file))
378  {
379  file = std::filesystem::path("frame_" + std::to_string(++candidate)).replace_extension(this->extension);
380  }
381 
382  ARMARX_CHECK_GREATER(candidate, 0) << "Could not determine the amount of frames per chunk. Update the metadata.csv file manually for the variable "
383  "'frames_per_chunk' (type 'unsigned int') with the corresponding value.";
384 
385  ARMARX_INFO << "Determined 'frames_per_chunk' to be '" << candidate << "'";
386 
387  this->updateMetadata("frames_per_chunk", "unsigned int", std::to_string(candidate));
388  }
389 
390  this->framesPerChunk = candidate;
391 }
392 
393 
394 void
395 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFrameCount()
396 {
397  unsigned int candidate = 0;
398 
399  if (auto it = this->metadata.find("frame_count"); it != this->metadata.end())
400  {
401  candidate = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
402 
403  ARMARX_CHECK_GREATER(candidate, 0) << "Amount of frames cannot be '0'";
404  }
405  else
406  {
407  ARMARX_WARNING << "No 'frame_count' entry found in the metadata.csv. Trying to derive the value now...";
408 
409  candidate = 0;
410  std::filesystem::path chunk = "chunk_0";
411  unsigned int chunkNumber = 0;
412  std::filesystem::path file = 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)).replace_extension(this->extension);
418  }
419 
420  chunk = "chunk_" + std::to_string(++chunkNumber);
421  }
422 
423  ARMARX_CHECK_GREATER(candidate, 0) << "Could not determine the amount of frames. Update the metadata.csv file manually for the variable "
424  "'frame_count' (type 'unsigned int') with the corresponding value";
425 
426  ARMARX_INFO << "Determined 'frame_count' to be '" << candidate << "'";
427 
428  this->updateMetadata("frame_count", "unsigned int", std::to_string(candidate));
429  }
430 
431  this->frameCount = candidate;
432 }
433 
434 
435 void
436 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFrameHeight()
437 {
438  unsigned int candidate = 0;
439 
440  if (auto it = this->metadata.find("frame_height"); it != this->metadata.end())
441  {
442  candidate = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
443 
444  ARMARX_CHECK_GREATER(candidate, 0) << "Frame height cannot be '0'";
445  }
446  else
447  {
448  ARMARX_WARNING << "No 'frame_height' entry found in the metadata.csv. Trying to derive the value now...";
449 
450  const std::filesystem::path chunk = "chunk_0";
451  const std::filesystem::path frame = std::filesystem::path("frame_0").replace_extension(this->extension);
452  const std::filesystem::path probe = this->metadataPath / chunk / frame;
453  cv::Mat frameImage = cv::imread(probe.string());
454 
455  ARMARX_CHECK_NOT_NULL(frameImage.data) << "Failed loading first frame. Image file corrupted?";
456 
457  candidate = static_cast<unsigned int>(frameImage.size().height);
458 
459  ARMARX_CHECK_GREATER(candidate, 0) << "Could not determine the frame height. Update the metadata.csv file manually for the variable "
460  "'frame_height' (type 'unsigned int') with the corresponding value";
461 
462  ARMARX_INFO << "Determined 'frame_height' to be '" << candidate << "'";
463 
464  this->updateMetadata("frame_height", "unsigned int", std::to_string(candidate));
465  }
466 
467  this->frameHeight = candidate;
468 }
469 
470 
471 void
472 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::initFrameWidth()
473 {
474  unsigned int candidate = 0;
475 
476  if (auto it = this->metadata.find("frame_width"); it != this->metadata.end())
477  {
478  candidate = boost::lexical_cast<unsigned int>(std::get<1>(it->second));
479 
480  ARMARX_CHECK_GREATER(candidate, 0) << "Frame width cannot be '0'";
481  }
482  else
483  {
484  ARMARX_WARNING << "No 'frame_width' entry found in the metadata.csv. Trying to derive the value now...";
485 
486  const std::filesystem::path chunk = "chunk_0";
487  const std::filesystem::path frame = std::filesystem::path("frame_0").replace_extension(this->extension);
488  const std::filesystem::path probe = this->metadataPath / chunk / frame;
489  cv::Mat frameImage = cv::imread(probe.string());
490 
491  ARMARX_CHECK_NOT_NULL(frameImage.data) << "Failed loading first frame. Image file corrupted?";
492 
493  candidate = static_cast<unsigned int>(frameImage.size().width);
494 
495  ARMARX_CHECK_GREATER(candidate, 0) << "Could not determine the frame width. Update the metadata.csv file manually for the variable "
496  "'frame_width' (type 'unsigned int') with the corresponding value";
497 
498  ARMARX_INFO << "Determined 'frame_width' to be '" << candidate << "'";
499 
500  this->updateMetadata("frame_width", "unsigned int", std::to_string(candidate));
501  }
502 
503  this->frameWidth = candidate;
504 }
505 
506 
507 void
508 visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::updateMetadata(
509  const std::string& varName, const std::string& varType, const std::string& varValue)
510 {
511  if (this->metadata.count(varName) == 0)
512  {
513  ARMARX_INFO << "Updating metadata.csv file with missing variable '" << varName << "' (type '" << varType << "') with value '" << varValue << "'";
514 
515  std::ofstream metadatacsv((this->metadataPath / "metadata.csv").string(), std::ios::out | std::ios::app);
516  metadatacsv << varName << "," << varType << "," << varValue << "\n";
517  }
518 }
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getCurrentFrame
virtual unsigned int getCurrentFrame() const override
Gets the current frame index of the playback.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:118
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:97
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::stopPlayback
virtual void stopPlayback() override
Stops the playback.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:266
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::~ChunkedImageSequencePlaybackStrategy
virtual ~ChunkedImageSequencePlaybackStrategy() override
Destructor.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:69
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:43
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:214
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:76
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::ChunkedImageSequencePlaybackStrategy
ChunkedImageSequencePlaybackStrategy()
Default constructor to manually setup later.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:51
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::hasNextFrame
virtual bool hasNextFrame() const override
Indicates whether the recording has a consecutive frame.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:125
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getFps
virtual unsigned int getFps() const override
Gets the amount of frames per second of the recording.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:83
ARMARX_ERROR
#define ARMARX_ERROR
Definition: Logging.h:189
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:111
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getFrameWidth
virtual unsigned int getFrameWidth() const override
Gets the width of a frame in pixel.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:104
armarx::to_string
const std::string & to_string(const std::string &s)
Definition: StringHelpers.h:40
ExpressionException.h
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::startPlayback
virtual void startPlayback(const std::filesystem::path &filePath) override
Starts the playback.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:147
ARMARX_INFO
#define ARMARX_INFO
Definition: Logging.h:174
visionx::imrec::strats::ChunkedImageSequencePlaybackStrategy::getFrameCount
virtual unsigned int getFrameCount() const override
Gets the total amout of frames in the recording.
Definition: ChunkedImageSequencePlaybackStrategy.cpp:90
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:186
ChunkedImageSequencePlaybackStrategy.h