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
72
73bool
78
79unsigned int
84
85unsigned int
90
91unsigned int
96
97unsigned int
102
103void
105{
106 this->currentFrame = frame;
107}
108
109unsigned int
114
115bool
117{
118 return this->currentFrame < this->frameCount;
119}
120
121static std::string
122getMetadataVersion(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
135void
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
204bool
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
227bool
229{
230 return this->getNextFrame(buffer.pixels);
231}
232
233bool
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
255void
260
261void
262visionx::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
280void
281visionx::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
308void
309visionx::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
354void
355visionx::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
392void
393visionx::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
437void
438visionx::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
477void
478visionx::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
517void
518visionx::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}
virtual void setCurrentFrame(unsigned int frame) override
Sets the frame from there the playback should resume afterwards (seek)
virtual unsigned int getFrameWidth() const override
Gets the width of a frame in pixel.
virtual unsigned int getFps() const override
Gets the amount of frames per second of the recording.
virtual bool hasNextFrame() const override
Indicates whether the recording has a consecutive frame.
virtual unsigned int getFrameCount() const override
Gets the total amout of frames in the recording.
virtual unsigned int getCurrentFrame() const override
Gets the current frame index of the playback.
virtual bool getNextFrame(void *buffer) override
Writes the next frame into a buffer of any form (RGB)
virtual void startPlayback(const std::filesystem::path &filePath) override
Starts the playback.
virtual bool isPlayingBack() const override
Indicates whether the instance is configured to be able to play back.
virtual unsigned int getFrameHeight() const override
Gets the height of a frame in pixel.
#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...
#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...
#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...
#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...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193