-
Tristan Walter authoredTristan Walter authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
VideoOpener.cpp 23.96 KiB
#include "VideoOpener.h"
#include <tracker/misc/default_config.h>
#include <misc/GlobalSettings.h>
#include <gui/types/Dropdown.h>
#include <pv.h>
#include <tracker/misc/Output.h>
#include <misc/default_settings.h>
#include <gui/types/StaticText.h>
#include <processing/RawProcessing.h>
#include <grabber/default_config.h>
#define TEMP_SETTING(NAME) (gui::temp_settings[#NAME])
namespace gui {
GlobalSettings::docs_map_t temp_docs;
sprite::Map temp_settings;
VideoOpener::LabeledCheckbox::LabeledCheckbox(const std::string& name)
: LabeledField(name),
_checkbox(std::make_shared<gui::Checkbox>(Vec2(), name)),
_ref(gui::temp_settings[name])
{
_checkbox->set_checked(_ref.value<bool>());
_checkbox->set_font(Font(0.6));
_checkbox->on_change([this](){
try {
_ref.get() = _checkbox->checked();
} catch(...) {}
});
}
void VideoOpener::LabeledCheckbox::update() {
_checkbox->set_checked(_ref.value<bool>());
}
VideoOpener::LabeledTextField::LabeledTextField(const std::string& name)
: LabeledField(name),
_text_field(std::make_shared<gui::Textfield>("", Bounds(0, 0, 300, 28))),
_ref(gui::temp_settings[name])
{
_text_field->set_placeholder(name);
_text_field->set_font(Font(0.6));
_text_field->set_text(_ref.get().valueString());
_text_field->on_text_changed([this](){
try {
_ref.get().set_value_from_string(_text_field->text());
} catch(...) {}
});
}
void VideoOpener::LabeledTextField::update() {
_text_field->set_text(_ref.get().valueString());
}
VideoOpener::LabeledDropDown::LabeledDropDown(const std::string& name)
: LabeledField(name),
_dropdown(std::make_shared<gui::Dropdown>(Bounds(0, 0, 300, 28))),
_ref(gui::temp_settings[name])
{
_dropdown->textfield()->set_font(Font(0.6));
assert(_ref.get().is_enum());
std::vector<Dropdown::TextItem> items;
int index = 0;
for(auto &name : _ref.get().enum_values()()) {
items.push_back(Dropdown::TextItem(name, index++));
}
_dropdown->set_items(items);
_dropdown->select_item(_ref.get().enum_index()());
_dropdown->textfield()->set_text(_ref.get().valueString());
_dropdown->on_select([this](auto index, auto) {
try {
_ref.get().set_value_from_string(_ref.get().enum_values()().at(index));
} catch(...) {}
_dropdown->set_opened(false);
});
}
void VideoOpener::LabeledDropDown::update() {
_dropdown->select_item(_ref.get().enum_index()());
}
VideoOpener::VideoOpener() {
grab::default_config::get(temp_settings, temp_docs, nullptr);
//::default_config::get(GlobalSettings::map(), temp_docs, nullptr);
_horizontal = std::make_shared<gui::HorizontalLayout>();
_extra = std::make_shared<gui::VerticalLayout>();
_infos = std::make_shared<gui::VerticalLayout>();
_horizontal->set_policy(gui::HorizontalLayout::TOP);
_extra->set_policy(gui::VerticalLayout::LEFT);
_infos->set_policy(gui::VerticalLayout::LEFT);
_horizontal->set_children({_infos, _extra});
TEMP_SETTING(output_name) = file::Path("video");
TEMP_SETTING(cmd_parameters) = std::string("-reset_average");
_horizontal_raw = std::make_shared<gui::HorizontalLayout>();
_horizontal_raw->set_clickable(true);
_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();
gui::temp_settings.register_callback((void*)this, [this](auto&map, auto&key, auto&value){
if(key == "threshold") {
if(_buffer)
_buffer->_threshold = value.template value<int>();
} else if(key == "average_samples" || key == "averaging_method") {
if(_buffer)
_buffer->restart_background();
}
});
_text_fields["output_name"] = std::make_unique<LabeledTextField>("output_name");
_text_fields["threshold"] = std::make_unique<LabeledTextField>("threshold");
_text_fields["average_samples"] = std::make_unique<LabeledTextField>("average_samples");
_text_fields["averaging_method"] = std::make_unique<LabeledDropDown>("averaging_method");
_text_fields["cmd_parameters"] = std::make_unique<LabeledTextField>("cmd_parameters");
std::vector<Layout::Ptr> objects{
Layout::Ptr(std::make_shared<Text>("Settings", Vec2(), White, gui::Font(0.8, Style::Bold)))
};
for(auto &[key, ptr] : _text_fields)
ptr->add_to(objects);
_raw_settings->set_children(objects);
_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_info->set_children({
Layout::Ptr(std::make_shared<Text>("Preview", Vec2(), White, gui::Font(0.8, Style::Bold))),
_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",
"track_threshold",
"calculate_posture",
"recognition_enable",
"auto_train",
"auto_quit",
"output_prefix",
"manual_matches",
"manual_splits"
};
_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, std::string tab) mutable
{
if(!path.empty()) {
auto tmp = path;
if (tmp.has_extension() && tmp.extension() == "pv")
tmp = tmp.remove_extension();
SETTING(filename) = tmp;
std::string str = "";
bool first = true;
for(auto && [key, ptr] : pointers) {
std::string val;
auto textfield = dynamic_cast<gui::Textfield*>(ptr);
if(!textfield) {
//! assume its a checkbox:
auto check = dynamic_cast<gui::Checkbox*>(ptr);
if(check)
val = Meta::toStr(check->checked());
else {
auto drop = dynamic_cast<gui::Dropdown*>(ptr);
if(drop) {
auto item = drop->selected_item();
if(item.ID() != -1) {
auto name = item.search_name();
Debug("Selected '%S' = %S", &key, &name);
val = name;
} else
val = drop->text();
} else {
Debug("Unknown type for field '%S'", &key);
}
}
} else {
val = textfield->text();
}
if(start_values[key] != val) {
Debug("%S = %d", &key, &val);
if(!first)
str += "\n";
str += key + "=" + val;
first = false;
}
}
str += "\n";
if(!first)
_result.extra_command_lines = str;
_result.tab = _file_chooser->current_tab();
_result.tab.content = nullptr;
_result.selected_file = _file_chooser->confirmed_file();
if(_result.tab.extension == "pv") {
// PV file, no need to add cmd
} else if(!_result.selected_file.empty()) {
auto add = TEMP_SETTING(cmd_parameters).value<std::string>();
_result.cmd = "-i '" + path.str() + "' " + "-o '"+TEMP_SETTING(output_name).value<file::Path>().str()+"' -threshold "+TEMP_SETTING(threshold).get().valueString()+" -average_samples "+TEMP_SETTING(average_samples).get().valueString()
+" -averaging_method "+TEMP_SETTING(averaging_method).get().valueString()
+(add.empty() ? "" : " ")+add;
}
if(_load_results_checkbox && _load_results_checkbox->checked()) {
_result.load_results = true;
_result.load_results_from = "";
}
}
}, [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->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;
_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(!_buffer->_terminated_background_task) {
if(!contains(_raw_info->children(), (Drawable*)_loading_text.get()))
_raw_info->add_child(2, _loading_text);
//_loading_text->set_pos(_screenshot->pos());
_loading_text->set_txt("generating average ("+Meta::toStr(min(TEMP_SETTING(average_samples).value<int>(), (int)_buffer->_number_samples.load()))+"/"+TEMP_SETTING(average_samples).get().valueString()+")");
} else if(contains(_raw_info->children(), (Drawable*)_loading_text.get())) {
_raw_info->remove_child(_loading_text);
}
}
});
_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->on_open([this](auto){
_buffer = nullptr;
});
_file_chooser->on_tab_change([this](auto){
_buffer = nullptr;
});
_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_video_index = 0;
_accumulator = std::make_unique<AveragingAccumulator<>>(TEMP_SETTING(averaging_method).value<averaging_method_t::Class>());
_accumulator->add(img);
_background_thread = std::make_unique<std::thread>([this](){
_terminated_background_task = false;
int step = max(1, int(_background_video->length() / max(2.0, double(TEMP_SETTING(average_samples).value<int>()))));
cv::Mat flt, img;
_number_samples = 0;
while(!_terminate_background && _background_video_index+1+step < _background_video->length()) {
_background_video_index += step;
_number_samples += 1;
_background_video->frame(_background_video_index, img);
if(max(img.cols, img.rows) > 500)
resize_image(img, 500 / double(max(img.cols, img.rows)));
_accumulator->add(img);
auto image = _accumulator->finalize();
std::lock_guard guard(_frame_mutex);
_background_copy = std::move(image);
}
_terminated_background_task = true;
});
}
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;
_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(_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());
}
}
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;
std::lock_guard guard(_file_chooser->graph()->lock());
if(_file_chooser->current_tab().extension != "pv") {
try {
if(p.empty())
U_EXCEPTION("No file selected.");
Debug("Opening '%S'", &p.str());
std::lock_guard guard(_video_mutex);
{
TEMP_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();
TEMP_SETTING(output_name) = filename;
_text_fields["output_name"]->update();
}
_buffer = std::make_unique<BufferedVideo>(p);
_buffer->open();
_screenshot->set_source(std::move(_buffer->next()));
_screenshot_previous_size = 0;
try {
_buffer->_threshold = TEMP_SETTING(threshold).value<int>();
} catch(const std::exception &e) {
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});
} 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;
}
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.remove_extension();
SETTING(filename) = p.remove_extension();
Path settings_file = pv::DataLocation::parse("settings");
sprite::Map tmp;
tmp.set_do_print(false);
GlobalSettings::docs_map_t docs;
default_config::get(tmp, docs, [](auto, auto){});
docs.clear();
pointers.clear();
start_values.clear();
if(settings_file.exists()) {
try {
GlobalSettings::load_from_string(
default_config::deprecations(),
tmp,
utils::read_file(settings_file.str()),
AccessLevelType::STARTUP,
true);
} catch(const cmn::illegal_syntax& e) {
Warning("File '%S' has illegal syntax: %s", &_selected.str(), e.what());
}
}
std::vector<Layout::Ptr> children {
Layout::Ptr(std::make_shared<Text>("Settings", Vec2(), White, gui::Font(0.8, Style::Bold)))
};
for(auto &name : _settings_to_show) {
std::string start;
if(tmp[name].is_type<std::string>())
start = tmp[name].value<std::string>();
else
start = tmp[name].get().valueString();
if(tmp[name].is_type<bool>()) {
children.push_back( Layout::Ptr(std::make_shared<Checkbox>(Vec2(), name, tmp[name].get().value<bool>(), gui::Font(0.6))) );
} else if(name == "output_prefix") {
std::vector<std::string> folders;
for(auto &p : _selected.remove_filename().find_files()) {
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
}
}
children.push_back( Layout::Ptr(std::make_shared<Text>(name, Vec2(), White, gui::Font(0.6))) );
children.push_back( Layout::Ptr(std::make_shared<Dropdown>(Bounds(0, 0, 300, 28), folders)) );
((Dropdown*)children.back().get())->textfield()->set_font(Font(0.6));
} else {
children.push_back( Layout::Ptr(std::make_shared<Text>(name, Vec2(), White, gui::Font(0.6))) );
children.push_back( Layout::Ptr(std::make_shared<Textfield>(start, Bounds(0, 0, 300, 28))));
((Textfield*)children.back().get())->set_font(Font(0.6));
}
if(name == "output_prefix") {
((Dropdown*)children.back().get())->on_select([dropdown = ((Dropdown*)children.back().get()), this](long_t, const Dropdown::TextItem & item)
{
_output_prefix = item.search_name();
dropdown->set_opened(false);
_file_chooser->execute([this](){
SETTING(output_prefix) = _output_prefix;
select_file(_selected);
});
});
((Dropdown*)children.back().get())->textfield()->on_enter([dropdown = ((Dropdown*)children.back().get()), this]()
{
_output_prefix = dropdown->textfield()->text();
dropdown->set_opened(false);
_file_chooser->execute([this](){
SETTING(output_prefix) = _output_prefix;
select_file(_selected);
});
});
if(_output_prefix.empty())
((Dropdown*)children.back().get())->select_item(-1);
else {
auto items = ((Dropdown*)children.back().get())->items();
for(size_t i=0; i<items.size(); ++i) {
if(items.at(i).search_name() == _output_prefix) {
((Dropdown*)children.back().get())->select_item(i);
break;
}
}
((Dropdown*)children.back().get())->textfield()->set_text(_output_prefix);
}
}
pointers[name] = children.back().get();
start_values[name] = start;
}
_load_results_checkbox = nullptr;
auto path = Output::TrackingResults::expected_filename();
if(path.exists()) {
children.push_back( Layout::Ptr(std::make_shared<Checkbox>(Vec2(), "load results", false, gui::Font(0.6))) );
_load_results_checkbox = dynamic_cast<Checkbox*>(children.back().get());
} else
children.push_back( Layout::Ptr(std::make_shared<Text>("No loadable results found.", Vec2(), Gray, gui::Font(0.7, Style::Bold))) );
_extra->set_children(children);
_extra->auto_size(Margin{0,0});
_extra->update_layout();
try {
pv::File video(SETTING(filename).value<file::Path>());
video.start_reading();
auto text = video.get_info();
gui::derived_ptr<gui::Text> info_text = std::make_shared<gui::Text>("Selected", Vec2(), gui::White, gui::Font(0.8, gui::Style::Bold));
gui::derived_ptr<gui::StaticText> info_description = std::make_shared<gui::StaticText>(settings::htmlify(text), Vec2(), Size2(300, 600), gui::Font(0.5));
_infos->set_children({
info_text,
info_description
});
_infos->auto_size(Margin{0, 0});
_infos->update_layout();
} catch(...) {
Except("Caught an exception while reading info from '%S'.", &SETTING(filename).value<file::Path>().str());
}
_horizontal->auto_size(Margin{0, 0});
_horizontal->update_layout();
SETTING(filename) = file::Path();
}
}