From 46490d68d74d4e86bf9c6c194284be9833ed4e06 Mon Sep 17 00:00:00 2001 From: Tristan Walter <twalter@orn.mpg.de> Date: Fri, 30 Oct 2020 14:58:53 +0100 Subject: [PATCH] * setting title early, to guarantee that a proper title is displayed on linux * opening/closing video in separate thread --- .../src/commons/common/gui/CrossPlatform.h | 2 +- Application/src/commons/common/gui/GLImpl.cpp | 2 +- Application/src/commons/common/gui/GLImpl.h | 2 +- .../src/commons/common/gui/IMGUIBase.cpp | 2 +- .../src/commons/common/gui/MetalImpl.h | 2 +- .../src/commons/common/gui/MetalImpl.mm | 4 +- .../src/commons/common/video/GenericVideo.cpp | 6 +- .../src/commons/common/video/VideoSource.cpp | 27 +- Application/src/grabber/default_config.cpp | 2 +- Application/src/tracker/VideoOpener.cpp | 305 ++++++++++++------ Application/src/tracker/VideoOpener.h | 21 +- Application/src/tracker/main.cpp | 2 +- docs/parameters_trex.rst | 2 +- 13 files changed, 242 insertions(+), 137 deletions(-) diff --git a/Application/src/commons/common/gui/CrossPlatform.h b/Application/src/commons/common/gui/CrossPlatform.h index c3818be..1618e22 100644 --- a/Application/src/commons/common/gui/CrossPlatform.h +++ b/Application/src/commons/common/gui/CrossPlatform.h @@ -35,7 +35,7 @@ namespace gui { virtual ~CrossPlatform() {} virtual void init() = 0; virtual void post_init() = 0; - virtual void create_window(int width, int height) = 0; + virtual void create_window(const char* title, int width, int height) = 0; virtual void loop(custom_function_t) = 0; virtual LoopStatus update_loop() = 0; //virtual void* texture(uint width, uint height) = 0; diff --git a/Application/src/commons/common/gui/GLImpl.cpp b/Application/src/commons/common/gui/GLImpl.cpp index c5b88a4..ba10ec8 100644 --- a/Application/src/commons/common/gui/GLImpl.cpp +++ b/Application/src/commons/common/gui/GLImpl.cpp @@ -112,7 +112,7 @@ void GLImpl::set_icons(const std::vector<file::Path>& icons) { glfwSetWindowIcon(window, images.size(), images.data()); } -void GLImpl::create_window(int width, int height) { +void GLImpl::create_window(const char* title, int width, int height) { #if __APPLE__ // GL 3.2 + GLSL 150 const char* glsl_version = "#version 150"; diff --git a/Application/src/commons/common/gui/GLImpl.h b/Application/src/commons/common/gui/GLImpl.h index 77cfd26..88f54ce 100644 --- a/Application/src/commons/common/gui/GLImpl.h +++ b/Application/src/commons/common/gui/GLImpl.h @@ -28,7 +28,7 @@ namespace gui { void init() override; void post_init() override; - void create_window(int width, int height) override; + void create_window(const char* title, int width, int height) override; void loop(custom_function_t) override; LoopStatus update_loop() override; TexturePtr texture(const Image*) override; diff --git a/Application/src/commons/common/gui/IMGUIBase.cpp b/Application/src/commons/common/gui/IMGUIBase.cpp index 07825d3..951e9e5 100644 --- a/Application/src/commons/common/gui/IMGUIBase.cpp +++ b/Application/src/commons/common/gui/IMGUIBase.cpp @@ -399,7 +399,7 @@ void clear_cache() { } } - _platform->create_window(width, height); + _platform->create_window(title.c_str(), width, height); glfwSetWindowPos(_platform->window_handle(), mx + (mw - width) * 0.5, my + (mh - height) * 0.5); glfwSetDropCallback(_platform->window_handle(), [](GLFWwindow* window, int N, const char** texts){ diff --git a/Application/src/commons/common/gui/MetalImpl.h b/Application/src/commons/common/gui/MetalImpl.h index 15f95cf..940ceeb 100644 --- a/Application/src/commons/common/gui/MetalImpl.h +++ b/Application/src/commons/common/gui/MetalImpl.h @@ -30,7 +30,7 @@ namespace gui { void init() override; void post_init() override; - void create_window(int width, int height) override; + void create_window(const char* title, int width, int height) override; void loop(custom_function_t) override; LoopStatus update_loop() override; TexturePtr texture(const Image*) override; diff --git a/Application/src/commons/common/gui/MetalImpl.mm b/Application/src/commons/common/gui/MetalImpl.mm index a8b6660..63a5593 100644 --- a/Application/src/commons/common/gui/MetalImpl.mm +++ b/Application/src/commons/common/gui/MetalImpl.mm @@ -198,10 +198,10 @@ bool MetalImpl::open_files(const std::vector<file::Path> &paths) { _data->commandQueue = [_data->device newCommandQueue]; } - void MetalImpl::create_window(int width, int height) { + void MetalImpl::create_window(const char* title, int width, int height) { // Create window with graphics context glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - window = glfwCreateWindow(width, height, "Dear ImGui GLFW+Metal example", NULL, NULL); + window = glfwCreateWindow(width, height, "", NULL, NULL); if (window == NULL) U_EXCEPTION("[METAL] Cannot create GLFW window."); diff --git a/Application/src/commons/common/video/GenericVideo.cpp b/Application/src/commons/common/video/GenericVideo.cpp index 0bce830..346456b 100644 --- a/Application/src/commons/common/video/GenericVideo.cpp +++ b/Application/src/commons/common/video/GenericVideo.cpp @@ -9,9 +9,9 @@ using namespace cmn; namespace cmn { ENUM_CLASS_DOCS(averaging_method_t, "Sum all samples and divide by N.", - "Calculate a per-pixel median of the samples.", - "Use a per-pixel minimum across samples.", - "Use a per-pixel maximum across samples." + "Calculate a per-pixel median of the samples to avoid noise. More computationally involved than mean, but often better results.", + "Use a per-pixel minimum across samples. Usually a good choice for short videos with black backgrounds and individuals that do not move much.", + "Use a per-pixel maximum across samples. Usually a good choice for short videos with white backgrounds and individuals that do not move much." ); } diff --git a/Application/src/commons/common/video/VideoSource.cpp b/Application/src/commons/common/video/VideoSource.cpp index 05d6d6e..14fd6ce 100644 --- a/Application/src/commons/common/video/VideoSource.cpp +++ b/Application/src/commons/common/video/VideoSource.cpp @@ -58,17 +58,26 @@ VideoSource::File::File(long_t index, const std::string& basename, const std::st _video = new Video(); auto npz = file::Path(_filename).replace_extension("npz"); if(npz.exists()) { - static bool message = false; - if(!message) { - Debug("Found timestamps for file '%S'.", &npz.str()); - message = true; + try { + static bool message = false; + if(!message) { + Debug("Found timestamps for file '%S'.", &npz.str()); + message = true; + } + + _timestamps = cnpy::npz_load(npz.str(), "frame_time").as_vec<double>(); + auto res = cnpy::npz_load(npz.str(), "imgshape").as_vec<int64_t>(); + _size = cv::Size( (int)res[1], (int)res[0] ); + _length = (long_t)_timestamps.size(); + } catch(...) { + Except("Failed opening NPZ archive '%S' with (presumably) timestamps in them for video '%S'. Proceeding without.", &npz.str(), &_filename); + + if(!_video->open(_filename)) + U_EXCEPTION("Opening Video '%S' failed.", &_filename); + _length = _video->length(); + _video->close(); } - _timestamps = cnpy::npz_load(npz.str(), "frame_time").as_vec<double>(); - auto res = cnpy::npz_load(npz.str(), "imgshape").as_vec<int64_t>(); - _size = cv::Size( (int)res[1], (int)res[0] ); - _length = (long_t)_timestamps.size(); - } else { if(!_video->open(_filename)) U_EXCEPTION("Opening Video '%S' failed.", &_filename); diff --git a/Application/src/grabber/default_config.cpp b/Application/src/grabber/default_config.cpp index c1211a7..8e4e01e 100644 --- a/Application/src/grabber/default_config.cpp +++ b/Application/src/grabber/default_config.cpp @@ -119,7 +119,7 @@ namespace default_config { CONFIG("equalize_histogram", false, "Equalizes the histogram of the image before thresholding and background subtraction."); CONFIG("quit_after_average", false, "If set to true, this will terminate the program directly after generating (or loading) a background average image.", STARTUP); CONFIG("averaging_method", averaging_method_t::mean, "Determines the way in which the background samples are combined. The background generated in the process will be used to subtract background from foreground objects during conversion."); - CONFIG("average_samples", int(100), "Number of samples taken to generate an average image. Usually has to be less if `average_method` is set to max."); + CONFIG("average_samples", int(100), "Number of samples taken to generate an average image. Usually fewer are necessary for `average_method`s max, and min."); CONFIG("reset_average", false, "If set to true, the average will be regenerated using the live stream of images (video or camera)."); CONFIG("video_size", Size2(-1,-1), "Is set to the dimensions of the resulting image.", SYSTEM); diff --git a/Application/src/tracker/VideoOpener.cpp b/Application/src/tracker/VideoOpener.cpp index 460bd3f..830d9e5 100644 --- a/Application/src/tracker/VideoOpener.cpp +++ b/Application/src/tracker/VideoOpener.cpp @@ -94,6 +94,44 @@ VideoOpener::VideoOpener() { grab::default_config::get(temp_settings, temp_docs, nullptr); //::default_config::get(GlobalSettings::map(), temp_docs, nullptr); + _stale_thread = std::make_unique<std::thread>([this](){ + std::unique_lock guard(_stale_mutex); + bool quit = false; + + while(!quit) { + _stale_variable.wait(guard); + + size_t i=0; + while(!_stale_buffers.empty()) { + auto ptr = std::move(_stale_buffers.front()); + _stale_buffers.pop(); + + if(ptr == nullptr) { + quit = true; + continue; + } + + guard.unlock(); + try { + auto path = ptr->_path; + Debug("Removing stale buffer '%S'...", &path.str()); + ptr = nullptr; + Debug("Removed stale buffer '%S'.", &path.str()); + } catch(const std::exception& e) { + Except("Exception while freeing stale buffer '%s'.", e.what()); + } + guard.lock(); + + ++i; + } + + if(i) + Debug("Removed %d stale buffers", i); + } + + Debug("Quit stale thread."); + }); + _horizontal = std::make_shared<gui::HorizontalLayout>(); _extra = std::make_shared<gui::VerticalLayout>(); _infos = std::make_shared<gui::VerticalLayout>(); @@ -144,7 +182,7 @@ VideoOpener::VideoOpener() { _loading_text = std::make_shared<gui::Text>("generating average", Vec2(100,0), Cyan, gui::Font(0.5)); - _raw_description = std::make_shared<gui::StaticText>("Info", Vec2(), Size2(400, -1), Font(0.6)); + _raw_description = std::make_shared<gui::StaticText>("Info", Vec2(), Size2(500, -1), Font(0.6)); _raw_info->set_children({ Layout::Ptr(std::make_shared<Text>("Preview", Vec2(), White, gui::Font(0.8, Style::Bold))), _screenshot, @@ -303,16 +341,37 @@ VideoOpener::VideoOpener() { 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; + const auto mw = _file_chooser->graph()->width() * 0.3; + if(_raw_description->max_size().x != mw) { + _raw_description->set_max_size(Size2(mw, -1)); + _screenshot_previous_size = 0; + } + + auto size = _screenshot->size().max(); + if(size != _screenshot_previous_size) { + auto ratio = mw / 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}); + + if(_screenshot_previous_size == 0) { + { + std::string info_text = "<h3>Info</h3>\n"; + info_text += "<key>resolution</key>: <ref><nr>"+Meta::toStr(_buffer->_video->size().width)+"</nr>x<nr>"+Meta::toStr(_buffer->_video->size().height)+"</nr></ref>\n"; + + DurationUS us{ uint64_t( _buffer->_video->length() / double(_buffer->_video->framerate()) * 1000.0 * 1000.0 ) }; + auto len = us.to_html(); + info_text += "<key>length</key>: <ref>"+len+"</ref>"; + + _raw_description->set_txt(info_text); + } + + _file_chooser->update_size(); + } + + _screenshot_previous_size = size; } } @@ -336,16 +395,40 @@ VideoOpener::VideoOpener() { }); _file_chooser->on_open([this](auto){ - _buffer = nullptr; + move_to_stale(std::move(_buffer)); }); _file_chooser->on_tab_change([this](auto){ - _buffer = nullptr; + move_to_stale(std::move(_buffer)); }); _file_chooser->open(); } +VideoOpener::~VideoOpener() { + { + std::lock_guard guard(_stale_mutex); + if(_buffer) + _stale_buffers.push(std::move(_buffer)); + _stale_buffers.push(nullptr); + } + + _stale_variable.notify_all(); + _stale_thread->join(); +} + +void VideoOpener::move_to_stale(std::unique_ptr<BufferedVideo>&& ptr) { + if(!ptr) + return; + + { + std::lock_guard guard(_stale_mutex); + _stale_buffers.push(std::move(ptr)); + } + + _stale_variable.notify_one(); +} + VideoOpener::BufferedVideo::BufferedVideo(const file::Path& path) : _path(path) { } @@ -374,8 +457,6 @@ void VideoOpener::BufferedVideo::restart_background() { if(max(img.cols, img.rows) > 500) resize_image(img, 500 / double(max(img.cols, img.rows))); - img.convertTo(_background_image, CV_32FC1); - _background_video_index = 0; _accumulator = std::make_unique<AveragingAccumulator<>>(TEMP_SETTING(averaging_method).value<averaging_method_t::Class>()); _accumulator->add(img); @@ -407,74 +488,98 @@ void VideoOpener::BufferedVideo::restart_background() { }); } -void VideoOpener::BufferedVideo::open() { +void VideoOpener::BufferedVideo::open(std::function<void(const bool)>&& callback) { 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; - _video_timer.reset(); + _update_thread = std::make_unique<std::thread>([this, cb = std::move(callback)]() mutable { + int64_t playback_index = 0; + Timer video_timer; + double seconds_between_frames = 0; + + cv::Mat local; + gpuMat background_image; + gpuMat flt, img, mask, diff, alpha, output; + + try { + { + 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()); + + playback_index = 0; + video_timer.reset(); + + restart_background(); + + // playback at 2x speed + seconds_between_frames = 1 / double(_video->framerate()); + } - if(_playback_index+1 >= _video->length()) - _playback_index = 0; + { + std::lock_guard gaurd(_frame_mutex); + _cached_frame = std::make_unique<Image>(local); + } - 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)); - } + cb(true); - { - std::lock_guard frame_guard(_frame_mutex); - if(_background_copy) { - _background_copy->get().convertTo(_background_image, CV_32FC1); - _background_copy = nullptr; + while(!_terminate) { + std::lock_guard guard(_video_mutex); + auto dt = video_timer.elapsed(); + if(dt < seconds_between_frames) + continue; + + ++playback_index; + video_timer.reset(); + + if((uint64_t)playback_index+1 >= _video->length()) + playback_index = 0; + + try { + _video->frame((size_t)playback_index, local); + + if(_number_samples.load() > 1) { + 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(_background_copy) { + _background_copy->get().convertTo(background_image, CV_32FC1); + _background_copy = nullptr; + } + 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()); + } } - cv::absdiff(_background_image, _flt, _diff); + + } catch(const UtilsException& ex) { + // pass + cb(false); + } catch(...) { + cb(false); } - - 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() { @@ -488,10 +593,26 @@ std::unique_ptr<Image> VideoOpener::BufferedVideo::next() { } void VideoOpener::select_file(const file::Path &p) { - const double max_width = 500; + const double max_width = _file_chooser->graph()->width() * 0.3; std::lock_guard guard(_file_chooser->graph()->lock()); if(_file_chooser->current_tab().extension != "pv") { + auto callback = [this, p, max_width](const bool success){ + if(!success) { + // immediately move to stale + std::lock_guard gui_lock(_file_chooser->graph()->lock()); + std::lock_guard guard(_video_mutex); + Except("Could not open file '%S'.", &p.str()); + + 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)); + _file_chooser->deselect(); + move_to_stale(std::move(_buffer)); + } + }; + try { if(p.empty()) U_EXCEPTION("No file selected."); @@ -515,11 +636,10 @@ void VideoOpener::select_file(const file::Path &p) { _text_fields["output_name"]->update(); } - _buffer = std::make_unique<BufferedVideo>(p); - _buffer->open(); - _screenshot->set_source(std::move(_buffer->next())); - _screenshot_previous_size = 0; + move_to_stale(std::move(_buffer)); + _screenshot_previous_size = 0; + _buffer = std::make_unique<BufferedVideo>(p); try { _buffer->_threshold = TEMP_SETTING(threshold).value<int>(); @@ -527,42 +647,17 @@ void VideoOpener::select_file(const file::Path &p) { Except("Converting number: '%s'", e.what()); } - { - std::string info_text = "<h3>Info</h3>\n"; - info_text += "<key>resolution</key>: <ref><nr>"+Meta::toStr(_buffer->_video->size().width)+"</nr>x<nr>"+Meta::toStr(_buffer->_video->size().height)+"</nr></ref>\n"; - - DurationUS us{ uint64_t( _buffer->_video->length() / double(_buffer->_video->framerate()) * 1000.0 * 1000.0 ) }; - auto len = us.to_html(); - info_text += "<key>length</key>: <ref>"+len+"</ref>"; - - _raw_description->set_txt(info_text); - } - - auto 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}); + _buffer->open(callback); } catch(const std::exception& e) { - std::lock_guard guard(_video_mutex); 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)); - _file_chooser->deselect(); - _buffer = nullptr; + callback(false); } return; } else { std::lock_guard guard(_video_mutex); - if(_buffer) { - _buffer = nullptr; - } + move_to_stale(std::move(_buffer)); } using namespace gui; diff --git a/Application/src/tracker/VideoOpener.h b/Application/src/tracker/VideoOpener.h index b252573..5099670 100644 --- a/Application/src/tracker/VideoOpener.h +++ b/Application/src/tracker/VideoOpener.h @@ -29,25 +29,19 @@ public: 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; std::unique_ptr<Image> _background_copy; std::unique_ptr<AveragingAccumulator<>> _accumulator; //uint64_t _background_samples = 0; uint64_t _background_video_index = 0; - std::atomic<bool> _terminated_background_task; - std::atomic<size_t> _number_samples; + std::atomic<bool> _terminated_background_task = true; + std::atomic<size_t> _number_samples = 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 = 0; std::atomic<uint32_t> _threshold = 0; @@ -59,15 +53,15 @@ public: ~BufferedVideo(); std::unique_ptr<Image> next(); - void open(); + void open(std::function<void(const bool)>&& callback); Size2 size(); void restart_background(); - void update_loop(); }; std::mutex _video_mutex; std::unique_ptr<BufferedVideo> _buffer; + std::queue<std::unique_ptr<BufferedVideo>> _stale_buffers; std::shared_ptr<FileChooser> _file_chooser; std::map<std::string, gui::Drawable*> pointers; @@ -78,6 +72,9 @@ public: gui::derived_ptr<gui::ExternalImage> _screenshot; gui::derived_ptr<gui::Text> _loading_text; gui::derived_ptr<gui::StaticText> _raw_description; + std::unique_ptr<std::thread> _stale_thread; + std::condition_variable _stale_variable; + double _screenshot_previous_size; struct LabeledField { @@ -143,9 +140,13 @@ public: public: VideoOpener(); + ~VideoOpener(); private: void select_file(const file::Path& path); + + std::mutex _stale_mutex; + void move_to_stale(std::unique_ptr<BufferedVideo>&&); }; } diff --git a/Application/src/tracker/main.cpp b/Application/src/tracker/main.cpp index 505e975..cdb8eb3 100644 --- a/Application/src/tracker/main.cpp +++ b/Application/src/tracker/main.cpp @@ -1293,7 +1293,7 @@ int main(int argc, char** argv) gui::IMGUIBase *imgui_base = nullptr; if((GlobalSettings::map().has("nowindow") ? SETTING(nowindow).value<bool>() : false) == false) { - imgui_base = new gui::IMGUIBase("", gui.gui(), [&](){ + imgui_base = new gui::IMGUIBase(gui.window_title(), gui.gui(), [&](){ //std::lock_guard<std::recursive_mutex> lock(gui.gui().lock()); if(SETTING(terminate)) return false; diff --git a/docs/parameters_trex.rst b/docs/parameters_trex.rst index 6926e85..44f0263 100644 --- a/docs/parameters_trex.rst +++ b/docs/parameters_trex.rst @@ -1900,7 +1900,7 @@ TRex parameters .. function:: version(string) - **default value:** "1.0" + **default value:** "1.0.4" Current application version. -- GitLab