Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
EventAnalysis.cpp 13.75 KiB
#include "EventAnalysis.h"
#include <tracking/Tracker.h>
#include <numeric>
#include <misc/Timer.h>

namespace track {
namespace EventAnalysis {

float _limit = 0;
bool _callback_registered;

void update_settings(const sprite::Map &, const std::string &key, const sprite::PropertyType &type) {
    if(key == "limit")
        _limit = type.value<decltype(_limit)>();
}

    
    template <class Key, class Value>
    uint64_t mapCapacity(const std::map<Key,Value> &map){
        uint64_t cap = sizeof(map);
        for(typename std::map<Key,Value>::const_iterator it = map.begin(); it != map.end(); ++it){
            cap += sizeof(it->first);
            cap += it->second.memory_size();
        }
        return cap;
    }
    
    struct AnalysisState {
        bool in_tailbeat;
        long_t frame;
        std::deque<float> offsets;
        long_t last_threshold_reached;
        long_t last_event_start, last_event_end;
        bool last_event_sign;
        
        std::vector<float> current_energy;
        float current_maximum;
        std::vector<long_t> threshold_reached;
        
        float prev;
        float prev_raw;
        
        //Vec2 acc_velocity;
        Vec2 v_before;
        Vec2 v_current;
        float v_samples;
        
        AnalysisState() : in_tailbeat(false), frame(0), last_threshold_reached(-1), last_event_start(-1), last_event_end(-1), prev(0), prev_raw(infinity<float>()) {}
        size_t memory_size() const {
            return sizeof(AnalysisState)
                 + sizeof(decltype(threshold_reached)::value_type) * threshold_reached.capacity()
                 + sizeof(decltype(offsets)::value_type) * offsets.size();
        }
    };
    
    static std::map<Individual*, AnalysisState> states;
    static std::map<Individual*, EventMap> individual_maps;
    static std::string analysis_status = "";
    static std::mutex mutex;
    
    void fish_deleted(Individual *fish) {
        std::lock_guard<decltype(mutex)> guard(mutex);
        
        auto it = individual_maps.find(fish);
        if(it != individual_maps.end()) {
            individual_maps.erase(it);
            auto sit = states.find(fish);
            if(sit != states.end())
                states.erase(sit);
            
            //Debug("Fish has been deleted from EventAnalysis: %d", fish->identity().ID());
        }
    }
    
    bool threshold_reached(Individual* fish, long_t frame) {
        std::lock_guard<decltype(mutex)> guard(mutex);
        auto &state = states[fish];
        if(contains(state.threshold_reached, frame))
            return true;
        return false;
    }
    
