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
66
67bool
72
73unsigned 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
83unsigned int
88
89unsigned int
94
95unsigned int
100
101void
103{
104 this->currentFrame = frame;
105}
106
107unsigned int
109{
110 return this->currentFrame;
111}
112
113bool
115{
116 return this->currentFrame < this->frameCount;
117}
118
119void
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
132bool
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
158bool
160{
161 return this->getNextFrame(buffer.pixels);
162}
163
164bool
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
185void
190
191void
192visionx::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
323void
324visionx::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
401void
402visionx::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}
ImageSequencePlaybackStrategy()
Default constructor to manually setup later.
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_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
double a(double t, double a0, double j)
Definition CtrlUtil.h:45