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