-
Tristan Walter authoredTristan Walter authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
InfoCard.cpp 15.56 KiB
#include "InfoCard.h"
#include <gui/gui.h>
#include <tracking/Tracker.h>
#include <tracking/Recognition.h>
#include <gui/Timeline.h>
namespace gui {
InfoCard::InfoCard()
: prev(std::make_shared<Button>("prev", Bounds(10, 0, 90, 25), Color(100, 100, 100, 200))),
next(std::make_shared<Button>("next", Bounds(105, 0, 90, 25), Color(100, 100, 100, 200))),
detail_button(std::make_shared<Button>("detail", Bounds(Vec2(), Size2(50,20)), Color(100, 100, 100, 200))),
_frameNr(-1),
_fish(nullptr)
{
}
void InfoCard::update() {
if(!content_changed())
return;
if(_fish) {
Tracker::LockGuard guard("InfoCard::update", 10);
if(!guard.locked())
return;
}
Color bg(50,50,50,125);
begin();
auto &cache = GUI::instance()->cache();
if(!cache.has_selection() || !_fish) {
end();
return;
}
auto clr = _fish->identity().color();
if(clr.r < 80) clr = clr + clr * ((80 - clr.r) / 80.f);
else if(clr.g < 80) clr = clr + clr * ((80 - clr.g) / 80.f);
else if(clr.b < 80) clr = clr + clr * ((80 - clr.b) / 80.f);
//auto layout = std::make_shared<VerticalLayout>(Vec2(10, 10));
advance(new Text(_fish->identity().name(), Vec2(11,11), White.alpha(clr.a * 0.7), Font(0.9, Style::Bold)));
auto text = advance(new Text(_fish->identity().name(), Vec2(10, 10), clr, Font(0.9, Style::Bold)));
if(!_fish->has(_frameNr)) {
advance(new Text(" (inactive)", text->pos() + Vec2(text->width(), 0), Gray.alpha(clr.a), Font(0.9, Style::Bold)));
}
auto segments = _fish->frame_segments();
auto add_segments = [txt = text, this
#if DEBUG_ORIENTATION
,fish
#endif
](const auto& segments, float offx) {
auto text = advance(new Text(Meta::toStr(segments.size())+" segments", txt->pos() + Vec2(offx, Base::default_line_spacing(txt->font())), White, Font(0.8)));
#if DEBUG_ORIENTATION
auto reason = fish->why_orientation(frameNr);
std::string reason_str = "(none)";
if(reason.frame == frameNr) {
reason_str = reason.flipped_because_previous ? "previous_direction" : "(none)";
}
advance(new Text(reason_str, text->pos() + Vec2(0, Base::default_line_spacing(text->font())), White, Font(0.8)));
#endif
auto range_of = [](const auto& rit) -> const FrameRange& {
using value_t = typename remove_cvref<decltype(rit)>::type;
using SegPtr_t = std::shared_ptr<Individual::SegmentInformation>;
if constexpr(std::is_same<value_t, FrameRange>::value) return rit;
else if constexpr(std::is_same<value_t, SegPtr_t>::value) return *rit;
else if constexpr(is_pair<value_t>::value) return rit.second;
else if constexpr(is_pair<typename remove_cvref<decltype(*rit)>::type>::value) return (*rit).second;
else if constexpr(std::is_same<decltype((*rit)->range), FrameRange>::value) return (*rit)->range;
else if constexpr(std::is_same<typename remove_cvref<decltype(*rit)>::type, std::shared_ptr<track::Individual::SegmentInformation>>::value)
return *(*rit);
else return *rit;
};
// draw segment list
auto rit = segments.rbegin();
long_t current_segment = -1;
for(; rit != segments.rend(); ++rit) {
if(range_of(*rit).end() < _frameNr)
break;
current_segment = range_of(*rit).start();
if(range_of(*rit).start() <= _frameNr)
break;
}
long_t i=0;
while(rit != segments.rend() && ++rit != segments.rend() && ++i < 2);
i = 0;
auto it = rit == segments.rend()
? segments.begin()
: track::find_frame_in_sorted_segments(segments.begin(), segments.end(), range_of(rit).start());
auto it0 = it;
for (; it != segments.end() && cmn::abs(std::distance(it0, it)) < 5; ++it, ++i)
{
auto str = std::to_string(range_of(it).start())+"-"+std::to_string(range_of(it).end());
auto p = Vec2(width() - 10 + offx, float(height() - 40) * 0.5 + ((i - 2) + 1) * (float)Base::default_line_spacing(Font(1.1)));
text = advance(new Text(str, p, White.alpha(25 + 230 * (1 - cmn::abs(i-2) / 5.0)), Font(0.8), Vec2(1), Vec2(1, 0.5)));
if(range_of(*it).start() == current_segment) {
bool inside = range_of(*it).contains(_frameNr);
auto offy = - (inside ? 0.f : (Base::default_line_spacing(Font(1.1))*0.5));
advance(new Line(Vec2(10+offx, p.y + offy), Vec2(text->pos().x - text->width() - 10, p.y + offy), inside ? White : White.alpha(125)));
}
}
};
add_segments(_fish->frame_segments(), 0);
add_segments(_fish->recognition_segments(), 200);
static bool first = true;
if(first) {
prev->on_click([](auto) {
auto & cache = GUI::instance()->cache();
long_t next_frame = GUI::frame();
if(cache.has_selection()) {
Tracker::LockGuard guard("InfoCard::update->prev->on_click");
auto segment = cache.primary_selection()->get_segment(next_frame);
if(next_frame == segment.start())
next_frame = cache.primary_selection()->get_segment(segment.start()-1).start();
else
next_frame = segment.start();
}
if(next_frame == -1)
return;
if(SETTING(gui_frame).value<long_t>() != next_frame)
SETTING(gui_frame) = next_frame;
});
next->on_click([](auto) {
auto & cache = GUI::instance()->cache();
long_t next_frame = GUI::frame();
if(cache.has_selection()) {
Tracker::LockGuard guard("InfoCard::update->next->on_click");
auto segment = cache.primary_selection()->get_segment(next_frame);
if(segment.start() != -1) {
auto it = cache.primary_selection()->find_segment_with_start(segment.start());
++it;
if(it == cache.primary_selection()->frame_segments().end()) {
next_frame = -1;
} else {
next_frame = (*it)->start();
}
} else
next_frame = -1;
}
if(next_frame == -1)
return;
if(SETTING(gui_frame).value<long_t>() != next_frame)
SETTING(gui_frame) = next_frame;
});
first = false;
}
next->set_pos(Vec2(next->pos().x, height() - next->height() - 10));
prev->set_pos(Vec2(10, next->pos().y));
advance_wrap(*next);
advance_wrap(*prev);
float y = Base::default_line_spacing(Font(1.1)) * 8 + 40;
bool fish_has_frame = _fish->has(_frameNr);
if(!fish_has_frame)
bg = Color(100, 100, 100, 125);
auto fdx = _fish->identity().ID();
auto fprobs = cache.probs(fdx);
if(fprobs) {
bool detail = SETTING(gui_show_detailed_probabilities);
Bounds tmp(0, y - 10, 200, 0);
auto idx = index();
if(idx < children().size() && children().at(idx)->type() == Type::RECT)
tmp.size() = children().at(idx)->size();
float max_w = 200;
auto rect = advance(new Rect(tmp, bg.alpha(detail ? 50 : bg.a)));
auto text = advance(new Text("matching", Vec2(10, y), White, Font(0.8, Style::Bold)));
if(!detail_button->parent()) {
detail_button->set_toggleable(true);
detail_button->set_toggle(SETTING(gui_show_detailed_probabilities));
detail_button->clear_event_handlers();
detail_button->on_click([](auto) {
if(GUI::instance())
SETTING(gui_show_detailed_probabilities) = !SETTING(gui_show_detailed_probabilities);
});
}
detail_button->set_pos(Vec2(text->width() + text->pos().x + 15, y + (text->height() - detail_button->height()) * 0.5));
advance_wrap(*detail_button);
if(detail_button->pos().x + detail_button->width() + 10 > max_w)
max_w = detail_button->pos().x + detail_button->width() + 10;
if(_fish->is_automatic_match(_frameNr)) {
y += text->height();
text = advance(new Text("(automatic match)", Vec2(10, y), White.alpha(150), Font(0.8, Style::Italic)));
y += text->height();
if(!automatic_button) {
automatic_button = std::make_shared<Button>("delete", Bounds(10, y, 50, 20), Color(100, 200));
automatic_button->on_click([this](auto){
Tracker::LockGuard guard("InfoCard::update->delete->on_click");
if(_fish) {
auto range = _fish->get_segment_safe(_frameNr);
if(!range.empty()) {
Debug("Erasing automatic matches for fish %d in range %d-%d", _fish->identity().ID(), range.start(), range.end());
Tracker::delete_automatic_assignments(_fish->identity().ID(), range);
GUI::reanalyse_from(_frameNr);
}
}
});
}
advance_wrap(*automatic_button);
y += automatic_button->height();
if(text->width() + text->pos().x + 10 > max_w)
max_w = text->width() + text->pos().x + 10;
} else
y += text->height();
std::string speed_str = Meta::toStr(cache.processed_frame.cached_individuals.at(fdx).speed) + "cm/s";
y += advance(new Text(speed_str, Vec2(10, y), White.alpha(125), Font(0.8)))->height();
track::Match::prob_t max_prob = 0;
int64_t bdx = -1;
for(auto blob : cache.processed_frame.blobs) {
if(fprobs->count(blob->blob_id())) {
auto &probs = (*fprobs).at(blob->blob_id());
if(probs.p > max_prob) {
max_prob = probs.p;
bdx = (int64_t)blob->blob_id();
}
}
}
for(auto blob : cache.processed_frame.blobs) {
if(fprobs->count(blob->blob_id())) {
auto color = Color(200, 200, 200, 255);
if(cache.fish_selected_blobs.find(fdx) != cache.fish_selected_blobs.end() && (long_t)blob->blob_id() == cache.fish_selected_blobs.at(fdx)) {
color = Green;
} else if(blob->blob_id() == bdx) {
color = Yellow;
}
auto &probs = (*fprobs).at(blob->blob_id());
auto probs_str = Meta::toStr(probs.p);
if(detail)
probs_str += " (p:"+Meta::toStr(probs.p_pos)+" a:"+Meta::toStr(probs.p_angle)+" s:"+Meta::toStr(probs.p_pos / probs.p_angle)+" t:"+Meta::toStr(probs.p_time)+")";
auto text = advance(new Text(Meta::toStr(blob->blob_id())+": ", Vec2(10, y), White, Font(0.8)));
auto second = advance(new Text(probs_str, text->pos() + Vec2(text->width(), 0), color, Font(0.8)));
y += text->height();
auto w = second->pos().x + second->width() + 10;
if(w > max_w)
max_w = w;
}
}
tmp.width = max_w;
tmp.height = y - tmp.y + 10;
rect->set_size(tmp.size());
}
y += 30;
if(Recognition::recognition_enabled() && fish_has_frame) {
Bounds tmp(0, y-10, 200, 0);
auto rect = advance(new Rect(tmp, bg.alpha(bg.a)));
auto idx = index();
if(idx < children().size() && children().at(idx)->type() == Type::RECT)
tmp.size() = children().at(idx)->size();
auto blob_id = _fish->compressed_blob(_frameNr)->blob_id();
auto && [valid, segment] = _fish->has_processed_segment(_frameNr);
std::map<long_t, float> raw;
std::string title = "recognition";
if(valid) {
auto && [n, values] = _fish->processed_recognition(segment.start());
title = "average n:"+Meta::toStr(n);
raw = values;
} else {
raw = Tracker::recognition()->ps_raw(_frameNr, blob_id);
}
float p_sum = 0;
for(auto && [key, value] : raw)
p_sum = max(p_sum, value);
float max_w = 200;
auto text = advance(new Text(title, Vec2(10, y), White, Font(0.8, Style::Bold)));
y += text->height();
max_w = max(max_w, 10 + text->width() + text->pos().x);
text = advance(new Text(Meta::toStr(segment), Vec2(10, y), Color(220,220,220,255), Font(0.8, Style::Italic)));
y += text->height();
max_w = max(max_w, 10 + text->width() + text->pos().x);
if(!raw.empty())
y += 5;
long_t mdx = -1;
float mdx_p = 0;
for(auto&& [fdx, p] : raw) {
if(p > mdx_p) {
mdx_p = p;
mdx = fdx;
}
}
Vec2 current_pos(10, y);
float _max_y = y;
float _max_w = 0;
size_t column_count = 0;
for(auto [fdx, p] : raw) {
p *= 100;
p = roundf(p);
p /= 100;
std::string str = Meta::toStr(fdx) + ": " + Meta::toStr(p);
Color color = White * (1 - p/p_sum) + Red * (p / p_sum);
auto text = advance(new Text(str, current_pos, color, Font(0.8)));
auto w = text->pos().x + text->width() + 10;
if(w > max_w)
max_w = w;
if(w > _max_w)
_max_w = w;
current_pos.y += text->height();
++column_count;
if(current_pos.y > _max_y)
_max_y = current_pos.y;
if(column_count > 25) {
column_count = 0;
current_pos.y = y;
current_pos.x = _max_w;
}
}
tmp.width = max_w;
tmp.height = _max_y - tmp.y + 10;
rect->set_size(tmp.size());
}
end();
set_background(bg);
}
void InfoCard::update(gui::DrawStructure &base, long_t frameNr) {
auto fish = GUI::cache().primary_selection();
if(_fish != fish) {
if(_fish) {
_fish->unregister_delete_callback(this);
}
fish->register_delete_callback(this, [this](Individual*) {
if(!GUI::instance())
return;
std::lock_guard<std::recursive_mutex> guard(GUI::instance()->gui().lock());
_fish = nullptr;
set_content_changed(true);
});
}
if(frameNr != _frameNr || _fish != fish) {
set_content_changed(true);
_frameNr = frameNr;
_fish = fish;
}
set_origin(Vec2(0, 0));
set_bounds(Bounds((10) / base.scale().x, 100 / base.scale().y, 200, Base::default_line_spacing(Font(1.1)) * 7 + 60));
set_scale(base.scale().reciprocal());
}
}