From b5ded694228d10df7c91d8246b0dbc4b25e835c4 Mon Sep 17 00:00:00 2001 From: Tristan Walter <twalter@orn.mpg.de> Date: Tue, 27 Oct 2020 17:21:06 +0100 Subject: [PATCH] * adding RAW video options to TRex opening dialog * find sphinx, add docs target --- Application/CMakeLists.txt | 24 +- Application/cmake/FindSphinx.cmake | 14 + .../src/commons/common/cpputils/stringutils.h | 8 - Application/src/commons/common/file/Path.cpp | 14 +- Application/src/commons/common/file/Path.h | 2 + .../src/commons/common/gui/DrawStructure.cpp | 5 + .../src/commons/common/gui/DrawStructure.h | 5 +- .../src/commons/common/gui/FileChooser.cpp | 231 +++++++++--- .../src/commons/common/gui/FileChooser.h | 49 ++- .../src/commons/common/gui/IMGUIBase.cpp | 3 + .../src/commons/common/gui/IMGUIBase.h | 8 +- .../commons/common/gui/types/Entangled.cpp | 8 +- .../src/commons/common/video/Video.cpp | 3 +- Application/src/commons/common/video/Video.h | 3 + .../src/commons/common/video/VideoSource.cpp | 67 +++- .../src/commons/common/video/VideoSource.h | 12 +- Application/src/grabber/grabber.cpp | 48 +-- Application/src/grabber/main.cpp | 2 +- Application/src/tracker/CMakeLists.txt | 2 + Application/src/tracker/VideoOpener.cpp | 338 +++++++++++++++++- Application/src/tracker/VideoOpener.h | 69 +++- Application/src/tracker/gui/gui.cpp | 32 +- Application/src/tracker/main.cpp | 34 +- docs/parameters_trex.rst | 2 +- 24 files changed, 808 insertions(+), 175 deletions(-) create mode 100644 Application/cmake/FindSphinx.cmake diff --git a/Application/CMakeLists.txt b/Application/CMakeLists.txt index 6bf4daa..2e10e98 100644 --- a/Application/CMakeLists.txt +++ b/Application/CMakeLists.txt @@ -25,6 +25,7 @@ set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "") #set(CMAKE_CXX_VISIBILITY_PRESET hidden) +option(TREX_BUILD_DOCS OFF) option(TREX_WITH_TESTS OFF) option(TREX_BUILD_GLFW OFF) option(TREX_BUILD_PNG OFF) @@ -963,6 +964,25 @@ foreach(dir ${dirs}) message(STATUS "dir='${dir}'") endforeach() - - add_subdirectory(src) + +if(NOT WIN32 AND ${TREX_BUILD_DOCS}) +find_package(Sphinx) +if(NOT Sphinx_FOUND) +message(ERROR "Cannot find Sphinx executable") +else() +message(STATUS "Found sphinx.") + +add_custom_target( + doc ALL + COMMAND $<TARGET_FILE:TRex> -d "${CMAKE_SOURCE_DIR}/../docs" -h rst + COMMAND $<TARGET_FILE:TGrabs> -d "${CMAKE_SOURCE_DIR}/../docs" -h rst + COMMAND make html + DEPENDS + TRex + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/../docs + COMMENT "Generating API documentation with Doxygen" + VERBATIM +) +endif() +endif() diff --git a/Application/cmake/FindSphinx.cmake b/Application/cmake/FindSphinx.cmake new file mode 100644 index 0000000..a2561fb --- /dev/null +++ b/Application/cmake/FindSphinx.cmake @@ -0,0 +1,14 @@ +find_program(SPHINX_EXECUTABLE NAMES sphinx-build + HINTS + $ENV{SPHINX_DIR} + PATH_SUFFIXES bin + DOC "Sphinx documentation generator" +) + +include(FindPackageHandleStandardArgs) + +find_package_handle_standard_args(Sphinx DEFAULT_MSG + SPHINX_EXECUTABLE +) + +mark_as_advanced(SPHINX_EXECUTABLE) diff --git a/Application/src/commons/common/cpputils/stringutils.h b/Application/src/commons/common/cpputils/stringutils.h index 732c264..d8c32aa 100644 --- a/Application/src/commons/common/cpputils/stringutils.h +++ b/Application/src/commons/common/cpputils/stringutils.h @@ -1,14 +1,6 @@ #ifndef _STRINGUTILS_H #define _STRINGUTILS_H -#ifdef _WIN32 -#include <direct.h> -#define GetCurrentDir _getcwd -#else -#include <unistd.h> -#define GetCurrentDir getcwd -#endif - #include <string> #include <vector> #include <cctype> diff --git a/Application/src/commons/common/file/Path.cpp b/Application/src/commons/common/file/Path.cpp index 7468646..7cb43e2 100644 --- a/Application/src/commons/common/file/Path.cpp +++ b/Application/src/commons/common/file/Path.cpp @@ -234,6 +234,18 @@ namespace file { return rmdir(str().c_str()) == 0; } + + bool valid_extension(const file::Path& path, const std::string& filter_extension) { + if(filter_extension.empty()) + return true; + + auto extensions = utils::split(utils::lowercase(filter_extension), ';'); + if(path.has_extension()) { + return contains(extensions, path.extension().to_string()); + } + + return false; + } std::set<Path> Path::find_files(const std::string& filter_extension) const { if(!is_folder()) @@ -250,7 +262,7 @@ namespace file { if(file == "." || file == "..") continue; - if(ent->d_type & DT_DIR || filter_extension.empty() || utils::endsWith(utils::lowercase(ent->d_name), filter_extension)) + if(ent->d_type & DT_DIR || valid_extension(file::Path(ent->d_name), filter_extension)) result.insert(*this / ent->d_name); } closedir (dir); diff --git a/Application/src/commons/common/file/Path.h b/Application/src/commons/common/file/Path.h index 0b1d04d..c488d03 100644 --- a/Application/src/commons/common/file/Path.h +++ b/Application/src/commons/common/file/Path.h @@ -87,6 +87,8 @@ namespace file { bool operator!=(const Path& lhs, const Path& rhs); std::string exec(const char* cmd); + + bool valid_extension(const file::Path&, const std::string& filter_extension); } std::ostream& operator<<(std::ostream& os, const file::Path& p); diff --git a/Application/src/commons/common/gui/DrawStructure.cpp b/Application/src/commons/common/gui/DrawStructure.cpp index 16590c7..8f8b0ab 100644 --- a/Application/src/commons/common/gui/DrawStructure.cpp +++ b/Application/src/commons/common/gui/DrawStructure.cpp @@ -719,4 +719,9 @@ namespace gui { else _root.insert_cache(base, std::make_shared<CacheObject>()); } + + void DrawStructure::set_size(const Size2& size) { + _width = size.width; + _height = size.height; + } } diff --git a/Application/src/commons/common/gui/DrawStructure.h b/Application/src/commons/common/gui/DrawStructure.h index 4032326..0695188 100644 --- a/Application/src/commons/common/gui/DrawStructure.h +++ b/Application/src/commons/common/gui/DrawStructure.h @@ -88,8 +88,8 @@ namespace gui { GETTER_PTR(Drawable*, hovered_object) GETTER_PTR(Drawable*, selected_object) - GETTER_CONST(const uint16_t, width) - GETTER_CONST(const uint16_t, height) + GETTER(uint16_t, width) + GETTER(uint16_t, height) GETTER(Vec2, scale) GETTER(std::atomic_bool, changed) GETTER_SETTER(Vec2, dialog_window_size) @@ -123,6 +123,7 @@ namespace gui { } void draw_log_messages(); + void set_size(const Size2& size); std::vector<Drawable*> collect(); bool is_key_pressed(Codes code) const; diff --git a/Application/src/commons/common/gui/FileChooser.cpp b/Application/src/commons/common/gui/FileChooser.cpp index 787e185..4508f86 100644 --- a/Application/src/commons/common/gui/FileChooser.cpp +++ b/Application/src/commons/common/gui/FileChooser.cpp @@ -4,18 +4,15 @@ namespace gui { -FileChooser::FileChooser(const file::Path& start, - const std::string& filter_extension, - std::function<void(const file::Path&)> callback, - std::function<void(const file::Path&)> on_select_callback, - derived_ptr<Entangled> extra) +FileChooser::FileChooser(const file::Path& start, const std::string& extension, + std::function<void(const file::Path&, std::string)> callback, + std::function<void(const file::Path&, std::string)> on_select_callback) : - _graph(1300, 750), + _graph(std::make_unique<DrawStructure>(1300, 820)), _description(std::make_shared<Text>("Please choose a file in order to continue.", Vec2(10, 10), White, Font(0.75))), - _extra(extra), _columns(std::make_shared<HorizontalLayout>()), _overall(std::make_shared<VerticalLayout>()), - _base("Choose file", _graph, [this](){ + _base("Choose file", *_graph, [this](){ using namespace gui; { @@ -29,25 +26,36 @@ FileChooser::FileChooser(const file::Path& start, if(!_list) return false; - //_graph.wrap_object(*_textfield); - //_graph.wrap_object(*_list); - _graph.wrap_object(*_overall); + //_graph->wrap_object(*_textfield); + //_graph->wrap_object(*_list); + _graph->wrap_object(*_overall); + if(_on_update) + _on_update(*_graph); if(!_selected_file.empty()) { - //auto text = _graph.text("Selected: "+_selected_file.str(), _list->bounds().pos() + Vec2(0, _list->bounds().height + 10), White, Font(0.6)); + //auto text = _graph->text("Selected: "+_selected_file.str(), _list->bounds().pos() + Vec2(0, _list->bounds().height + 10), White, Font(0.6)); //_button->set_pos(text->pos() + Vec2(0, text->height() + 10)); - //_graph.wrap_object(*_button); + //_graph->wrap_object(*_button); } if (SETTING(terminate)) _running = false; return _running; - }, [](gui::Event e) { + }, [this](gui::Event e) { // -- + if(e.type == gui::WINDOW_RESIZED) { + using namespace gui; + { + std::lock_guard guard(_graph->lock()); + Size2 size(e.size.width, e.size.height); + auto scale = _base.dpi_scale(); + _graph->set_size(size.div(scale)); + } + update_size(); + } }), _path(start), - _filter(filter_extension), _running(true), _files([](const file::Path& A, const file::Path& B) -> bool { return (A.is_folder() && !B.is_folder()) || (A.is_folder() == B.is_folder() && A.str() < B.str()); //A.str() == ".." || (A.str() != ".." && ((A.is_folder() && !B.is_folder()) || (A.is_folder() == B.is_folder() && A.str() < B.str()))); @@ -55,16 +63,20 @@ FileChooser::FileChooser(const file::Path& start, _callback(callback), _on_select_callback(on_select_callback) { + _default_tab.extension = extension; + set_tab(""); + _base.set_open_files_fn([this](const std::vector<file::Path>& paths) -> bool{ if(paths.size() != 1) return false; auto path = paths.front(); - if(path.exists() || path.str() == "/" || path.add_extension("pv").exists()) { + if(!_validity || _validity(path)) //path.exists() || path.str() == "/" || path.add_extension("pv").exists()) + { file_selected(0, path.str()); return true; } else { - Error("Path '%S' cannot be found.", &path.str()); + Error("Path '%S' cannot be opened.", &path.str()); } return false; }); @@ -74,12 +86,7 @@ FileChooser::FileChooser(const file::Path& start, _overall->set_policy(VerticalLayout::CENTER); //_overall->set_background(Transparent, Blue); - if(_extra) { - _extra->auto_size(Margin{0,0}); - _extra->set_name("Extra"); - } - - _list = std::make_shared<ScrollableList<FileItem>>(Bounds(0, 0, _graph.width() - 20 - (_extra ? _extra->width() + 5 : 0), _graph.height() - 70 - 10 - 70)); + _list = std::make_shared<ScrollableList<FileItem>>(Bounds(0, 0, _graph->width() - 20 - (_current_tab.content ? _current_tab.content->width() + 5 : 0), _graph->height() - 70 - 10 - 70)); _list->set_stays_toggled(true); //if(_extra) // _extra->set_background(Transparent, Green); @@ -93,10 +100,12 @@ FileChooser::FileChooser(const file::Path& start, _textfield->on_enter([&](){ auto path = file::Path(_textfield->text()); - if(path.exists() || path.str() == "/" || path.add_extension("pv").exists()) { + //if(path.exists() || path.str() == "/" || path.add_extension("pv").exists()) + if(!_validity || _validity(path)) + { file_selected(0, path.str()); } else { - Error("Path '%S' cannot be found.", &path.str()); + Error("Path '%S' cannot be opened.", &path.str()); } }); @@ -109,8 +118,8 @@ FileChooser::FileChooser(const file::Path& start, _columns->set_name("Columns"); _rows->set_name("Rows"); - if(_extra) - _columns->set_children({_rows, _extra}); + if(_current_tab.content) + _columns->set_children({_rows, _current_tab.content}); else _columns->set_children({_rows}); @@ -120,21 +129,29 @@ FileChooser::FileChooser(const file::Path& start, if(!_path.exists()) _path = "."; - auto files = _path.find_files(_filter); - _files.clear(); - _files.insert(files.begin(), files.end()); - _files.insert(".."); + + try { + auto files = _path.find_files(_current_tab.extension); + _files.clear(); + _files.insert(files.begin(), files.end()); + _files.insert(".."); + + } catch(const UtilsException& ex) { + Error("Cannot list folder '%S'.", &_path); + } update_names(); _textfield->set_text(_path.str()); - _graph.set_scale(_base.dpi_scale() * gui::interface_scale()); + _graph->set_scale(_base.dpi_scale() * gui::interface_scale()); _list->on_select([this](auto i, auto&path){ file_selected(i, path.path()); }); _button->set_font(gui::Font(0.6, Align::Center)); _button->on_click([this](auto){ _running = false; _confirmed_file = _selected_file; + if(_on_open) + _on_open(_confirmed_file); }); _list->set_font(gui::Font(0.6, gui::Align::Left)); @@ -146,6 +163,87 @@ FileChooser::FileChooser(const file::Path& start, }); } +void FileChooser::set_on_update(std::function<void (DrawStructure &)> && fn) { + _on_update = std::move(fn); +} + +void FileChooser::set_tabs(const std::vector<Settings>& tabs) { + _tabs.clear(); + tabs_elements.clear(); + + for(auto tab : tabs) { + if(tab.extension == "") + tab.extension = _default_tab.extension; + _tabs[tab.name] = tab; + + auto button = new Button(tab.name, Bounds(0, 0, Base::default_text_bounds(tab.name).width + 20, 33)); + button->set_toggleable(true); + button->on_click([this, button](auto e){ + if(button->toggled()) { + set_tab(button->txt()); + } + }); + auto ptr = std::shared_ptr<Drawable>(button); + tabs_elements.push_back(ptr); + } + + if(_tabs.size() > 1) { + _tabs_bar = std::make_shared<HorizontalLayout>(tabs_elements); + } else { + _tabs_bar = nullptr; + } + + if(_tabs_bar) { + _rows->set_children(std::vector<Layout::Ptr>{ + _description, _textfield, _list + }); + } else { + _rows->set_children(std::vector<Layout::Ptr>{ + _description, _textfield, _list + }); + } + + if(!_tabs.empty()) + set_tab(tabs.front().name); + else + set_tab(""); +} + +void FileChooser::set_tab(std::string tab) { + if(tab != _current_tab.name) { + + } else + return; + + if(tab.empty()) { + _current_tab = _default_tab; + } else if(!_tabs.count(tab)) { + auto str = Meta::toStr(_tabs); + Except("FileChooser %S does not contain tab '%S'.", &str, &tab); + } else { + _current_tab = _tabs.at(tab); + } + + for(auto &ptr : tabs_elements) { + if(static_cast<Button*>(ptr.get())->txt() != tab) { + static_cast<Button*>(ptr.get())->set_toggle(false); + static_cast<Button*>(ptr.get())->set_clickable(true); + } else + static_cast<Button*>(ptr.get())->set_clickable(false); + } + + change_folder(_path); + if(!_selected_file.empty()) + file_selected(0, _selected_file); + + if(_current_tab.content) { + _current_tab.content->auto_size(Margin{0,0}); + _current_tab.content->set_name("Extra"); + } + + _graph->set_dirty(&_base); +} + void FileChooser::update_names() { _names.clear(); for(auto &f : _files) { @@ -174,16 +272,18 @@ Color FileChooser::FileItem::text_color() const { void FileChooser::open() { _base.loop(); - _callback(_confirmed_file); + if(_callback) + _callback(_confirmed_file, _current_tab.name); } -void FileChooser::file_selected(size_t, file::Path p) { +void FileChooser::change_folder(const file::Path& p) { auto org = _path; + auto copy = _files; if(p.str() == "..") { try { _path = _path.remove_filename(); - auto files = _path.find_files(_filter); + auto files = _path.find_files(_current_tab.extension); _files.clear(); _files.insert(files.begin(), files.end()); _files.insert(".."); @@ -193,16 +293,14 @@ void FileChooser::file_selected(size_t, file::Path p) { } catch(const UtilsException&e) { _path = org; - auto files = _path.find_files(_filter); - _files.clear(); - _files.insert(files.begin(), files.end()); - _files.insert(".."); + _files = copy; } + update_names(); } else if(p.is_folder()) { try { _path = p; - auto files = _path.find_files(_filter); + auto files = _path.find_files(_current_tab.extension); _files.clear(); _files.insert(files.begin(), files.end()); _files.insert(".."); @@ -212,29 +310,40 @@ void FileChooser::file_selected(size_t, file::Path p) { } catch(const UtilsException&e) { _path = org; - auto files = _path.find_files(_filter); - _files.clear(); - _files.insert(files.begin(), files.end()); - _files.insert(".."); + _files = copy; } - + update_names(); } - else { - _selected_file = p.remove_extension(); +} + +void FileChooser::file_selected(size_t, file::Path p) { + if(p.str() == ".." || p.is_folder()) { + change_folder(p); + } else { + _selected_file = p; if(!_selected_text) _selected_text = std::make_shared<StaticText>("Selected: "+_selected_file.str(), Vec2(), Vec2(700, 0), Font(0.6)); else _selected_text->set_txt("Selected: "+_selected_file.str()); - _overall->set_children({ - _columns, - _selected_text, - _button - }); + if(_tabs_bar) + _overall->set_children({ + _tabs_bar, + _columns, + _selected_text, + _button + }); + else + _overall->set_children({ + _columns, + _selected_text, + _button + }); _overall->update_layout(); //update_size(); - _on_select_callback(_selected_file); + if(_on_select_callback) + _on_select_callback(_selected_file, _current_tab.extension); update_size(); } @@ -242,12 +351,15 @@ void FileChooser::file_selected(size_t, file::Path p) { } void FileChooser::update_size() { - float left_column_width = _graph.width() - 20 - (_extra && _extra->width() > 20 ? _extra->width() + 10 : 0) - 10; + float left_column_width = _graph->width() - 20 - (_current_tab.content && _current_tab.content->width() > 20 ? _current_tab.content->width() + 10 : 0) - 10; if(_selected_text) { - _selected_text->set_max_size(Size2(left_column_width)); + _selected_text->set_max_size(Size2(_graph->width() - 20)); } - float left_column_height = _graph.height() - 70 - 10 - (_overall->children().size() > 1 ? _button->height() + 10 : 0); + if(_tabs_bar) _tabs_bar->auto_size(Margin{0,0}); + if(_tabs_bar) _tabs_bar->update_layout(); + + float left_column_height = _graph->height() - 70 - 10 - (_overall->children().size() > 1 ? _button->height() + 10 : 0) - (_tabs_bar ? _tabs_bar->height() + 10 : 0) - (_selected_text && !_selected_file.empty() ? _selected_text->height() : 0); _list->set_bounds(Bounds(0, 0, left_column_width, left_column_height - 70)); _textfield->set_bounds(Bounds(0, 0, left_column_width, 30)); @@ -256,7 +368,12 @@ void FileChooser::update_size() { if(_rows) _rows->auto_size(Margin{0,0}); if(_rows) _rows->update_layout(); - if(_extra) _extra->auto_size(Margin{0,0}); + if(_current_tab.content) + _columns->set_children({_rows, _current_tab.content}); + else + _columns->set_children({_rows}); + + if(_current_tab.content) _current_tab.content->auto_size(Margin{0,0}); _columns->auto_size(Margin{0,0}); _columns->update_layout(); diff --git a/Application/src/commons/common/gui/FileChooser.h b/Application/src/commons/common/gui/FileChooser.h index 9b2b976..94ac55f 100644 --- a/Application/src/commons/common/gui/FileChooser.h +++ b/Application/src/commons/common/gui/FileChooser.h @@ -22,39 +22,74 @@ class FileChooser { operator std::string() const override; }; - DrawStructure _graph; +public: + struct Settings { + std::string name; + std::string extension; + derived_ptr<Entangled> content; + + bool is_valid_extension(const file::Path& path) const { + return file::valid_extension(path, extension); + } + + std::string toStr() const { + return name; + } + + static std::string class_name() { + return "FileChooser::Settings"; + } + }; + +protected: + GETTER_NCONST(std::unique_ptr<DrawStructure>, graph) derived_ptr<Text> _description; derived_ptr<StaticText> _selected_text; - derived_ptr<Entangled> _extra; derived_ptr<ScrollableList<FileItem>> _list; - derived_ptr <Button> _button; - derived_ptr <Textfield> _textfield; + derived_ptr<Button> _button; + derived_ptr<Textfield> _textfield; derived_ptr<VerticalLayout> _rows; derived_ptr<HorizontalLayout> _columns; derived_ptr<VerticalLayout> _overall; + derived_ptr<HorizontalLayout> _tabs_bar; + std::vector<Layout::Ptr> tabs_elements; IMGUIBase _base; std::vector<FileItem> _names; file::Path _path; - std::string _filter; bool _running; std::set<file::Path, std::function<bool(const file::Path&, const file::Path&)>> _files; file::Path _selected_file, _confirmed_file; - std::function<void(const file::Path&)> _callback, _on_select_callback; + std::function<void(const file::Path&, std::string)> _callback, _on_select_callback; + std::function<void(DrawStructure&)> _on_update; + std::function<bool(file::Path)> _validity; + std::function<void(file::Path)> _on_open; std::queue<std::function<void()>> _execute; std::mutex _execute_mutex; + std::map<std::string, Settings> _tabs; + GETTER(Settings, current_tab) + Settings _default_tab; public: - FileChooser(const file::Path& start, const std::string& filter_extension, std::function<void(const file::Path&)> callback, std::function<void(const file::Path&)> on_select_callback= [](auto&){}, derived_ptr<Entangled> extra = nullptr); + FileChooser(const file::Path& start, const std::string& extension, + std::function<void(const file::Path&, std::string)> callback, + std::function<void(const file::Path&, std::string)> on_select_callback = nullptr); + void set_tabs(const std::vector<Settings>&); + void set_tab(std::string); void open(); void execute(std::function<void()>&&); void update_size(); + void set_on_update(std::function<void(DrawStructure&)>&&); + void set_on_open(std::function<void(file::Path)>&& fn) { _on_open = std::move(fn); } + void set_validity_check(std::function<bool(file::Path)>&& fn) { _validity = std::move(fn); } private: void file_selected(size_t i, file::Path path); void update_names(); + void update_tabs(); + void change_folder(const file::Path&); }; } diff --git a/Application/src/commons/common/gui/IMGUIBase.cpp b/Application/src/commons/common/gui/IMGUIBase.cpp index 8af45b0..07825d3 100644 --- a/Application/src/commons/common/gui/IMGUIBase.cpp +++ b/Application/src/commons/common/gui/IMGUIBase.cpp @@ -1007,6 +1007,9 @@ void IMGUIBase::draw_element(const DrawOrder& order) { //_all_textures[this].insert(tex_cache); } + if(!static_cast<ExternalImage*>(o)->source()) + break; + auto image_size = static_cast<ExternalImage*>(o)->source()->bounds().size(); if(image_size.empty()) break; diff --git a/Application/src/commons/common/gui/IMGUIBase.h b/Application/src/commons/common/gui/IMGUIBase.h index 3f6353e..c577051 100644 --- a/Application/src/commons/common/gui/IMGUIBase.h +++ b/Application/src/commons/common/gui/IMGUIBase.h @@ -66,8 +66,10 @@ namespace gui { public: template<typename impl_t = default_impl_t> - IMGUIBase(std::string title, DrawStructure& base, CrossPlatform::custom_function_t custom_loop, std::function<void(const gui::Event&)> event_fn) : _graph(&base), _custom_loop(custom_loop), _event_fn(event_fn) + IMGUIBase(std::string title, DrawStructure& base, CrossPlatform::custom_function_t custom_loop, std::function<void(const gui::Event&)> event_fn) : _custom_loop(custom_loop), _event_fn(event_fn) { + set_graph(base); + auto ptr = new impl_t([this](){ if(_graph == NULL) return; @@ -97,6 +99,10 @@ namespace gui { init(title); } + void set_graph(DrawStructure& base) { + _graph = &base; + + } void init(const std::string& title); ~IMGUIBase(); diff --git a/Application/src/commons/common/gui/types/Entangled.cpp b/Application/src/commons/common/gui/types/Entangled.cpp index 8cf5fbb..0c85584 100644 --- a/Application/src/commons/common/gui/types/Entangled.cpp +++ b/Application/src/commons/common/gui/types/Entangled.cpp @@ -188,8 +188,8 @@ namespace gui { if(c) { #ifndef NDEBUG - if(!Drawable::name().empty()) - Debug("Changed '%S' content (%d children, %f width).", &Drawable::name(), _children.size(), width()); +// if(!Drawable::name().empty()) +// Debug("Changed '%S' content (%d children, %f width).", &Drawable::name(), _children.size(), width()); #endif /*SectionInterface* p = this; while((p = p->parent()) != nullptr) { @@ -212,10 +212,6 @@ namespace gui { void Entangled::before_draw() { _content_changed_while_updating = false; -#ifndef NDEBUG - if(!Drawable::name().empty() && _content_changed) - Debug("Calling before draw: '%S' (%d).", &Drawable::name(), _content_changed ? 1 : 0); -#endif update(); for(auto c : _children) { diff --git a/Application/src/commons/common/video/Video.cpp b/Application/src/commons/common/video/Video.cpp index 28d322f..9943a80 100644 --- a/Application/src/commons/common/video/Video.cpp +++ b/Application/src/commons/common/video/Video.cpp @@ -169,7 +169,9 @@ void Video::frame(long_t index, cv::Mat& frame, bool lazy) { //Warning("Have to reset video index from %d to %d (%S)", _last_index, index, &_filename); _cap->set(cv::CAP_PROP_POS_FRAMES, index); } else if(index > _last_index+1) { +#ifndef NDEBUG Warning("Have to skip from video index from %d to %d (%S)", _last_index, index-1, &_filename); +#endif for(; _last_index+1 < index; _last_index++) _cap->grab(); } @@ -177,7 +179,6 @@ void Video::frame(long_t index, cv::Mat& frame, bool lazy) { _last_index = index; // Read requested frame - static cv::Mat read; if(!_cap->read(read)) U_EXCEPTION("Cannot read frame %d of video '%S'.", index, &_filename); #endif diff --git a/Application/src/commons/common/video/Video.h b/Application/src/commons/common/video/Video.h index 91a00c5..7611f06 100644 --- a/Application/src/commons/common/video/Video.h +++ b/Application/src/commons/common/video/Video.h @@ -206,6 +206,9 @@ private: */ cv::Size _size; + //! Temporary read cache + cv::Mat read; + /** * Calculates maps using OpenCVs initUndistortRectifyMap method, which are later * used to undistort frames of this video. diff --git a/Application/src/commons/common/video/VideoSource.cpp b/Application/src/commons/common/video/VideoSource.cpp index 1a4fad1..062da96 100644 --- a/Application/src/commons/common/video/VideoSource.cpp +++ b/Application/src/commons/common/video/VideoSource.cpp @@ -1,6 +1,6 @@ #include "VideoSource.h" #include "Video.h" -#include <iomanip> +#include <regex> #include <file/Path.h> #include <misc/GlobalSettings.h> #include <misc/ThreadPool.h> @@ -207,7 +207,54 @@ VideoSource::~VideoSource() { } } -VideoSource::VideoSource(const std::vector<file::Path>& files) : _last_file(NULL), _length(0), _has_timestamps(false), _framerate(-1) +VideoSource::VideoSource(const std::string& source) { + std::smatch m; + std::regex rplaceholder ("%[0-9]+(\\.[0-9]+(.[1-9][0-9]*)?)?d$"), rext(".*(\\..+)$"); + + long_t number_length = -1, start_number = 0, end_number = VIDEO_SEQUENCE_UNSPECIFIED_VALUE; + + std::string base_name, extension; + if(std::regex_search(source,m,rext)) { + auto x = m[1]; + extension = x.str().substr(1); + base_name = source.substr(0, m.position(1)); + + Debug("Extension '%S' basename '%S'", &extension, &base_name); + + } else { + U_EXCEPTION("File extension not found in '%S'", &source); + } + + if(std::regex_search (base_name,m,rplaceholder)) { + auto x = m[0]; + + std::string s = x.str(); + auto p = m.position(); + + s = s.substr(1, s.length()-2); + auto split = utils::split(s, '.'); + + if(split.size()>1) { + start_number = std::stoi(split[1]); + } + if(split.size()>2) { + end_number = std::stoi(split[2]); + } + + number_length = std::stoi(split[0]); + base_name = base_name.substr(0, p); + Debug("match '%S' at %d with nr %d", &s, p, number_length); + } + + if(number_length != -1) { + // no placeholders found, just load file. + open(base_name, extension, start_number, end_number, number_length); + } else { + open(base_name, extension); + } +} + +VideoSource::VideoSource(const std::vector<file::Path>& files) { for(auto &path : files) { auto extension = path.extension().to_string(); @@ -229,8 +276,9 @@ VideoSource::VideoSource(const std::vector<file::Path>& files) : _last_file(NULL _has_timestamps = _files_in_seq.front()->has_timestamps(); } -VideoSource::VideoSource(const std::string& basename, const std::string& extension, int seq_start, int seq_end, int padding) : _last_file(NULL), _length(0), _has_timestamps(false), _framerate(-1) { - +VideoSource::VideoSource() {} +void VideoSource::open(const std::string& basename, const std::string& extension, int seq_start, int seq_end, int padding) +{ if (seq_start == VIDEO_SEQUENCE_INVALID_VALUE || seq_end == VIDEO_SEQUENCE_INVALID_VALUE) { File *f = File::open(0, basename, extension); @@ -288,8 +336,8 @@ VideoSource::VideoSource(const std::string& basename, const std::string& extensi ss << basename << std::setfill('0') << std::setw(padding) << i; File *f = File::open(i-seq_start, ss.str(), extension, i != seq_start); - if(!f) - U_EXCEPTION("Cannot find file '%s.%S' in sequence %d-%d.", ss.str().c_str(), &extension, seq_start, seq_end); + if(!f) + U_EXCEPTION("Cannot find file '%s.%S' in sequence %d-%d.", ss.str().c_str(), &extension, seq_start, seq_end); _files_in_seq.push_back(f); _length += f->length(); @@ -305,9 +353,9 @@ VideoSource::VideoSource(const std::string& basename, const std::string& extensi U_EXCEPTION("Provided an empty video sequence for video source '%S'.", &basename); } - if(_files_in_seq.empty()) - U_EXCEPTION("Cannot load video sequence '%S' (it is empty).", &basename); - + if(_files_in_seq.empty()) + U_EXCEPTION("Cannot load video sequence '%S' (it is empty).", &basename); + _size = _files_in_seq.at(0)->resolution(); _has_timestamps = _files_in_seq.front()->has_timestamps(); @@ -336,6 +384,7 @@ VideoSource::VideoSource(const std::string& basename, const std::string& extensi } void VideoSource::frame(uint64_t globalIndex, gpuMat& output) { + U_EXCEPTION("Using empty function."); } void VideoSource::frame(uint64_t globalIndex, cv::Mat& output) { diff --git a/Application/src/commons/common/video/VideoSource.h b/Application/src/commons/common/video/VideoSource.h index 680adb5..bfbd442 100644 --- a/Application/src/commons/common/video/VideoSource.h +++ b/Application/src/commons/common/video/VideoSource.h @@ -67,21 +67,23 @@ private: */ std::vector<File*> _files_in_seq; - File* _last_file; + File* _last_file = nullptr; cv::Size _size; - uint64_t _length; + uint64_t _length = 0; cv::Mat _average; cv::Mat _mask; - bool _has_timestamps; - short _framerate; + bool _has_timestamps = false; + short _framerate = -1; public: /** * Automatically load a range of files with a certain extension called * {basename}{seq_start<=number<=seq_end}.{extension} */ - VideoSource(const std::string& basename, const std::string& extension, int seq_start = VIDEO_SEQUENCE_INVALID_VALUE, int seq_end = VIDEO_SEQUENCE_INVALID_VALUE, int padding = 4); + VideoSource(); + VideoSource(const std::string& source); VideoSource(const std::vector<file::Path>& files); + void open(const std::string& basename, const std::string& extension, int seq_start = VIDEO_SEQUENCE_INVALID_VALUE, int seq_end = VIDEO_SEQUENCE_INVALID_VALUE, int padding = 4); ~VideoSource(); diff --git a/Application/src/grabber/grabber.cpp b/Application/src/grabber/grabber.cpp index 84377f7..12ec77c 100644 --- a/Application/src/grabber/grabber.cpp +++ b/Application/src/grabber/grabber.cpp @@ -1,4 +1,4 @@ -#include <regex> + #include <cstdio> #include "grabber.h" @@ -701,51 +701,7 @@ void FrameGrabber::initialize_video() { } if(filenames.size() == 1) { - std::string source = filenames.front().str(); - std::smatch m; - std::regex rplaceholder ("%[0-9]+(\\.[0-9]+(.[1-9][0-9]*)?)?d$"), rext(".*(\\..+)$"); - - long_t number_length = -1, start_number = 0, end_number = VIDEO_SEQUENCE_UNSPECIFIED_VALUE; - - std::string base_name, extension; - if(std::regex_search(source,m,rext)) { - auto x = m[1]; - extension = x.str().substr(1); - base_name = source.substr(0, m.position(1)); - - Debug("Extension '%S' basename '%S'", &extension, &base_name); - - } else { - U_EXCEPTION("File extension not found in '%S'", &source); - } - - if(std::regex_search (base_name,m,rplaceholder)) { - auto x = m[0]; - - std::string s = x.str(); - auto p = m.position(); - - s = s.substr(1, s.length()-2); - auto split = utils::split(s, '.'); - - if(split.size()>1) { - start_number = std::stoi(split[1]); - } - if(split.size()>2) { - end_number = std::stoi(split[2]); - } - - number_length = std::stoi(split[0]); - base_name = base_name.substr(0, p); - Debug("match '%S' at %d with nr %d", &s, p, number_length); - } - - if(number_length != -1) { - // no placeholders found, just load file. - _video = new VideoSource(base_name, extension, start_number, end_number, number_length); - } else { - _video = new VideoSource(base_name, extension); - } + _video = new VideoSource(filenames.front()); } else { _video = new VideoSource(filenames); diff --git a/Application/src/grabber/main.cpp b/Application/src/grabber/main.cpp index ed681fe..d15b35d 100644 --- a/Application/src/grabber/main.cpp +++ b/Application/src/grabber/main.cpp @@ -452,7 +452,7 @@ int main(int argc, char** argv) fwrite(rst.data(), sizeof(char), rst.length(), f); fclose(f); - printf("%s\n", rst.c_str()); + //printf("%s\n", rst.c_str()); Debug("Saved at '%S'.", &path.str()); exit(0); diff --git a/Application/src/tracker/CMakeLists.txt b/Application/src/tracker/CMakeLists.txt index 432e05f..46d74d8 100644 --- a/Application/src/tracker/CMakeLists.txt +++ b/Application/src/tracker/CMakeLists.txt @@ -20,6 +20,8 @@ file(GLOB SRCS main.cpp VideoOpener.cpp VideoOpener.h + ../grabber/default_config.cpp + ../grabber/default_config.h gfx/TRexIcon16.png gfx/TRexIcon32.png gfx/TRexIcon64.png diff --git a/Application/src/tracker/VideoOpener.cpp b/Application/src/tracker/VideoOpener.cpp index ef2a732..b050c2b 100644 --- a/Application/src/tracker/VideoOpener.cpp +++ b/Application/src/tracker/VideoOpener.cpp @@ -6,10 +6,15 @@ #include <tracker/misc/Output.h> #include <misc/default_settings.h> #include <gui/types/StaticText.h> +#include <processing/RawProcessing.h> +#include <grabber/default_config.h> namespace gui { VideoOpener::VideoOpener() { + grab::default_config::get(GlobalSettings::map(), GlobalSettings::docs(), &GlobalSettings::set_access_level); + ::default_config::get(GlobalSettings::map(), GlobalSettings::docs(), &GlobalSettings::set_access_level); + _horizontal = std::make_shared<gui::HorizontalLayout>(); _extra = std::make_shared<gui::VerticalLayout>(); _infos = std::make_shared<gui::VerticalLayout>(); @@ -20,6 +25,54 @@ VideoOpener::VideoOpener() { _horizontal->set_children({_infos, _extra}); + SETTING(output_name) = file::Path("video"); + + _horizontal_raw = std::make_shared<gui::HorizontalLayout>(); + _raw_settings = std::make_shared<gui::VerticalLayout>(); + _raw_info = std::make_shared<gui::VerticalLayout>(); + _raw_info->set_policy(gui::VerticalLayout::LEFT); + _screenshot = std::make_shared<gui::ExternalImage>(); + _text_fields.clear(); + _text_fields["output_name"] = LabeledField("output name"); + _text_fields["output_name"]._text_field->set_text(SETTING(output_name).get().valueString()); + _text_fields["output_name"]._text_field->on_text_changed([this](){ + file::Path number = _text_fields["output_name"]._text_field->text(); + SETTING(output_name) = number; + }); + + _text_fields["threshold"] = LabeledField("threshold"); + _text_fields["threshold"]._text_field->set_text(SETTING(threshold).get().valueString()); + _text_fields["threshold"]._text_field->on_text_changed([this](){ + auto number = Meta::fromStr<int>(_text_fields["threshold"]._text_field->text()); + SETTING(threshold) = number; + + if(_buffer) { + _buffer->_threshold = number; + } + }); + _text_fields["average_samples"] = LabeledField("average_samples"); + _text_fields["average_samples"]._text_field->set_text(SETTING(average_samples).get().valueString()); + _text_fields["average_samples"]._text_field->on_text_changed([this](){ + auto number = Meta::fromStr<int>(_text_fields["average_samples"]._text_field->text()); + SETTING(average_samples) = number; + + if(_buffer) { + _buffer->restart_background(); + } + }); + + std::vector<Layout::Ptr> objects{}; + for(auto &[key, ptr] : _text_fields) { + objects.push_back(ptr._joint); + } + + _raw_settings->set_children(objects); + + _raw_description = std::make_shared<gui::StaticText>("Info", Vec2(), Size2(500, -1)); + _raw_info->set_children({_screenshot, _raw_description}); + _horizontal_raw->set_children({_raw_settings, _raw_info}); + _horizontal_raw->set_policy(gui::HorizontalLayout::TOP); + _settings_to_show = { "track_max_individuals", "blob_size_ranges", @@ -35,7 +88,10 @@ VideoOpener::VideoOpener() { _output_prefix = SETTING(output_prefix).value<std::string>(); - _file_chooser = std::make_shared<gui::FileChooser>(SETTING(output_dir).value<file::Path>(), ".pv", [this](const file::Path& path) + _file_chooser = std::make_shared<gui::FileChooser>( + SETTING(output_dir).value<file::Path>(), + "pv", + [this](const file::Path& path, std::string tab) mutable { if(!path.empty()) { auto tmp = path; @@ -88,6 +144,14 @@ VideoOpener::VideoOpener() { if(!first) _result.extra_command_lines = str; + _result.tab = _file_chooser->current_tab(); + _result.tab.content = nullptr; + + if(_result.tab.extension == "pv") { + // PV file, no need to add cmd + } else { + _result.cmd = "-i '" + path.str() + "' " + "-o '"+SETTING(output_name).value<file::Path>().str()+"' -threshold "+SETTING(threshold).get().valueString()+" -average_samples "+SETTING(average_samples).get().valueString()+ " -reset_average"; + } if(_load_results_checkbox && _load_results_checkbox->checked()) { _result.load_results = true; @@ -96,18 +160,274 @@ VideoOpener::VideoOpener() { } - }, [this](auto& path){ select_file(path); }, _horizontal); + }, [this](auto& path, std::string tab){ select_file(path); }); + + _file_chooser->set_tabs({ + FileChooser::Settings{std::string("Pre-processed (PV)"), std::string("pv"), _horizontal}, + FileChooser::Settings{std::string("Convert (RAW)"), std::string("mp4;avi;mov;flv;m4v;webm"), _horizontal_raw} + }); + + _file_chooser->set_on_update([this](auto&) mutable { + std::lock_guard guard(_video_mutex); + if(_buffer) { + auto image = _buffer->next(); + if(image) { + _screenshot->set_source(std::move(image)); + + if(_screenshot->size().max() != _screenshot_previous_size) { + _screenshot_previous_size = _screenshot->size().max(); + + const double max_width = 500; + auto ratio = max_width / _screenshot_previous_size; + Debug("%f (%f / %f)", ratio, max_width, _screenshot_previous_size); + _screenshot->set_scale(Vec2(ratio)); + + _raw_info->auto_size(Margin{0, 0}); + _raw_settings->auto_size(Margin{0, 0}); + _horizontal_raw->auto_size(Margin{0, 0}); + } + } + } + }); + + _file_chooser->set_validity_check([this](file::Path path) { + if((path.exists() && path.is_folder()) + || _file_chooser->current_tab().is_valid_extension(path)) + return true; + return false; + }); _file_chooser->open(); } +VideoOpener::BufferedVideo::BufferedVideo(const file::Path& path) : _path(path) { +} + +VideoOpener::BufferedVideo::~BufferedVideo() { + _terminate = true; + _terminate_background = true; + + if(_update_thread) + _update_thread->join(); + if(_background_thread) + _background_thread->join(); + + _background_video = nullptr; +} + +void VideoOpener::BufferedVideo::restart_background() { + _terminate_background = true; + if(_background_thread) + _background_thread->join(); + + _terminate_background = false; + + std::lock_guard guard(_frame_mutex); + cv::Mat img; + _background_video->frame(0, img); + if(max(img.cols, img.rows) > 500) + resize_image(img, 500 / double(max(img.cols, img.rows))); + + img.convertTo(_background_image, CV_32FC1); + _background_image.copyTo(_accumulator); + + _background_samples = 1; + _background_video_index = 0; + //_accumulator = cv::Mat::zeros(img.rows, img.cols, CV_32FC1); + + _background_thread = std::make_unique<std::thread>([this](){ + int step = max(1, int(_background_video->length() / max(2.0, double(SETTING(average_samples).value<int>())))); + Debug("Start calculating background in %d steps", step); + cv::Mat flt, img; + + while(!_terminate_background && _background_video_index+1+step < _background_video->length()) { + _background_video_index += step; + + _background_video->frame(_background_video_index, img); + if(max(img.cols, img.rows) > 500) + resize_image(img, 500 / double(max(img.cols, img.rows))); + + img.convertTo(flt, CV_32FC1); + if(!_accumulator.empty()) + cv::add(_accumulator, flt, _accumulator); + else + flt.copyTo(_accumulator); + ++_background_samples; + Debug("%d/%d (%d)", _background_video_index, _background_video->length(), step); + + std::lock_guard guard(_frame_mutex); + cv::divide(_accumulator, cv::Scalar(_background_samples), _background_copy); + _set_copy_background = true; + } + + Debug("Done calculating background"); + }); +} + +void VideoOpener::BufferedVideo::open() { + std::lock_guard guard(_video_mutex); + _video = std::make_unique<VideoSource>(_path.str()); + + _video->frame(0, _local); + _local.copyTo(_img); + + _background_video = std::make_unique<VideoSource>(_path.str()); + _cached_frame = std::make_unique<Image>(_local); + + _playback_index = 0; + _video_timer.reset(); + + restart_background(); + + // playback at 2x speed + _seconds_between_frames = 1 / double(_video->framerate()); + + _update_thread = std::make_unique<std::thread>([this](){ + while(!_terminate) { + std::lock_guard guard(_video_mutex); + auto dt = _video_timer.elapsed(); + if(dt < _seconds_between_frames) + continue; + + _playback_index = _playback_index + 1; // loading is too slow... + + if((uint32_t)_playback_index.load() % 100 == 0) + Debug("Playback %.2fms / %.2fms ...", dt * 1000, _seconds_between_frames * 1000); + + if(dt > _seconds_between_frames) { + + } //else + //_playback_index = _playback_index + dt / _seconds_between_frames; + _video_timer.reset(); + + if(_playback_index+1 >= _video->length()) + _playback_index = 0; + + update_loop(); + } + }); +} + +void VideoOpener::BufferedVideo::update_loop() { + try { + _video->frame((size_t)_playback_index, _local); + _local.copyTo(_img); + if(max(_img.cols, _img.rows) > 500) + resize_image(_img, 500 / double(max(_img.cols, _img.rows))); + _img.convertTo(_flt, CV_32FC1); + + if(_alpha.empty()) { + _alpha = gpuMat(_img.rows, _img.cols, CV_8UC1); + _alpha.setTo(cv::Scalar(255)); + } + + { + std::lock_guard frame_guard(_frame_mutex); + if(_set_copy_background) { + _set_copy_background = false; + _background_copy.copyTo(_background_image); + } + cv::absdiff(_background_image, _flt, _diff); + } + + cv::inRange(_diff, _threshold.load(), 255, _mask); + cv::merge(std::vector<gpuMat>{_mask, _img, _img, _alpha}, _output); + _output.copyTo(_local); + + std::lock_guard frame_guard(_frame_mutex); + _cached_frame = std::make_unique<Image>(_local); + + } catch(const std::exception& e) { + Except("Caught exception while updating '%s'", e.what()); + } +} + +Size2 VideoOpener::BufferedVideo::size() { + std::lock_guard guard(_video_mutex); + return Size2(_video->size()); +} + +std::unique_ptr<Image> VideoOpener::BufferedVideo::next() { + std::lock_guard guard(_frame_mutex); + return std::move(_cached_frame); +} + void VideoOpener::select_file(const file::Path &p) { + const double max_width = 500; + + if(!p.empty() && (!p.has_extension() || p.extension() != "pv")) { + try { + Debug("Opening '%S'", &p.str()); + + std::lock_guard guard(_video_mutex); + { + SETTING(output_name) = file::Path("video"); + auto filename = p; + + if(p.has_extension()) + filename = filename.remove_extension(); + + if(utils::contains(p.filename().to_string(), '%')) { + filename = filename.remove_filename(); + } + + filename = filename.filename(); + + SETTING(output_name) = filename; + if(SETTING(output_name).value<file::Path>().empty()) { + Warning("No output filename given. Defaulting to 'video'."); + } else + Warning("Given empty filename, the program will default to using input basename '%S'.", &filename.str()); + + _text_fields["output_name"]._text_field->set_text(filename.str()); + } + + _buffer = std::make_unique<BufferedVideo>(p); + _buffer->open(); + _screenshot->set_source(std::move(_buffer->next())); + _screenshot_previous_size = 0; + + try { + auto number = _text_fields["threshold"]._text_field->text(); + if(!number.empty()) + _buffer->_threshold = Meta::fromStr<uint32_t>(number); + + } catch(const std::exception &e) { + Except("Converting number: '%s'", e.what()); + } + + auto ratio = max_width / _screenshot->size().max(); + Debug("%f (%f / %f)", ratio, max_width, _screenshot->size().max()); + _screenshot->set_scale(Vec2(ratio)); + + _raw_info->auto_size(Margin{0, 0}); + _raw_settings->auto_size(Margin{0, 0}); + _horizontal_raw->auto_size(Margin{0, 0}); + + } catch(const std::exception& e) { + Except("Cannot open file '%S' (%s)", &p.str(), e.what()); + + cv::Mat img = cv::Mat::zeros(max_width, max_width, CV_8UC1); + cv::putText(img, "Cannot open video.", Vec2(50, 220), cv::FONT_HERSHEY_PLAIN, 1, White); + _screenshot->set_source(std::make_unique<Image>(img)); + _screenshot->set_scale(Vec2(1)); + _buffer = nullptr; + } + return; + + } else { + std::lock_guard guard(_video_mutex); + if(_buffer) { + _buffer = nullptr; + } + } + using namespace gui; using namespace file; GlobalSettings::map().dont_print("filename"); - _selected = p; - SETTING(filename) = p; + _selected = p.remove_extension(); + SETTING(filename) = p.remove_extension(); Path settings_file = pv::DataLocation::parse("settings"); sprite::Map tmp; @@ -148,10 +468,14 @@ void VideoOpener::select_file(const file::Path &p) { } else if(name == "output_prefix") { std::vector<std::string> folders; for(auto &p : _selected.remove_filename().find_files()) { - if(p.is_folder() && p.filename() != "data" && p.filename() != "..") { - if(!p.find_files().empty()) { - folders.push_back(p.filename().to_string()); + try { + if(p.is_folder() && p.filename() != "data" && p.filename() != "..") { + if(!p.find_files().empty()) { + folders.push_back(p.filename().to_string()); + } } + } catch(const UtilsException& ex) { + continue; // cannot read folder } } diff --git a/Application/src/tracker/VideoOpener.h b/Application/src/tracker/VideoOpener.h index 9d72a24..0b5050b 100644 --- a/Application/src/tracker/VideoOpener.h +++ b/Application/src/tracker/VideoOpener.h @@ -5,6 +5,7 @@ #include <gui/types/Layout.h> #include <gui/types/Checkbox.h> #include <file/Path.h> +#include <video/VideoSource.h> namespace gui { @@ -13,18 +14,82 @@ public: struct Result { std::string extra_command_lines; std::string load_results_from; + std::string cmd; + FileChooser::Settings tab; bool load_results; Result() : load_results(false) {} } _result; + struct BufferedVideo { + file::Path _path; + std::unique_ptr<VideoSource> _video; + std::unique_ptr<VideoSource> _background_video; + gpuMat _background_image; + cv::Mat _local; + gpuMat _flt, _img, _mask, _diff, _alpha, _output; + cv::Mat _accumulator, _background_copy; + bool _set_copy_background; + uint64_t _background_samples = 0; + uint64_t _background_video_index = 0; + + std::mutex _frame_mutex; + std::mutex _video_mutex; + + std::unique_ptr<Image> _cached_frame; + std::atomic<bool> _terminate = false, _terminate_background = false; + std::atomic<double> _playback_index = 0; + Timer _video_timer; + double _seconds_between_frames; + + std::atomic<uint32_t> _threshold = 0; + + std::unique_ptr<std::thread> _update_thread; + std::unique_ptr<std::thread> _background_thread; + + BufferedVideo() {} + BufferedVideo(const file::Path& path); + ~BufferedVideo(); + + std::unique_ptr<Image> next(); + void open(); + Size2 size(); + + void restart_background(); + void update_loop(); + }; + + std::mutex _video_mutex; + std::unique_ptr<BufferedVideo> _buffer; + std::shared_ptr<FileChooser> _file_chooser; std::map<std::string, gui::Drawable*> pointers; std::map<std::string, std::string> start_values; - gui::derived_ptr<gui::VerticalLayout> _extra, _infos; - gui::derived_ptr<gui::HorizontalLayout> _horizontal; + gui::derived_ptr<gui::VerticalLayout> _extra, _infos, _raw_info, _raw_settings; + gui::derived_ptr<gui::HorizontalLayout> _horizontal, _horizontal_raw; + gui::derived_ptr<gui::ExternalImage> _screenshot; + gui::derived_ptr<gui::StaticText> _raw_description; + double _screenshot_previous_size; + + struct LabeledField { + gui::derived_ptr<gui::Text> _text; + gui::derived_ptr<gui::Textfield> _text_field; + gui::derived_ptr<gui::HorizontalLayout> _joint; + + LabeledField(const std::string& name = "") + : _text(std::make_shared<gui::Text>(name)), + _text_field(std::make_shared<gui::Textfield>("", Bounds(0, 0, 500 - _text->width() - 80, 33))), + _joint(std::make_shared<gui::HorizontalLayout>(std::vector<Layout::Ptr>{_text, _text_field})) + { + _text->set_font(Font(0.75, Style::Bold)); + _text->set_color(White); + _text_field->set_placeholder(name); + } + }; + std::map<std::string, LabeledField> _text_fields; + gui::Checkbox *_load_results_checkbox = nullptr; std::string _output_prefix; std::vector<std::string> _settings_to_show; diff --git a/Application/src/tracker/gui/gui.cpp b/Application/src/tracker/gui/gui.cpp index 569e7a4..9f95515 100644 --- a/Application/src/tracker/gui/gui.cpp +++ b/Application/src/tracker/gui/gui.cpp @@ -882,23 +882,29 @@ void GUI::start_recording() { } size_t max_number = 0; - for(auto &file : frames.find_files()) { - auto name = file.filename().to_string(); - if(utils::beginsWith(name, "clip")) { - try { - if(utils::endsWith(name, ".avi")) - name = name.substr(0, name.length() - 4); - auto number = Meta::fromStr<size_t>(name.substr(std::string("clip").length())); - if(number > max_number) - max_number = number; - - } catch(const std::exception& e) { - Except("%S not a number ('%s').", &name, e.what()); + try { + for(auto &file : frames.find_files()) { + auto name = file.filename().to_string(); + if(utils::beginsWith(name, "clip")) { + try { + if(utils::endsWith(name, ".avi")) + name = name.substr(0, name.length() - 4); + auto number = Meta::fromStr<size_t>(name.substr(std::string("clip").length())); + if(number > max_number) + max_number = number; + + } catch(const std::exception& e) { + Except("%S not a number ('%s').", &name, e.what()); + } } } + + ++max_number; + + } catch(const UtilsException& ex) { + Warning("Cannot iterate on folder '%S'. Defaulting to index 0.", &frames.str()); } - ++max_number; Debug("Clip index is %d. Starting at frame %d.", max_number, frame()); frames = frames / ("clip" + Meta::toStr(max_number)); diff --git a/Application/src/tracker/main.cpp b/Application/src/tracker/main.cpp index f1ff779..acc4f26 100644 --- a/Application/src/tracker/main.cpp +++ b/Application/src/tracker/main.cpp @@ -78,6 +78,14 @@ #include <opencv2/core/utils/logger.hpp> +#ifdef _WIN32 +#include <direct.h> +#define GetCurrentDir _getcwd +#else +#include <unistd.h> +#define GetCurrentDir getcwd +#endif + //-Functions------------------------------------------------------------------- using namespace track; @@ -432,7 +440,7 @@ int main(int argc, char** argv) fwrite(rst.data(), sizeof(char), rst.length(), f); fclose(f); - printf("%s\n", rst.c_str()); + //printf("%s\n", rst.c_str()); Debug("Saved at '%S'.", &path.str()); exit(0); @@ -518,11 +526,25 @@ int main(int argc, char** argv) if((GlobalSettings::map().has("nowindow") ? SETTING(nowindow).value<bool>() : false) == false) { gui::VideoOpener opener; opening_result = opener._result; - - if(opening_result.load_results) - load_results = true; - if(!opening_result.load_results_from.empty()) - load_results_from = opening_result.load_results_from; + + if(opening_result.tab.extension == "pv") { + if(opening_result.load_results) + load_results = true; + if(!opening_result.load_results_from.empty()) + load_results_from = opening_result.load_results_from; + } else { + auto wd = SETTING(wd).value<file::Path>(); + Debug("Opening a video file: '%S', '%S'", &opening_result.tab.name, &wd.str()); +#if defined(__APPLE__) + wd = wd / ".." / ".." / ".." / "TGrabs.app" / "Contents" / "MacOS" / "TGrabs"; +#else + wd = wd / "tgrabs"; +#endif + auto exec = wd.str() + " " + opening_result.cmd; + Debug("Executing '%S'", &exec); + file::exec(exec.c_str()); + exit(0); + } } if(SETTING(filename).value<Path>().empty()) { diff --git a/docs/parameters_trex.rst b/docs/parameters_trex.rst index a98d9a9..6926e85 100644 --- a/docs/parameters_trex.rst +++ b/docs/parameters_trex.rst @@ -184,7 +184,7 @@ TRex parameters .. function:: build_is_debug(string) - **default value:** "debug" + **default value:** "release" If built in debug mode, this will show 'debug'. -- GitLab