    bool advance_analysis(Individual* fish, EventMap &map, bool insert = true)
    {
        auto &state = states[fish];
        
        if(state.frame < fish->start_frame())
            state.frame = fish->start_frame();
        
        if(state.frame > fish->end_frame())
            return false;
        
        // restrain cache
        if(state.offsets.size() > 25)
            state.offsets.pop_front();
        
        // push the next value
        state.offsets.push_back(midline_offset(fish, state.frame++));
        
        // ignore the first 2 frames
        // (generate smoothed offset, need previous and next value)
        if(state.offsets.size() < 3)
            return true;
        
        // assume state offsets is not empty
        assert(state.offsets.size() > 2);
        
        long_t frame = state.frame - 2;
        float previous = state.offsets.at(state.offsets.size()-3);
        float current = state.offsets.at(state.offsets.size()-2);
        float next = state.offsets.at(state.offsets.size()-1);
        
        if(cmn::isinf(next))
            next = current;
        if(cmn::isinf(previous))
            previous = current;
        
        current = cmn::isinf(current) ? infinity<float>() : ((previous + current + next) / 3);
        
        float offset = (cmn::isinf(current) || cmn::isinf(state.prev_raw))
                         ? current
                         : (current - (cmn::isinf(state.prev_raw) ? 0 : state.prev_raw));
        
        float prev = state.prev;
        state.prev = offset;
        state.prev_raw = current;
        
        Vec2 pt0(frame-1, cmn::isinf(prev) ? 0 : prev), pt1(frame, offset);
        
        //if(std::isinf(offset))
        //    state.current_energy.push_back(0);
        //else
            state.current_energy.push_back(0.5 * FAST_SETTINGS(meta_mass_mg) * SQR(offset));
        
        if(cmn::isinf(offset)) {
            if(state.last_event_start != -1) {
                state.last_threshold_reached = state.last_event_start = -1;
            }
        }
        else if(cmn::abs(offset) >= _limit || crosses_abs_height(pt0, pt1, _limit) != 0)
        {
            // current frame is above threshold
            state.last_threshold_reached = frame;
            state.threshold_reached.push_back(frame);
            
            if(state.last_event_start == -1) {
                state.last_event_start = frame;
                state.current_maximum = 0;
                state.current_energy.clear();
                state.v_before = state.v_samples ? state.v_current / state.v_samples : Vec2(0, 0);
                state.v_current = Vec2(0, 0);
                state.v_samples = 0;
            }
            
            state.current_maximum = max(cmn::abs(current), state.current_maximum);
            state.last_event_end = frame;
            state.last_event_sign = std::signbit(current);
            
            return true;
        }
        
        if(/*state.last_event_start != -1 &&*/ fish->centroid_posture(frame)) {
            state.v_current += fish->centroid_posture(frame)->v();
            state.v_samples++;
        }
        
        const float max_frames = roundf(max(5, 0.055f * FAST_SETTINGS(frame_rate)));
        if(state.last_threshold_reached != -1
           && frame - state.last_threshold_reached <= max_frames)
        {
            // extend until offset function reaches zero
            if(state.last_event_sign == std::signbit(current))
                state.last_event_end = frame;
            
            return true;
        }
        else if(state.last_event_start != -1) {
            if(insert && state.current_maximum >= SETTING(event_min_peak_offset).value<float>())
            {
                long_t len = state.last_event_end - state.last_event_start + 1;
                assert(len > 0);
                
                auto velocity = state.v_current / state.v_samples;
                auto d_angle = atan2(velocity.y, velocity.x) - atan2(state.v_before.y, state.v_before.x);
                float angle_change = atan2(sin(d_angle), cos(d_angle));
                float acceleration = length(state.v_before) - length(velocity);
                
                
                float energy = std::accumulate(state.current_energy.begin(), state.current_energy.begin() + len, 0.f); // len;
                if(std::isinf(energy))
                    U_EXCEPTION("Energy is infinite.");
                
                map.events[state.last_event_start] = Event(state.last_event_start, state.last_event_end, energy, angle_change, acceleration, length(state.v_before), length(velocity));
                map.lengths[state.last_event_start] = len;
                //Debug("%d: Adding event %d", fish->identity().ID(), state.last_event_start);
            }
            
            state.last_event_start = state.last_event_end = -1;
            state.current_maximum = 0;
        }
        
        return false;
    }

    float midline_offset(Individual *fish, long_t frame) {
        /*auto c = fish->head(frame);
        auto c0 = fish->head(frame-1);
        if(!c || !c0)
            return INFINITY;
        return (c->v(Units::PX_AND_SECONDS, true).length() - c0->v(Units::PX_AND_SECONDS, true).length()) * 0.15; //sqrt(c->acceleration(DEFAULT, true)) * 0.5;*/
        
        ///** ZEBRAFISH **
        auto midline = fish->fixed_midline(frame);
        if (!midline)
            return infinity<float>();
        
        auto posture = fish->posture_stuff(frame);
        if(!posture || !posture->cached())
            return infinity<float>();
        
        float ratio = posture->midline_length / midline->len();
        if(ratio > 1)
            ratio = 1/ratio;
        if(ratio < 0.6)
            return infinity<float>();
        
        auto &pts = midline->segments();
        auto complete = (pts.back().pos - pts.front().pos);
        
        complete /= length(complete);
        return cmn::atan2(complete.y, complete.x);
    }

