ImageSequencePlaybackStrategy.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 // Default FPS to return
25 #define DEFAULT_FPS 30
26 
27 
29 
30 
31 // STD/STL
32 #include <algorithm>
33 #include <filesystem>
34 
35 // OpenCV
36 #include <opencv2/core/core.hpp>
37 #include <opencv2/highgui/highgui.hpp>
38 #include <opencv2/imgproc/imgproc.hpp>
39 
40 // ArmarX
41 #include <SimoxUtility/algorithm/string/string_tools.h>
42 
45 
47 {
48  this->playingBack = false;
49  this->currentFrame = 0;
50  this->frameCount = 0;
51 }
52 
54  const std::filesystem::path& filePath)
55 {
56  this->playingBack = false;
57  this->currentFrame = 0;
58  this->frameCount = 0;
59  this->startPlayback(filePath);
60 }
61 
63 {
64  this->stopPlayback();
65 }
66 
67 bool
69 {
70  return this->playingBack;
71 }
72 
73 unsigned int
75 {
77  << "FPS cannot be reconstructed from a bare image sequence. Using a pre-set default of "
78  << DEFAULT_FPS << " FPS instead";
79 
80  return DEFAULT_FPS;
81 }
82 
83 unsigned int
85 {
86  return this->frameCount;
87 }
88 
89 unsigned int
91 {
92  return this->frameHeight;
93 }
94 
95 unsigned int
97 {
98  return this->frameWidth;
99 }
100 
101 void
103 {
104  this->currentFrame = frame;
105 }
106 
107 unsigned int
109 {
110  return this->currentFrame;
111 }
112 
113 bool
115 {
116  return this->currentFrame < this->frameCount;
117 }
118 
119 void
121  const std::filesystem::path& filePath)
122 {
123  this->initBasePathPrefixSuffix(filePath);
124  this->initFramePaths();
125  this->initFrameDimensions();
126 
127  this->frameCount = static_cast<unsigned int>(this->framePaths.size());
128 
129  this->playingBack = true;
130 }
131 
132 bool
134 {
135  // Return failure on invalid frame
136  if (this->currentFrame >= this->frameCount)
137  {
138  return false;
139  }
140 
141  // Load frame with OpenCV
142  cv::Mat frame;
143  if (!this->getNextFrame(frame))
144  {
145  return false;
146  }
147 
148  // Convert to RGB and write data to buffer
149  cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB);
150  std::memcpy(buffer,
151  frame.data,
152  this->frameHeight * this->frameWidth * static_cast<unsigned int>(frame.channels()));
153 
154  // Return success
155  return true;
156 }
157 
158 bool
160 {
161  return this->getNextFrame(buffer.pixels);
162 }
163 
164 bool
166 {
167  buffer = cv::imread(this->framePaths[this->currentFrame++].string());
168 
169  // Return failure if OpenCV encountered an error
170  if (buffer.data == nullptr)
171  {
172  return false;
173  }
174 
175  // Check if the dimensions fit the expected image size
176  if (static_cast<unsigned int>(buffer.size().height) != this->frameHeight or
177  static_cast<unsigned int>(buffer.size().width) != this->frameWidth)
178  {
179  return false;
180  }
181 
182  return true;
183 }
184 
185 void
187 {
188  this->playingBack = false;
189 }
190 
191 void
192 visionx::imrec::strats::ImageSequencePlaybackStrategy::initBasePathPrefixSuffix(
193  const std::filesystem::path& filePath)
194 {
195  // Define helper functions to find a common prefix or suffix between two strings respectively
196  auto findCommonPrefix = [](const std::string& s1, const std::string& s2) -> std::string
197  {
198  std::string commonPrefix = "";
199 
200  for (unsigned int i = 0; i < s1.length() and i < s2.length(); ++i)
201  {
202  if (s1[i] == s2[i])
203  {
204  commonPrefix += s1[i];
205  }
206  else
207  {
208  break;
209  }
210  }
211 
212  return commonPrefix;
213  };
214  auto findCommonSuffix = [](const std::string& s1, const std::string& s2) -> std::string
215  {
216  std::string commonSuffix = "";
217 
218  for (unsigned int i = 1; i <= s1.length() and i <= s2.length(); ++i)
219  {
220  if (s1[s1.length() - i] == s2[s2.length() - i])
221  {
222  commonSuffix = s1[s1.length() - i] + commonSuffix;
223  }
224  else
225  {
226  break;
227  }
228  }
229 
230  return commonSuffix;
231  };
232 
233  // Individual frames for the image sequence will be identified by a requried prefix and suffix, and they have to be in a common folder
234  std::filesystem::path basePath;
235  std::string requiredPrefix = "";
236  std::string requiredSuffix = "";
237 
238  // If a directory was given, assume each contained file is a frame
239  if (std::filesystem::is_directory(filePath))
240  {
241  // Set base path
242  basePath = std::filesystem::canonical(filePath);
243 
244  // Initialise candidates for the common prefix and suffix
245  std::string prefixCandidate = "";
246  std::string suffixCandidate = "";
247  bool candidatesInitialised = false;
248 
249  // Iterate over dictionary and narrow down the common prefix and suffix
250  for (auto de = std::filesystem::directory_iterator(basePath);
251  de != std::filesystem::directory_iterator{};
252  ++de)
253  {
254  const std::string currentFile = de->path().filename().string();
255 
256  // If prefixCandidate and suffixCandidate are uninitialised, use currentFile as pivot.
257  // This is legit because we assume that each file in the folder is a frame if only a folder path was given as parameter
258  if (!candidatesInitialised)
259  {
260  prefixCandidate = currentFile;
261  suffixCandidate = currentFile;
262  candidatesInitialised = true;
263  }
264  else
265  {
266  prefixCandidate = findCommonPrefix(prefixCandidate, currentFile);
267  suffixCandidate = findCommonSuffix(suffixCandidate, currentFile);
268  }
269  }
270 
271  // Use candidates as requirements
272  requiredPrefix = prefixCandidate;
273  requiredSuffix = suffixCandidate;
274  }
275  // Otherwise examine given file path
276  else
277  {
278  // Set base path
279  basePath = std::filesystem::canonical(filePath.parent_path());
280 
281  // Cound wildcards
282  std::string filename = filePath.filename().string();
283  const unsigned int wildcardCount =
284  static_cast<unsigned int>(std::count(filename.begin(), filename.end(), '*'));
285 
286  // Expect wildcard count to be 0 or 1
287  ARMARX_CHECK_EXPRESSION(wildcardCount <= 1) << "Cannot parse more than one wildcard";
288 
289  // If there's one wildcard, parse the filename for a required prefix and suffix
290  if (wildcardCount == 1)
291  {
292  requiredPrefix = filename.substr(0, filename.find('*'));
293  requiredSuffix = filename.substr(filename.find('*') + 1, filename.length());
294  }
295  // If there's a file without wildcard specified, it is used as a base to find a common prefix and suffix
296  else
297  {
298  // Set candidates to the set filename
299  std::string prefixCandidate = filename;
300  std::string suffixCandidate = filename;
301 
302  // Iterate over dictionary and narrow down the common prefix and suffix
303  for (auto de = std::filesystem::directory_iterator(basePath);
304  de != std::filesystem::directory_iterator{};
305  ++de)
306  {
307  const std::string currentFile = de->path().filename().string();
308  prefixCandidate = findCommonPrefix(prefixCandidate, currentFile);
309  suffixCandidate = findCommonSuffix(suffixCandidate, currentFile);
310  }
311 
312  // Use candidates as requirements
313  requiredPrefix = prefixCandidate;
314  requiredSuffix = suffixCandidate;
315  }
316  }
317 
318  this->basePath = basePath;
319  this->requiredPrefix = requiredPrefix;
320  this->requiredSuffix = requiredSuffix;
321 }
322 
323 void
324 visionx::imrec::strats::ImageSequencePlaybackStrategy::initFramePaths()
325 {
326  // Define natural sort function
327  unsigned long prefixLength = requiredPrefix.length();
328  unsigned long suffixLength = requiredSuffix.length();
329  auto naturalSortComparator = [prefixLength,
330  suffixLength](const std::filesystem::path& a,
331  const std::filesystem::path& b) -> bool
332  {
333  std::string as = a.filename().string();
334  std::string bs = b.filename().string();
335  unsigned long normalisation = 2; // => (x.length() - 1) - (suffixLength + 1)
336  as = as.substr(prefixLength, as.length() - (suffixLength + normalisation));
337  bs = bs.substr(prefixLength, bs.length() - (suffixLength + normalisation));
338 
339  // If the strings are equal in length, use default string comparison
340  if (as.length() == bs.length())
341  {
342  return as < bs;
343  }
344 
345  // Compare if lengths differ
346  while (as.length() > 0 and bs.length() > 0)
347  {
348  unsigned int asi = 0;
349  unsigned int bsi = 0;
350  std::stringstream(as) >> asi;
351  std::stringstream(bs) >> bsi;
352 
353  // If the first characters are equal and the interpreted integers are equal, remove that and retry comparison
354  if (as[0] == bs[0] and asi == bsi)
355  {
356  unsigned int decimals = 1;
357 
358  while (asi /= 10)
359  {
360  ++decimals;
361  }
362 
363  as = as.substr(decimals, as.length());
364  bs = bs.substr(decimals, bs.length());
365  }
366  // If both parts are an integer, compare the integers
367  else if ((asi > 0 or (asi == 0 and as[0] == '0')) and
368  (bsi > 0 or (bsi == 0 and bs[0] == '0')))
369  {
370  return asi < bsi;
371  }
372  // As a last resort, compare the individual characters
373  else
374  {
375  return as[0] < bs[0];
376  }
377  }
378 
379  return as.length() < bs.length();
380  };
381 
382  // Using the base path, the prefix, and the suffix, find all matching frames and save their path
383  for (auto de = std::filesystem::directory_iterator(basePath);
384  de != std::filesystem::directory_iterator{};
385  ++de)
386  {
387  const std::string currentFile = de->path().filename().string();
388 
389  // Save frame file path if requirements fit
390  if (simox::alg::starts_with(currentFile, this->requiredPrefix) and
391  simox::alg::ends_with(currentFile, this->requiredSuffix))
392  {
393  this->framePaths.push_back(de->path());
394  }
395  }
396 
397  // Sort the frames alphanumerically (naturally)
398  std::sort(this->framePaths.begin(), this->framePaths.end(), naturalSortComparator);
399 }
400 
401 void
402 visionx::imrec::strats::ImageSequencePlaybackStrategy::initFrameDimensions()
403 {
404  const std::filesystem::path probe = this->framePaths.at(0);
405  cv::Mat frameImage = cv::imread(probe.string());
406 
407  ARMARX_CHECK_EXPRESSION(frameImage.data != nullptr)
408  << "Failed loading first frame. Image file corrupted?";
409 
410  this->frameHeight = static_cast<unsigned int>(frameImage.size().height);
411  this->frameWidth = static_cast<unsigned int>(frameImage.size().width);
412 }
visionx::imrec::strats::ImageSequencePlaybackStrategy::getCurrentFrame
virtual unsigned int getCurrentFrame() const override
Gets the current frame index of the playback.
Definition: ImageSequencePlaybackStrategy.cpp:108
visionx::imrec::strats::ImageSequencePlaybackStrategy::startPlayback
virtual void startPlayback(const std::filesystem::path &filePath) override
Starts the playback.
Definition: ImageSequencePlaybackStrategy.cpp:120
visionx::imrec::strats::ImageSequencePlaybackStrategy::hasNextFrame
virtual bool hasNextFrame() const override
Indicates whether the recording has a consecutive frame.
Definition: ImageSequencePlaybackStrategy.cpp:114
ImageSequencePlaybackStrategy.h
visionx::imrec::strats::ImageSequencePlaybackStrategy::getFrameWidth
virtual unsigned int getFrameWidth() const override
Gets the width of a frame in pixel.
Definition: ImageSequencePlaybackStrategy.cpp:96
armarx::starts_with
bool starts_with(const std::string &haystack, const std::string &needle)
Definition: StringHelpers.cpp:47
visionx::imrec::strats::ImageSequencePlaybackStrategy::getFps
virtual unsigned int getFps() const override
Gets the amount of frames per second of the recording.
Definition: ImageSequencePlaybackStrategy.cpp:74
armarx::ctrlutil::a
double a(double t, double a0, double j)
Definition: CtrlUtil.h:45
filename
std::string filename
Definition: VisualizationRobot.cpp:86
visionx::imrec::strats::ImageSequencePlaybackStrategy::~ImageSequencePlaybackStrategy
virtual ~ImageSequencePlaybackStrategy() override
Destructor.
Definition: ImageSequencePlaybackStrategy.cpp:62
ExpressionException.h
visionx::imrec::strats::ImageSequencePlaybackStrategy::ImageSequencePlaybackStrategy
ImageSequencePlaybackStrategy()
Default constructor to manually setup later.
Definition: ImageSequencePlaybackStrategy.cpp:46
ARMARX_CHECK_EXPRESSION
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
Definition: ExpressionException.h:73
visionx::imrec::strats::ImageSequencePlaybackStrategy::stopPlayback
virtual void stopPlayback() override
Stops the playback.
Definition: ImageSequencePlaybackStrategy.cpp:186
armarx::ends_with
bool ends_with(const std::string &haystack, const std::string &needle)
Definition: StringHelpers.cpp:53
visionx::imrec::strats::ImageSequencePlaybackStrategy::getFrameHeight
virtual unsigned int getFrameHeight() const override
Gets the height of a frame in pixel.
Definition: ImageSequencePlaybackStrategy.cpp:90
visionx::imrec::strats::ImageSequencePlaybackStrategy::setCurrentFrame
virtual void setCurrentFrame(unsigned int frame) override
Sets the frame from there the playback should resume afterwards (seek)
Definition: ImageSequencePlaybackStrategy.cpp:102
visionx::imrec::strats::ImageSequencePlaybackStrategy::getNextFrame
virtual bool getNextFrame(void *buffer) override
Writes the next frame into a buffer of any form (RGB)
Definition: ImageSequencePlaybackStrategy.cpp:133
DEFAULT_FPS
#define DEFAULT_FPS
Definition: ImageSequencePlaybackStrategy.cpp:25
Logging.h
ARMARX_WARNING
#define ARMARX_WARNING
Definition: Logging.h:193
visionx::imrec::strats::ImageSequencePlaybackStrategy::getFrameCount
virtual unsigned int getFrameCount() const override
Gets the total amout of frames in the recording.
Definition: ImageSequencePlaybackStrategy.cpp:84
visionx::imrec::strats::ImageSequencePlaybackStrategy::isPlayingBack
virtual bool isPlayingBack() const override
Indicates whether the instance is configured to be able to play back.
Definition: ImageSequencePlaybackStrategy.cpp:68