#include "DrawMenu.h" #include <types.h> #include <gui/Timeline.h> #include <gui/types/List.h> #include <misc/Output.h> #include <gui/gui.h> #include <gui/WorkProgress.h> #include <tracking/Tracker.h> #include <gui/types/StaticText.h> #include <gui/types/Tooltip.h> #include <tracking/Individual.h> #include <misc/MemoryStats.h> namespace gui { class BlobItem : public gui::List::Item { protected: GETTER_SETTER(std::string, name) public: BlobItem(const std::string& n = "", long_t bdx = -1) : gui::List::Item(bdx), _name(n) { } operator const std::string&() const override { return _name; } bool operator==(const gui::List::Item& other) const override { return other.ID() == ID(); } void operator=(const gui::List::Item& other) override { gui::List::Item::operator=(other); assert(dynamic_cast<const BlobItem*>(&other)); _name = static_cast<const BlobItem*>(&other)->_name; } }; class ItemIndividual : public gui::List::Item { protected: GETTER_SETTER(std::string, name) GETTER_SETTER(long_t, ptr) GETTER_SETTER(long_t, selected_blob_id) public: ItemIndividual(long_t fish = -1, long_t blob = -1) : gui::List::Item(fish), _ptr(fish), _selected_blob_id(blob) { if(fish != -1) { Identity id((idx_t)_ptr); _name = id.name(); } } void operator=(const ItemIndividual& other) { Item::operator=(other); auto p = dynamic_cast<const ItemIndividual*>(&other); _name = p->_name; _ptr = p->_ptr; _selected_blob_id = p->_selected_blob_id; } operator const std::string&() const override { return _name; } bool operator==(const gui::List::Item& other) const override { auto p = dynamic_cast<const ItemIndividual*>(&other); if(!p) return false; return p->_ptr == _ptr && p->_selected_blob_id == _selected_blob_id && p->_name == _name; } }; class DrawMenuPrivate { enum Actions { LOAD = 0, LOAD_SETTINGS, SAVE, CONFIG, TRAINING, FACES, DEBUG, CLEAR, CHECK, OUTPUT, EXPORT, EXPORT_VF, START_VALIDATION, QUIT }; protected: gui::derived_ptr<gui::List> menu; GETTER(gui::derived_ptr<gui::List>, list) gui::derived_ptr<gui::List> second_list; gui::derived_ptr<gui::List> foi_list; gui::derived_ptr<gui::HorizontalLayout> layout; gui::derived_ptr<Button> reanalyse; std::vector<std::shared_ptr<gui::List::Item>> _individual_items; std::vector<std::shared_ptr<gui::List::Item>> _blob_items; std::vector<std::shared_ptr<List::Item>> _foi_items; std::set<long_t> _foi_ids; public: DrawMenuPrivate() { layout = std::make_shared<HorizontalLayout>(std::vector<Layout::Ptr>{}, Vec2(), Bounds(5, 5)); _list = std::make_shared<gui::List>( Bounds(Tracker::average().cols - 300 - 110 - 10 - 80 * 3, 7, 150, 33), "match", std::vector<std::shared_ptr<List::Item>>{}, [this](gui::List*, const List::Item& item){ if(item == _list->selected_item()) { } else { second_list->set_title("Blobs for "+(const std::string&)item); Debug("clicked '%S'", &(const std::string&)item); } } ); _list->set_toggle(true); _list->set_folded(true); _list->on_toggle([](){ { std::lock_guard<std::recursive_mutex> guard(GUI::instance()->gui().lock()); GUI::cache().set_tracking_dirty(); } GUI::instance()->set_redraw(); }); second_list = std::make_shared<gui::List>(Bounds(Tracker::average().cols - 581 - 110 - 10 - 80 * 2, 7, 200, 33), "blobs", std::vector<std::shared_ptr<List::Item>>{}, [this](List*, const List::Item& item) { Debug("%d %d", item.ID(), item.selected()); if(!item.selected() && item.ID() >= 0) { GUI::instance()->add_manual_match(GUI::instance()->frameinfo().frameIndex, _list->selected_item() >= 0 ? (idx_t)_list->selected_item() : std::numeric_limits<idx_t>::max(), item.ID()); } } ); second_list->set_toggle(true); second_list->set_foldable(false); menu = std::make_shared<gui::List>(Bounds(Vec2(), Size2(150,33)), "menu", std::vector<std::shared_ptr<List::Item>>{ std::make_shared<TextItem>("load state", LOAD), std::make_shared<TextItem>("save state", SAVE), std::make_shared<TextItem>("save config", CONFIG), std::make_shared<TextItem>("train network", TRAINING), std::make_shared<TextItem>("load settings", LOAD_SETTINGS), //std::make_shared<TextItem>("training faces", FACES), std::make_shared<TextItem>("auto correct", CHECK), std::make_shared<TextItem>("clear auto-matches", CLEAR), std::make_shared<TextItem>("debug posture", DEBUG), std::make_shared<TextItem>("export tracking data", EXPORT), std::make_shared<TextItem>("export visual field", EXPORT_VF), std::make_shared<TextItem>("validation", START_VALIDATION), std::make_shared<TextItem>("quit", QUIT) }, [this](auto, const List::Item& item) { auto gPtr = GUI::instance(); switch((Actions)item.ID()) { case LOAD: gPtr->load_state(GUI::GUIType::GRAPHICAL); break; case LOAD_SETTINGS: gPtr->work().add_queue("", [gPtr](){ gPtr->gui().dialog([](Dialog::Result result) { if(result == Dialog::SECOND) { // load from results auto path = Output::TrackingResults::expected_filename(); if(path.exists()) { try { auto header = Output::TrackingResults::load_header(path); default_config::warn_deprecated(path.str(), GlobalSettings::load_from_string(default_config::deprecations(), GlobalSettings::map(), header.settings, AccessLevelType::PUBLIC)); } catch(const UtilsException& e) { GUI::instance()->gui().dialog([](Dialog::Result){}, "Cannot load settings from results file. Check out this error message:\n<i>"+std::string(e.what())+"</i>", "Error"); Except("Cannot load settings from results file. Skipping that step..."); } } } else if(result == Dialog::OKAY) { // load from settings file auto settings_file = pv::DataLocation::parse("settings"); GUI::execute_settings(settings_file, AccessLevelType::PUBLIC); auto output_settings = pv::DataLocation::parse("output_settings"); if(output_settings.exists() && output_settings != settings_file) GUI::execute_settings(output_settings, AccessLevelType::STARTUP); } }, "Loading settings will replace values of currently loaded settings. Where do you want to source from?", "load settings", "from .settings", "cancel", "from results"); }); break; case SAVE: gPtr->save_state(); break; case CONFIG: gPtr->write_config(false); break; case TRAINING: gPtr->training_data_dialog(); break; case EXPORT: gPtr->export_tracks(); break; case EXPORT_VF: gPtr->work().add_queue("saving visual fields...", [](){ GUI::instance()->save_visual_fields(); }); break; case START_VALIDATION: ConfirmedCrossings::start(); break; case CHECK: gPtr->auto_correct(); break; case CLEAR: { Tracker::instance()->clear_segments_identities(); Debug("Cleared all averaged probabilities and automatic matches."); } break; case DEBUG: if(GUI::cache().has_selection()) { auto it = GUI::cache().fish_selected_blobs.find(GUI::cache().selected.front()); if(it != GUI::cache().fish_selected_blobs.end()) { SETTING(gui_show_fish) = std::pair<int64_t, long_t>(it->second, GUI::frame()); GUI::reanalyse_from(GUI::frame()); SETTING(analysis_paused) = false; } } break; case QUIT: gPtr->confirm_terminate(); break; default: Warning("Unknown action '%S'.", &(const std::string&)item); } menu->set_folded(true); }); menu->set_folded(true); foi_list = std::make_shared<gui::List>(Size2(150, 33), "foi type", std::vector<std::shared_ptr<List::Item>>{}, [&](auto, const List::Item& item) { SETTING(gui_foi_name) = ((TextItem)item).text(); foi_list->set_folded(true); }); reanalyse = std::make_shared<Button>("reanalyse", Size2(100, 33)); reanalyse->on_click([&](auto){ GUI::reanalyse_from(GUI::frame()); SETTING(analysis_paused) = false; }); layout->set_origin(Vec2(1, 0)); layout->set_policy(HorizontalLayout::Policy::TOP); std::vector<Layout::Ptr> tmp {foi_list, reanalyse, menu}; layout->set_children(tmp); } void matching_gui() { /** * ----------------------------- * DISPLAY MANUAL MATCHING GUI * ----------------------------- */ { /** * Try and match the last displayed fish items to the currently existing ones */ struct FishAndBlob { idx_t fish; long_t blob; FishAndBlob(idx_t fish = 0, long_t blob = -1) : fish(fish), blob(blob) {} void convert(std::shared_ptr<List::Item> ptr) { Identity id(fish); auto obj = static_cast<ItemIndividual*>(ptr.get()); auto name = id.name(); if(fish != obj->ID() || blob != obj->selected_blob_id() || name != obj->name()) { obj->set_ID(fish); obj->set_name(name); obj->set_ptr(fish); obj->set_selected_blob_id(blob); } } }; std::vector<std::shared_ptr<FishAndBlob>> fish_and_blob; if(!FAST_SETTINGS(manual_identities).empty()) { for(auto id : FAST_SETTINGS(manual_identities)) { fish_and_blob.push_back(std::make_shared<FishAndBlob>(id, GUI::cache().fish_selected_blobs.count(id) ? GUI::cache().fish_selected_blobs.at(id) : -1)); } } else { for(auto id : GUI::cache().active_ids) fish_and_blob.push_back(std::make_shared<FishAndBlob>(id, GUI::cache().fish_selected_blobs.at(id))); //for(auto &id : GUI::cache().inactive_ids) // fish_and_blob.push_back(std::make_shared<FishAndBlob>(id, -1)); } update_vector_elements<List::Item, ItemIndividual>(_individual_items, fish_and_blob); } if(_individual_items.size() < 100) { _list->set_items(_individual_items); //base.wrap_object(_list); if(_list->selected_item() != -1 && !_list->folded()) { /** * Try and match the last displayed blob items to the currently relevant ones */ struct BlobID { long_t id; BlobID(long_t id = -1) : id(id) {} void convert(std::shared_ptr<List::Item> ptr) { auto item = static_cast<BlobItem*>(ptr.get()); if(item->ID() != id || (id == -1 && item->name() != "none")) { item->set_ID(id); if(id != -1) { std::string fish; for(auto && [fdx, bdx] : GUI::instance()->cache().fish_selected_blobs) { if(bdx == id) { fish = GUI::instance()->cache().individuals.at(fdx)->identity().name(); break; } } item->set_name(fish.empty() ? "blob"+std::to_string(id) : fish+" ("+Meta::toStr(id)+")"); } else item->set_name("none"); } } }; // find currently selected individual long_t selected_individual = _list->selected_item(); long_t selected = -1; for(auto x : _individual_items) { if(x->ID() == selected_individual) { selected = ((ItemIndividual*)x.get())->selected_blob_id(); break; } } // generate blob items std::map<uint32_t, std::shared_ptr<BlobID>> ordered; std::vector<std::shared_ptr<BlobID>> blobs = {std::make_shared<BlobID>(-1)}; for(auto v : GUI::cache().raw_blobs) ordered[v->blob->blob_id()] = std::make_shared<BlobID>(v->blob->blob_id()); for(auto && [id, ptr] : ordered) blobs.push_back(ptr); update_vector_elements<List::Item, BlobItem>(_blob_items, blobs); // set items and display second_list->set_items(_blob_items); second_list->select_item(selected); GUI::instance()->gui().wrap_object(*second_list); } } } Timer memory_timer; mem::IndividualMemoryStats overall; gui::derived_ptr<Entangled> stats; long_t last_end_frame; void memory_stats() { if(!stats) { stats = std::make_shared<Entangled>(); last_end_frame = -1; } auto &base = GUI::instance()->gui(); stats->set_scale(base.scale().reciprocal()); Size2 ddsize = /*_base ? _base->window_dimensions() :*/ Size2(base.width(), base.height()); static Tooltip* tooltip = nullptr; if(!tooltip) { tooltip = new Tooltip(nullptr, 300); } if((overall.id == -1 || memory_timer.elapsed() > 10) && GUI::cache().tracked_frames.end != last_end_frame) { Tracker::LockGuard guard("memory_stats", 100); if(guard.locked()) { overall.clear(); last_end_frame = GUI::cache().tracked_frames.end; for(auto && [fdx, fish] : GUI::cache().individuals) { if(!GUI::cache().has_selection() || GUI::cache().is_selected(fdx)) { mem::IndividualMemoryStats stats(fish); overall += stats; } } { mem::OutputLibraryMemoryStats stats; overall += stats; } { mem::TrackerMemoryStats stats; overall += stats; } std::set<std::string> to_delete; for(auto && [key, value] : overall.sizes) { if(!value) to_delete.insert(key); } for(auto key : to_delete) overall.sizes.erase(key); stats->set_origin(Vec2(0.5)); Size2 intended_size(ddsize.width * base.scale().x * 0.85, ddsize.height * base.scale().y * 0.3); float margin = intended_size.width * 0.005; stats->update([&](Entangled& base) { Size2 bars(intended_size.width / float(overall.sizes.size()), intended_size.height - margin * 2 - 20 * 3); uint64_t mi = std::numeric_limits<uint64_t>::max(), ma = 0; for(auto && [name, size] : overall.sizes) { if(size < mi) mi = size; if(size > ma) ma = size; } ColorWheel wheel; float x = margin; static std::vector<std::shared_ptr<StaticText>> elements; static Size2 previous_dimensions; if(previous_dimensions != bars) { tooltip->set_other(nullptr); elements.clear(); previous_dimensions = bars; } if(elements.size() < overall.sizes.size()) { elements.resize(overall.sizes.size()); } size_t i=0; for(auto && [name, size] : overall.sizes) { auto color = wheel.next(); double h = double((size - mi) / double(ma - mi)) * bars.height; //Debug("%S: %f (%lu, %lu)", &name, h, size - mi, ma - mi); base.advance(new Rect(Bounds(x + margin, margin + bars.height - h, bars.width - margin * 2, h), color)); auto text = elements.at(i); auto pos = Vec2(x + bars.width * 0.5, margin + bars.height + margin); if(!text) { text = std::make_shared<StaticText>(utils::trim(utils::find_replace(name, "_", " ")) + "\n<ref>" + Meta::toStr(FileSize{size})+"</ref>", pos, Vec2(bars.width, 20)); elements.at(i) = text; text->set_origin(Vec2(0.5, 0)); text->set_background(Transparent, Transparent); text->set_margins(Bounds()); auto it = overall.details.find(name); if(it != overall.details.end()) { text->add_custom_data("tttext", (void*)new std::string(), [](void* ptr) { delete (std::string*)ptr; }); text->set_clickable(true); text->on_hover([text](Event e){ if(e.hover.hovered) { tooltip->set_text(*(std::string*)text->custom_data("tttext")); tooltip->set_other(text.get()); } else if(tooltip && tooltip->other() == text.get()) { tooltip->set_other(nullptr); } }); } else if(text->custom_data("tttext")) { text->add_custom_data("tttext", (void*)nullptr); } } else { text->set_txt(utils::trim(utils::find_replace(name, "_", " ")) + "\n<ref>" + Meta::toStr(FileSize{size})+"</ref>"); text->set_pos(pos); } auto it = overall.details.find(name); if(it != overall.details.end()) { std::string str; for(auto && [key, size] : it->second) { auto k = utils::trim(utils::find_replace(key, "_", " ")); str += "<ref>"+key+"</ref>: "+Meta::toStr(FileSize{size})+"\n"; } if(!str.empty()) str = str.substr(0, str.length()-1); auto ptr = (std::string*)text->custom_data("tttext"); assert(ptr); *ptr = str; tooltip->set_content_changed(true); if(text->hovered()) { tooltip->set_text(str); } } base.advance_wrap(*text); //base.advance(new Text(name, Vec2(x + bars.width * 0.5, margin + bars.height + margin), White, Font(0.75, Align::Center))); //base.advance(new Text(Meta::toStr(FileSize{size}), Vec2(x + bars.width * 0.5, text->pos().y + text->height() + 20), Gray, Font(0.75, Align::Center))); x += bars.width; ++i; } auto str = Meta::toStr(FileSize{overall.bytes}); base.advance(new Text(str, Vec2(10, 10), White, Font(0.75))); }); //tooltip.set_scale(base.scale().reciprocal()); stats->auto_size(Margin{margin,margin}); stats->set_background(Black.alpha(125)); memory_timer.reset(); } } stats->set_pos(ddsize * 0.5); base.wrap_object(*stats); if(tooltip->other()) { base.wrap_object(*tooltip); tooltip->set_scale(base.scale().reciprocal()); } } void draw() { matching_gui(); if(_foi_items.empty() || _foi_ids != FOI::ids()) { _foi_items.clear(); _foi_ids = FOI::ids(); for(auto foid : FOI::ids()) { _foi_items.push_back(std::make_shared<TextItem>(FOI::name(foid), foid)); } foi_list->set_items(_foi_items); } auto &base = GUI::instance()->gui(); auto && [offset, max_w] = Timeline::timeline_offsets(); Vec2 pos = Vec2(max_w / base.scale().x - 10, 5) - offset / base.scale().x; layout->set_scale(base.scale().reciprocal()); layout->set_pos(pos); second_list->set_scale(layout->scale()); second_list->set_pos(_list->global_bounds().pos() - Vec2(second_list->global_bounds().width, 0)); base.wrap_object(*layout); if(_individual_items.size() < 100) { if(!contains(layout->children(), _list.get())) { //auto tmp = layout.children(); //tmp.insert(tmp.begin(), &_list); layout->add_child(0, _list.get()); //layout.set_children(tmp); } } else layout->remove_child(_list.get()); if(SETTING(gui_show_memory_stats)) { memory_stats(); } } static std::shared_ptr<DrawMenuPrivate>& instance() { static auto drawMenuPtr = std::make_shared<DrawMenuPrivate>(); return drawMenuPtr; } }; void DrawMenu::draw() { DrawMenuPrivate::instance()->draw(); } bool DrawMenu::matching_list_open() { return !DrawMenuPrivate::instance()->list().get()->folded(); } void DrawMenu::close() { DrawMenuPrivate::instance() = nullptr; } }