    bool update_events(const std::set<Individual*>& individuals) {
        std::lock_guard<decltype(mutex)> guard(mutex);
        if(!_callback_registered) {
            _callback_registered = true;
            
            GlobalSettings::map().register_callback((void*)&_limit, update_settings);
            update_settings(GlobalSettings::map(), "limit", SETTING(limit).get());
        }
        
        Timer timer;
        
        long_t left_over = 0;
        
        using namespace EventAnalysis;
        
        for (auto& fish : individuals) {
            if(individual_maps.find(fish) == individual_maps.end())
                fish->register_delete_callback(&mutex, [](Individual* fish){
                    if(!Tracker::instance())
                        return;
                    fish_deleted(fish);
                });
            
            auto &map = individual_maps[fish];
            
            if(map.end_frame == -1) {
                map.start_frame = map.end_frame = fish->start_frame();
            }
            
            if(states[fish].frame - 1 > fish->end_frame()) {
                states[fish].frame = fish->end_frame() + 1;
            }
            
            long_t i = map.end_frame;
            for(; i<=fish->end_frame() && i - map.end_frame <= 10000; i++)
                advance_analysis(fish, map);
            
            left_over += fish->end_frame() - (states[fish].frame - 1);
            map.end_frame = i;
        }
        
        if(left_over < 0)
            left_over = 0;
        
        if(left_over) {
            static Timer timer;
            if(timer.elapsed() > 10) {
                analysis_status = "processing ("+std::to_string(left_over)+" frames left)";
                auto str = FileSize(mapCapacity(individual_maps) + mapCapacity(states)).to_string();
                Debug("Time: %.2fms (%S)", timer.elapsed() * 1000, &str);
                timer.reset();
            }
            
            return true;
            
        } else
            analysis_status = "";
        
        return false;
    }
    
    std::string status() {
        std::lock_guard<decltype(mutex)> guard(mutex);
        return analysis_status;
    }
    
    EventsContainer* events() {
        return new EventsContainer(mutex, individual_maps);
    }
    
    void reset_events(long_t after_frame) {
        std::lock_guard<decltype(mutex)> guard(mutex);
        
        if(after_frame == -1) {
            individual_maps.clear();
            states.clear();
            
        } else {
            auto copy = individual_maps;
            for(auto &c : copy) {
                if(c.first->start_frame() >= after_frame) {
                    individual_maps.erase(c.first);
                    states.erase(c.first);
                }
            }
            
            for(auto &map : individual_maps) {
                Debug("Erasing... %d(%d-%d): %d - %d", map.first->identity().ID(), map.first->start_frame(), map.first->end_frame(), map.second.start_frame, map.second.end_frame);
                if(map.second.start_frame != -1 && map.second.end_frame >= after_frame) {
                    size_t count = 0;
                    if(map.second.start_frame >= after_frame) {
                        count = map.second.end_frame - map.second.start_frame + 1;
                        map.second.clear();
                        
                    } else {
                        for(auto iter = map.second.events.begin(), endIter = map.second.events.end(); iter != endIter; )
                        {
                            if (iter->first >= after_frame) {
                                map.second.lengths.erase(iter->first);
                                map.second.events.erase(iter++);
                                count++;
                            } else {
                                ++iter;
                            }
                        }
                    }
                    
                    // reset analysis state
                    auto &state = states[map.first];
                    
                    // ... clear cache of midline offsets and regenerate
                    state.last_event_start = -1;
                    state.last_event_end = -1;
                    state.frame = min(map.first->end_frame(), max(after_frame - 100, map.first->start_frame()));
                    map.second.end_frame = map.second.start_frame = state.frame;
                    if(map.second.end_frame > map.first->end_frame())
                        map.second.end_frame = map.first->end_frame();
                    state.offsets.clear();
                    
                    Debug("Erasing from frame %ld (start_frame: %d) for fish %d.", state.frame, map.first->start_frame(), map.first->identity().ID());
                    
                    if(map.first->start_frame() < after_frame) {
                        for(; state.frame < min(map.first->end_frame()+1, after_frame);)
                            advance_analysis(map.first, map.second, false);
                    } else {
                        map.second.end_frame = map.second.start_frame = -1;
                    }
                    
                    Debug("Erased %lu events for fish %d (%d-%d).", count, map.first->identity().ID(), map.second.start_frame,map.second.end_frame);
                }
            }
        }
    }
}
}