Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Output.cpp 54.17 KiB
#include "Output.h"
#include <misc/Timer.h>
#include <tracking/FOI.h>
#include <misc/default_config.h>
#include <tracking/Recognition.h>
#include <lzo/minilzo.h>
#include <gui/gui.h>
#include <gui/WorkProgress.h>

using namespace track;
typedef int64_t data_long_t;

/*IMPLEMENT(Output::ResultsFormat::_blob_pool)(cmn::hardware_concurrency(), [](Individual*obj){
    Timer timer;
    
    
});*/

Output::ResultsFormat::ResultsFormat(const file::Path& filename, std::function<void(const std::string&, float, const std::string&)> update_progress)
    : DataFormat(filename.str()), _update_progress(update_progress), last_callback(0), estimated_size(0),
    _post_pool(cmn::hardware_concurrency(), [this](Individual* obj) {
    // post processing for individuals
    Timer timer;
    //pv::Frame frame;
    //PPFrame output;

    /*for(auto &stuff : obj->_basic_stuff) {
        stuff->blob->calculate_properties();
        stuff->blob->calculate_moments();
    }*/

    if (timer.elapsed() > 1)
        Debug("Blobs took %fs", timer.elapsed());

    timer.reset();

    //Debug("Generate midlines for %d...", obj->identity().ID());
    obj->update_midlines(_property_cache.get());
    //Debug("Done with midlines for %d.", obj->identity().ID());

    //data_long_t previous = obj->start_frame();
    //for(auto && [i, c] : obj->_centroid) {
        /*auto outline = obj->outline(i);
        auto midline = obj->midline(i);

        if(outline && midline) {

            // calculate midline centroid
            Vec2 centroid_point(0, 0);
            auto points = outline->uncompress();

            for (auto &p : points) {
                centroid_point += p;
            }
            centroid_point /= float(points.size());
            centroid_point += obj->_blobs.at(i)->bounds().pos();

            auto enhanced = new PhysicalProperties(prev_enhanced, c->time(), centroid_point, midline->angle());
            obj->_centroid_posture[i] = enhanced;
            prev_enhanced = enhanced;
        }*/

        // save frame segments
        /*obj->push_to_segments(i, previous);
        previous = i;
    }*/

    obj->_local_cache.regenerate(obj);

    if (timer.elapsed() >= 1) {
        auto us = timer.elapsed() * 1000 * 1000;

        auto str = DurationUS{ (uint64_t)us }.to_string();
        auto name = obj->identity().name();

        if (!SETTING(quiet))
            Debug("%S post-processing took %S", &name, &str);
    }
}), _generic_pool(min(4u, cmn::hardware_concurrency()), [this](std::exception_ptr e) {
    _exception_ptr = e; // send it to main thread
}), _expected_individuals(0), _N_written(0)
{}

Output::ResultsFormat::~ResultsFormat() {
    _generic_pool.wait();
    _post_pool.wait();
}

template<> void Data::read(track::FrameProperties& p) {
    read<uint64_t>(p.org_timestamp);
    p.time = p.org_timestamp / double(1000*1000);
    auto *ptr = static_cast<Output::ResultsFormat*>(this);
    if(ptr->header().version >= Output::ResultsFormat::V_31) {
        read_convert<data_long_t>(p.active_individuals);
    } else
        p.active_individuals = -1;
}

template<>
uint64_t Data::write(const track::FrameProperties& val) {
    write<data_long_t>(val.frame);
    write<uint64_t>(val.org_timestamp);
    return write<data_long_t>(val.active_individuals);
}

MinimalOutline::Ptr Output::ResultsFormat::read_outline(Data& ref, Midline::Ptr midline) const {
    MinimalOutline::Ptr ptr = std::make_shared<MinimalOutline>();
    static_assert(MinimalOutline::factor == 10, "MinimalOutline::factor was 10 last time I checked.");
    
    uint64_t L;
    ref.read<uint64_t>(L);
    ptr->_points.resize(L);
    /*if(_header.version > Output::ResultsFormat::Versions::V_9) {
        ptr->_tail_index = ref.read<data_long_t>();
    } else
        ptr->_tail_index = ptr->_points.size() * 0.5;*/
    if(_header.version > Output::ResultsFormat::Versions::V_9 && _header.version < Output::ResultsFormat::Versions::V_24) {
        data_long_t index;
        ref.read<data_long_t>(index);
        midline->tail_index() = (long_t)index;
    } /*else if(_header.version >= Output::ResultsFormat::Versions::V_24) {
        midline->tail_index() = ref.read<data_long_t>();
        midline->head_index() = ref.read<data_long_t>();
    }*/
    
    if(_header.version >= Output::ResultsFormat::Versions::V_17) {
        ref.read_convert<float>(ptr->_first.x);
        ref.read_convert<float>(ptr->_first.y);
        
        ref.read_data(ptr->_points.size() * sizeof(decltype(ptr->_points)::value_type), (char*)ptr->_points.data());
    } else {
        struct Point {
            float x, y;
        };
        
        std::vector<Point> points;
        points.resize(ptr->_points.size());
        ref.read_data(ptr->_points.size() * sizeof(Point), (char*)points.data());
        std::vector<Vec2> vecs;
        for(auto &p : points)
            vecs.push_back(Vec2(p.x, p.y));
        
        ptr->convert_from(vecs);
    }
    
    return ptr;
}

template<>
uint64_t Data::write(const track::MinimalOutline& val) {
    auto p = write<uint64_t>(val._points.size());
    //write<data_long_t>(val.tail_index());
    write<float>(val._first.x);
    write<float>(val._first.y);
    
    static_assert(std::is_same<uint16_t, decltype(MinimalOutline::_points)::value_type>::value, "Assuming that MinimalOutline::_points is an array of uint16_t");
    write_data(val._points.size() * sizeof(decltype(val._points)::value_type), (char*)val._points.data());
    //pack.write<uint64_t>(outline->points.size());
    //pack.write<data_long_t>(outline->tail_index);
    //pack.write_data(outline->points.size() * sizeof(Vec2), (char*)outline->points.data());
    return p;
}

template<>
uint64_t Data::write(const PhysicalProperties& val) {
    /**
     * Format of binary representation:
     *  - POSITION (2x4 bytes) in pixels
     *  - ANGLE (4 bytes)
     * both are float.
     *
     * Derivates etc. can be calculated after loading.
     */
    
    uint64_t p = write<Vec2>(val.pos(Units::PX_AND_SECONDS));
    write<float>(val.angle());
    //write<double>(val.frame());
    
    return p;
}

void Output::ResultsFormat::read_blob(Data& ref, pv::CompressedBlob& blob) const {
    const uint64_t elem_size = sizeof(pv::ShortHorizontalLine);
    
    uint16_t id = UINT16_MAX;
    if(_header.version >= Output::ResultsFormat::Versions::V_4
       && _header.version <= Output::ResultsFormat::Versions::V_11)
    {
        ref.read<uint16_t>(id);
    }
    
    bool split = false, tried_to_split = false;
    uint8_t byte = 0;
    if(_header.version >= Output::ResultsFormat::Versions::V_20) {
        ref.read<uint8_t>(byte);
        split = (byte & 0x1) != 0;
    }
    
    data_long_t parent_id = -1;
    if(_header.version >= Output::ResultsFormat::Versions::V_26) {
        if((byte & 0x2) != 0)
            ref.read<data_long_t>(parent_id);
        if((byte & 0x4) != 0)
            tried_to_split = true;
        
    } else if(split
              && _header.version >= Output::ResultsFormat::Versions::V_22
              && _header.version <= Output::ResultsFormat::Versions::V_25)
        ref.read<data_long_t>(parent_id);
    
    uint16_t start_y, len;
    ref.read<uint16_t>(start_y);
    ref.read<uint16_t>(len);
    
    blob.lines.resize(len);
    ref.read_data(elem_size * len, (char*)blob.lines.data());
    
    blob.status_byte = byte;
    blob.start_y = start_y;
    blob.parent_id = (long_t)parent_id;
}

template<>
uint64_t Data::write(const pv::BlobPtr& val) {
    auto &mask = val->hor_lines();
    auto compressed = pv::ShortHorizontalLine::compress(mask);
    
    const uint64_t elem_size = sizeof(pv::ShortHorizontalLine);
    
    // this will turn
    uint8_t byte = (val->parent_id() != -1 ? 0x2 : 0x0)
                   | uint8_t(val->split() ? 0x1 : 0)
                   | uint8_t(val->tried_to_split() ? 0x4 : 0x0);
    uint64_t p = write<uint8_t>(byte);
    if(val->parent_id() != -1)
        write<data_long_t>(val->parent_id());
    write<uint16_t>(uint16_t(mask.empty() ? 0 : mask.front().y));
    write<uint16_t>(uint16_t(compressed.size()));
    write_data(compressed.size() * elem_size, (char*)compressed.data());
    
    return p;
}

Midline::Ptr Output::ResultsFormat::read_midline(Data& ref) {
    auto midline = std::make_shared<Midline>();
    ref.read<float>(midline->len());
    ref.read<float>(midline->angle());
    ref.read<Vec2>(midline->offset());
    ref.read<Vec2>(midline->front());
    if(_header.version >= Versions::V_24) {
        ref.read_convert<data_long_t>(midline->tail_index());
        ref.read_convert<data_long_t>(midline->head_index());
    }
    midline->is_normalized() = _header.version < Versions::V_25;
    
    uint64_t L;
    ref.read<uint64_t>(L);
    midline->segments().resize(L);
    if(_header.version >= Output::ResultsFormat::Versions::V_10) {
        std::vector<Output::V20MidlineSegment> segments;
        segments.resize(midline->segments().size());
        ref.read_data(midline->segments().size() * sizeof(Output::V20MidlineSegment), (char*)segments.data());
        for(uint64_t i=0; i<segments.size(); i++)
            midline->segments()[i] = segments[i];
    } else {
        std::vector<Output::V9MidlineSegment> segments;
        segments.resize(midline->segments().size());
        
        ref.read_data(midline->segments().size() * sizeof(Output::V9MidlineSegment), (char*)segments.data());
        for(uint64_t i=0; i<segments.size(); i++)
            midline->segments()[i] = segments[i];
    }
    
    return midline;
}

template<>
uint64_t Data::write(const Midline& val) {
    auto p = write<float>(val.len());
    write<float>(val.angle());
    write<Vec2>(val.offset());
    write<Vec2>(val.front());
    write<data_long_t>(val.tail_index());
    write<data_long_t>(val.head_index());
    
    write<uint64_t>(val.segments().size());
    
    std::vector<Output::V20MidlineSegment> segments;
    segments.resize(val.segments().size());
    for(uint64_t i=0; i<segments.size(); i++) {
        segments[i].height = val.segments()[i].height;
        segments[i].l_length = val.segments()[i].l_length;
        segments[i].x = (float)val.segments()[i].pos.x;
        segments[i].y = (float)val.segments()[i].pos.y;
    }
    write_data(segments.size() * sizeof(Output::V20MidlineSegment), (char*)segments.data());
    
    return p;
}

Individual* Output::ResultsFormat::read_individual(cmn::Data &ref, const CacheHints* cache) {
    Timer timer;
    
    uint32_t ID;
    
    if(_header.version >= Output::ResultsFormat::Versions::V_5)
        ref.read<uint32_t>(ID);
    else {
        uint16_t sid;
        ref.read<uint16_t>(sid);
        ID = (uint32_t)sid;
    }
    
    Individual *fish = new Individual(ID);
    
    if(_header.version <= Output::ResultsFormat::Versions::V_15) {
        ref.seek(ref.tell() + sizeof(data_long_t) * 2);
        //ref.read<data_long_t>(); // pixel_samples
        //ref.read<data_long_t>();
    }
    
    if(_header.version <= Output::ResultsFormat::Versions::V_13) {
        ref.seek(ref.tell() + sizeof(uint8_t) * 3);
        //ref.read<uchar>(); // jump over colors
        //ref.read<uchar>();
        //ref.read<uchar>();
    }
    
    if(_header.version >= Output::ResultsFormat::Versions::V_7) {
        std::string name;
        ref.read<std::string>(name);
        
        Identity id(ID);
        if(name != id.raw_name() && !name.empty()) {
            auto map = FAST_SETTINGS(individual_names);
            map[ID] = name;
            SETTING(individual_names) = map;
        }
    }
    
    fish->_manually_matched.clear();
    if(_header.version >= Output::ResultsFormat::Versions::V_15) {
        data_long_t tmp;
        uint64_t N;
        ref.read<uint64_t>(N);
        
        for (uint64_t i=0; i<N; ++i) {
            ref.read<data_long_t>(tmp);
            fish->_manually_matched.insert((long_t)tmp);
        }
    }
    
    fish->identity().set_ID(ID);
    
    //PhysicalProperties *prev = NULL;
    //PhysicalProperties *prev_weighted = NULL;
    std::future<void> last_future;
    
    uint64_t N;
    ref.read<uint64_t>(N);
    //double psize = 0;
    //uint64_t pos_before = this->tell();
    data_long_t prev_frame = -1;
    data_long_t frameIndex;
    
    auto analysis_range = Tracker::analysis_range();
    bool check_analysis_range = SETTING(analysis_range).value<std::pair<long_t, long_t>>().first != -1 || SETTING(analysis_range).value<std::pair<long_t, long_t>>().second != -1;
    
    struct TemporaryData {
        std::shared_ptr<Individual::BasicStuff> stuff;
        data_long_t prev_frame;
        Vec2 pos;
        float angle;
    };
    
    std::mutex mutex;
    std::condition_variable variable;
    std::deque<TemporaryData> stuffs;
    std::atomic_bool stop = false;
    
    std::thread worker([&mutex, &variable, &stuffs, &stop, fish, check_analysis_range, analysis_range, cache_ptr = cache]()
    {
        std::unique_lock<std::mutex> guard(mutex);
        PhysicalProperties *prev = NULL;
        auto _no_cache = (const CacheHints*)0x1;
        
        while(!stop || !stuffs.empty()) {
            variable.wait_for(guard, std::chrono::milliseconds(250));
            
            while(!stuffs.empty()) {
                auto & data = stuffs.front();
                
                guard.unlock();
                {
                    const long_t& frameIndex = data.stuff->frame;
                    
                    auto prop = new PhysicalProperties(fish, prev, frameIndex, data.pos, data.angle, cache_ptr);
                    data.stuff->centroid = prop;
                    
                    if(fish->_startFrame == -1)
                        fish->_startFrame = frameIndex;
                    fish->_endFrame = frameIndex;
                    
                    auto cache = fish->cache_for_frame(frameIndex, Tracker::properties(frameIndex, cache_ptr)->time, cache_ptr);
                    auto p = fish->probability(cache, frameIndex, data.stuff->blob).p;
                    
                    auto segment = fish->update_add_segment(
                        frameIndex,
                        data.stuff->centroid,
                        (long_t)data.prev_frame,
                        &data.stuff->blob,
                        p
                    );
                    
                    if(check_analysis_range && (frameIndex > analysis_range.end || frameIndex <= analysis_range.start)) {
                        prev = nullptr;
                    } else
                        prev = prop;
                    
                    segment->add_basic_at(frameIndex, (long_t)fish->_basic_stuff.size());
                    fish->_basic_stuff.push_back(data.stuff);
                }
                guard.lock();
                
                stuffs.pop_front();
            }
        }
    });
    
    TemporaryData data;
    double time;
    
    for (uint64_t i=0; i<N; i++) {
        ref.read<data_long_t>(frameIndex);
        if(prev_frame == -1 && (!check_analysis_range || frameIndex >= analysis_range.start))
            prev_frame = frameIndex;
        
        {
            ref.read<Vec2>(data.pos);
            ref.read<float>(data.angle);
                
            if(_header.version < Output::ResultsFormat::Versions::V_27) {
                if(_header.version >= Output::ResultsFormat::Versions::V_8)
                    ref.read<double>(time);
                else
                    ref.read_convert<float>(time);
            }
        }
        
        //fish->_blob_indices[frameIndex] = ref.read<uint32_t>();
        if(_header.version < Output::ResultsFormat::Versions::V_7)
            ref.seek(ref.tell() + sizeof(uint32_t)); // blob index no longer used
            //ref.read<uint32_t>();
        
        data.stuff = std::make_shared<Individual::BasicStuff>();
        data.stuff->frame = (long_t)frameIndex;
        data.prev_frame = prev_frame;
        read_blob(ref, data.stuff->blob);
        
        if(_header.version >= Output::ResultsFormat::Versions::V_7 && _header.version < Output::ResultsFormat::Versions::V_29)
        {
            static Vec2 tmp;
            ref.read<Vec2>(tmp);
        }
        
        if(check_analysis_range && (frameIndex > analysis_range.end || frameIndex < analysis_range.start)) {
            continue;
        }
        
        {
            std::lock_guard<std::mutex> guard(mutex);
            stuffs.push_back(data);
        }
        variable.notify_one();
        
        prev_frame = frameIndex;
        
        if(i%100000 == 0 && i)
            Debug("Blob %d/%d", i, N);
    }
    
    stop = true;
    variable.notify_all();
    worker.join();
    
    //Output::ResultsFormat::_blob_pool.enqueue(fish);
    
    // read pixel information
    
    if(_header.version >= Versions::V_19) {
        ref.read<uint64_t>(N);
        data_long_t frame;
        uint64_t value;
        
        for(uint64_t i=0; i<N; ++i) {
            ref.read<data_long_t>(frame);
            ref.read<uint64_t>(value);
            
            if(check_analysis_range && (frame > analysis_range.end || frame < analysis_range.start))
                continue;
            
            auto stuff = fish->basic_stuff((long_t)frame);
            if(!stuff) {
                Except("(%d) Cannot find basic_stuff for frame %d.", fish->identity().ID(), frame);
            } else
                stuff->thresholded_size = value;
        }
    }
    
    //uint64_t delta = this->tell() - pos_before;
    //Debug("PSize %f", delta / double(1000*1000));
    //pos_before = this->tell();
    
    // number of head positions
    if(_header.version <= Versions::V_24) {
        ref.read<uint64_t>(N);
        PhysicalProperties *prev = NULL;
        
        for (uint64_t i=0; i<N; i++) {
            ref.read<data_long_t>(frameIndex);
            
            PhysicalProperties *prop;
            {
                Vec2 pos;
                float angle;
                double time;
                
                ref.read<Vec2>(pos);
                ref.read<float>(angle);
                
                if(_header.version < Output::ResultsFormat::Versions::V_27) {
                    if(_header.version >= Output::ResultsFormat::Versions::V_8)
                        ref.read<double>(time);
                    else
                        ref.read_convert<float>(time);
                }
                
                prop = new PhysicalProperties(fish, prev, frameIndex, pos, angle);
            }
            
            prev = prop;
            
            auto midline = read_midline(ref);
            auto outline = read_outline(ref, midline);
        
            if(check_analysis_range && (frameIndex > analysis_range.end || frameIndex < analysis_range.start))
                continue;
            
            if(FAST_SETTINGS(calculate_posture)) {
                // save values
                auto stuff = std::make_shared<Individual::PostureStuff>();
                
                stuff->frame = (long_t)frameIndex;
                stuff->cached_pp_midline = midline;
                stuff->head = prop;
                stuff->outline = outline;
                
                auto segment = fish->segment_for((long_t)frameIndex);
                if(!segment) U_EXCEPTION("(%d) Have to add basic stuff before adding posture stuff (frame %d).", fish->identity().ID(), frameIndex);
                segment->add_posture_at(stuff, fish);
                
            } else {
                delete prop;
                prev = NULL;
            }
        }
        
    } else if(_header.version >= Versions::V_25) {
        // now read outlines and midlines
        ref.read<uint64_t>(N);
        
        std::map<long_t, std::shared_ptr<Individual::PostureStuff>> sorted;
        data_long_t previous_frame = -1;
        
        for (uint64_t i=0; i<N; i++) {
            ref.read<data_long_t>(frameIndex);
            if(frameIndex < previous_frame) {
                Warning("Unordered frames (%ld vs %d)", frameIndex, previous_frame);
                return fish;
            }
            previous_frame = frameIndex;
            
            auto midline = read_midline(ref);
            
            if(check_analysis_range && (frameIndex > analysis_range.end || frameIndex < analysis_range.start))
                continue;
            
            if(FAST_SETTINGS(calculate_posture)) {
                // save values
                auto stuff = std::make_shared<Individual::PostureStuff>();
                
                stuff->frame = (long_t)frameIndex;
                stuff->cached_pp_midline = midline;
                
                auto segment = fish->segment_for((long_t)frameIndex);
                if(!segment) U_EXCEPTION("(%d) Have to add basic stuff before adding posture stuff (frame %d).", fish->identity().ID(), frameIndex);
                
                sorted[stuff->frame] = stuff;
            }
        }
        
        ref.read<uint64_t>(N);
        for (uint64_t i=0; i<N; i++) {
            ref.read<data_long_t>(frameIndex);
            auto outline = read_outline(ref, nullptr);
            
            if(check_analysis_range && (frameIndex > analysis_range.end || frameIndex < analysis_range.start))
                continue;
            
            if(FAST_SETTINGS(calculate_posture)) {
                //fish->posture_stuff(frameIndex);
                std::shared_ptr<Individual::PostureStuff> stuff;
                auto it = sorted.find((long_t)frameIndex);
                
                if(it == sorted.end()) {
                    stuff = std::make_shared<Individual::PostureStuff>();
                    stuff->frame = (long_t)frameIndex;
                    
                    //
                    //fish->_posture_stuff.push_back(stuff);
                    sorted[(long_t)frameIndex] = stuff;
                    
                } else
                    stuff = it->second;
                
                assert(stuff);
                stuff->outline = outline;
            }
        }
        
        std::shared_ptr<Individual::SegmentInformation> segment = nullptr;
        for(auto && [frame, stuff] : sorted) {
            if(!segment || !segment->contains(frame))
                segment = fish->segment_for(frame);
            if(!segment) U_EXCEPTION("(%d) Have to add basic stuff before adding posture stuff (frame %d).", fish->identity().ID(), frame);
            
            segment->add_posture_at(stuff, fish);
        }
    }
    
    //delta = this->tell() - pos_before;
    _post_pool.enqueue(fish);
    
    //if(N > 1000)
    //    Debug("Time for individual %d: %f", fish->identity().ID(), timer.elapsed());
    
    return fish;
}

template<> void Data::read(Individual*& out_ptr) {
    struct Callback {
        std::function<void()> _fn;
        Callback(std::function<void()> fn) : _fn(fn) {}
        ~Callback() {
            try {
                _fn();
            } catch(const std::exception& e) {
                Except("Caught an exception inside ~Callback(): %s",e.what());
            }
        }
    };
    
    auto results = dynamic_cast<Output::ResultsFormat*>(this);
    auto callback = new Callback([results](){
        ++results->_N_written;
        
        if(!SETTING(quiet)) {
            auto N = results->_expected_individuals.load();
            double N_written = results->_N_written.load();
            if(N <= 100 || results->_N_written % max(100u, uint64_t(N * 0.01)) == 0) {
                Debug("Read individual %.0f/%lu (%.0f%%)...", N_written, N, N_written / float(N) * 100);
                results->_update_progress("", N_written / float(N), results->filename().str()+"\n<ref>loading individual</ref> <number>"+Meta::toStr(results->_N_written)+"</number> <ref>of</ref> <number>"+Meta::toStr(N)+"</number>");
            }
        }
    });
    
    if(results && results->_header.version >= Output::ResultsFormat::V_18) {
        uint64_t size, uncompressed_size;
        read<uint64_t>(size);
        read<uint64_t>(uncompressed_size);
        
        auto in = Meta::toStr(FileSize(size));
        auto out = Meta::toStr(FileSize(uncompressed_size));
        
        //Debug("Reading compressed block of size %S.", &in, &out);
        
        std::vector<char>* cache = nullptr;
        auto ptr = read_data_fast(size);
        
        results->_generic_pool.enqueue([ptr, uncompressed_size, out_ptr = &out_ptr, results, size, callback, cache]()
        {
            DataPackage /*compressed_block, */uncompressed_block;
            uncompressed_block.resize(uncompressed_size, false);
            
            lzo_uint new_len;
            if(lzo1x_decompress((uchar*)ptr,size,(uchar*)uncompressed_block.data(),&new_len,NULL) == LZO_E_OK)
            {
                if(new_len != uncompressed_size)
                    Warning("Uncompressed size %lu is different than expected %lu", new_len, uncompressed_size);
                ReadonlyMemoryWrapper compressed((uchar*)uncompressed_block.data(), new_len);
                (*out_ptr) = results->read_individual(compressed, results->_property_cache.get());
                
            } else {
                U_EXCEPTION("Failed to decode individual from file %S", &results->filename());
            }
            
            if(cache)
                delete cache;
            delete callback;
        });
        
        return;
    }
    
    if(!results)
        U_EXCEPTION("This is not ResultsFormat.");
    
    out_ptr = results->read_individual(*this, results->_property_cache.get());
    delete callback;
}

#define OUT_LEN(L)     (L + L / 16 + 64 + 3)

#define HEAP_ALLOC(var,size) \
lzo_align_t __LZO_MMODEL var [ ((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t) ]

static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);

template<>
uint64_t Data::write(const Individual& val) {
    /**
     * Structure of exported binary:
     *  4 bytes ID
     *
     *  N number of frames for centroid
     *  (Nx (8 bytes frameIndex + 12 bytes)) PhysicalProperties for centroid
     *  (Nx k bytes) Blob
     *  (Nx (8 bytes M + Mx 1 byte grey values))
     
     *  N number of frames for head
     *  (Nx (8 bytes frameIndex + 12 bytes)) PhysicalProperties for head
     *  (Nx Midline)
     *  (Nx Outline)
     */
    
    const uint64_t pack_size = Output::ResultsFormat::estimate_individual_size(val);
    auto *ptr = static_cast<Output::ResultsFormat*>(this);
    const std::function<void(uint64_t)> callback = [ptr](uint64_t pos) {
        pos += ptr->current_offset();
        
        if(pos - ptr->last_callback > ptr->estimated_size * 0.01) {
            ptr->_update_progress("", min(1, double(pos)/double(ptr->estimated_size)), "");
            ptr->last_callback = pos;
        }
    };
    
    DataPackage pack(pack_size, &callback);
    
    // header
    assert(val.identity().ID() >= 0);
    uint32_t ID = (uint32_t)val.identity().ID();
    pack.write<uint32_t>(ID);
    pack.write<std::string>(val.identity().raw_name());
    
    pack.write<uint64_t>(val._manually_matched.size());
    for (auto m : val._manually_matched)
        pack.write<data_long_t>(m);
    
    // centroid based information
    pack.write<uint64_t>(val._basic_stuff.size());
    
    std::set<std::tuple<long_t, const Individual::BasicStuff*>> sorted;
    for(auto& stuff : val._basic_stuff) {
        sorted.insert({stuff->frame, stuff.get()});
    }
    
    for(auto&& [frame, stuff] : sorted) {
        // write frame number
        pack.write<data_long_t>(stuff->frame);
        
        // write centroid PhysicalProperties
        pack.write<PhysicalProperties>(*stuff->centroid);
        
        // assume we have a blob and grey values as well
        pack.write<pv::BlobPtr>(stuff->blob.unpack());
        
        //pack.write<Vec2>(stuff->centroid->pos(Units::PX_AND_SECONDS));
    }
    
    // write pixel size information
    pack.write<uint64_t>(val._basic_stuff.size());
    for(auto & stuff : val._basic_stuff) {
        pack.write<data_long_t>(stuff->frame);
        pack.write<uint64_t>(stuff->thresholded_size);
    }
    
    //auto str = Meta::toStr(FileSize(pack.size()));
    //Debug("Individual %d is %S after centroid", val.identity().ID(), &str);
    
    // head based information
    /*pack.write<uint64_t>(val._head.size());
    
    for (auto &c : val._head) {
        // write frame number
        pack.write<data_long_t>(c.first);
        
        // write head PhysicalProperties
        pack.write(*c.second);
    }*/
    
    // write N, and then write all midlines and outlines (unprocessed)
    std::map<data_long_t, Midline::Ptr> cached_pp_midlines;
    std::map<data_long_t, MinimalOutline::Ptr> outlines;
    for(auto& stuff : val._posture_stuff) {
        if(stuff->cached_pp_midline)
            cached_pp_midlines[stuff->frame] = stuff->cached_pp_midline;
        if(stuff->outline)
            outlines[stuff->frame] = stuff->outline;
    }
    
    pack.write<uint64_t>(cached_pp_midlines.size());
    for(auto && [frame, midline] : cached_pp_midlines) {
        pack.write<data_long_t>(frame);
        pack.write<Midline>(*midline);
    }
    
    pack.write<uint64_t>(outlines.size());
    
    for(auto && [frame, outline] : outlines) {
        pack.write<data_long_t>(frame);
        pack.write<MinimalOutline>(*outline);
    }
    
    //str = Meta::toStr(FileSize(pack.size()));
    //auto estimate = Meta::toStr(FileSize(pack_size));
    //auto per_frame = Meta::toStr(FileSize(pack.size() / double(val.frame_count())));
    
    //Debug("Individual %d is %S (estimated %S) thats %S / frame", val.identity().ID(), &str, &estimate, &per_frame);
    
    lzo_uint out_len = 0;
    assert(pack.size() < LZO_UINT_MAX);
    uint64_t in_len = pack.size();
    uint64_t reserved_size = OUT_LEN(in_len);
    
    DataPackage out;
    out.resize(reserved_size);
    
    // lock for wrkmem
    if(lzo1x_1_compress((uchar*)pack.data(), in_len, (uchar*)out.data(), &out_len, wrkmem) == LZO_E_OK)
    {
        uint64_t size = out_len + sizeof(uint32_t)*2;
        
        pack.reset_offset();
        
        assert(out_len < UINT32_MAX);
        pack.write<uint64_t>(out_len);
        pack.write<uint64_t>(in_len);
        pack.write_data(out_len, out.data());
        
        auto ptr = static_cast<const Output::ResultsFormat*>(this);
        if(ptr->_expected_individuals.load() < 1000 || ptr->_N_written.load() % 100 == 0 || ptr->_N_written.load() + 1 == ptr->_expected_individuals.load()) {
            auto before = Meta::toStr(FileSize(in_len));
            auto after = Meta::toStr(FileSize(size));
            
            Debug("Saved %.2f%%... (individual %d compressed from %S to %S).", double(ptr->_N_written.load() + 1) / double(ptr->_expected_individuals.load()) * 100, val.identity().ID(), &before, &after);
        }
    
    } else {
        U_EXCEPTION("Compression of %lu bytes failed (individual %d).", pack.size(), val.identity().ID());
    }
    
    return write(pack);
}

namespace Output {
    Timer reading_timer;
    float bytes_per_second = 0, samples = 0;
    std::string speed = "";
    float percent_read;
    
    /*const char* ResultsFormat::read_data_fast(uint64_t num_bytes) {
        if(reading_timer.elapsed() >= 1) {
            if(samples > 0) {
                speed = Meta::toStr(FileSize(uint64_t(bytes_per_second / samples / reading_timer.elapsed())));
                Debug("Reading @ %S/s", &speed);
            }
            reading_timer.reset();
            bytes_per_second = 0;
            samples = 0;
        }
        
        if(current_offset() - last_callback > read_size() * 0.01) {
            last_callback = current_offset();
            percent_read = float((current_offset() + num_bytes) / double(read_size()));
            
            //if(int(percent_read*100) % 10 == 0)
            {
                _update_progress("", percent_read, ""+filename().str()+"\n<ref>reading @ "+speed+"/s</ref>");
                Debug("Reading %.0f%% (@ %S/s)", percent_read*100, &speed);
            }
        }
        
        Timer timer;
        auto ptr = DataFormat::read_data_fast(num_bytes);
        auto time = timer.elapsed();
        
        bytes_per_second += num_bytes / time;
        ++samples;
        
        return ptr;
    }*/
    
    void ResultsFormat::_read_header() {
        std::string version_string;
        read<std::string>(version_string);
        if(!utils::beginsWith(version_string, "TRACK"))
            U_EXCEPTION("Illegal file format for tracking results.");
        
        if (version_string == "TRACK") {
            _header.version = Versions::V_1;
            
        } else {
            std::string str = version_string.substr(5);
            _header.version = (Versions)Meta::fromStr<int>(str);
        }
        
        if(_header.version >= V_3) {
            read<uint64_t>(_header.gui_frame);
        } else _header.gui_frame = 0;
        if(_header.version >= V_11 && _header.version < V_15) {
            seek(tell() + sizeof(data_long_t));
        }
        
        _header.consecutive_segments.clear();
        _header.video_resolution = Size2(-1);
        _header.video_length = 0;
        _header.average.clear();
        
        if(_header.version >= ResultsFormat::V_28) {
            uint32_t N;
            read<uint32_t>(N);
            
            // read N pairs of numbers to convert to ranges
            uint32_t start, end;
            for (uint32_t i=0; i<N; ++i) {
                read<uint32_t>(start);
                read<uint32_t>(end);
                
                _header.consecutive_segments.push_back(Rangel(start, end));
            }
            
            read<Size2>(_header.video_resolution);
            read<uint64_t>(_header.video_length);
            
            _header.average.create(_header.video_resolution.height, _header.video_resolution.width, 1);
            read_data(_header.average.size(), (char*)_header.average.data());
        }
        
        if(_header.version >= ResultsFormat::V_30) {
            read<int64_t>(_header.analysis_range.start);
            read<int64_t>(_header.analysis_range.end);
        } else
            _header.analysis_range = Range<int64_t>(-1, -1);
        
        if(_header.version >= ResultsFormat::V_14) {
            read<std::string>(_header.settings);
            //Debug("Read settings map: %S", &_header.settings);
        }
        
        if(_header.version >= ResultsFormat::V_23) {
            read<std::string>(_header.cmd_line);
        }
        
        if(!SETTING(quiet)) {
            DebugHeader("READING PROGRAM STATE");
            Debug("Read head of '%S' (version:V_%d gui_frame:%lu analysis_range:%ld-%ld)", &filename().str(), (int)_header.version+1, _header.gui_frame, _header.analysis_range.start, _header.analysis_range.end);
            Debug("Generated with command-line: '%S'", &_header.cmd_line);
        }
    }
    
    void ResultsFormat::_write_header() {
        std::string version_string = "TRACK"+std::to_string((int)Versions::current);
        write<std::string>(version_string);
        if(!SETTING(quiet)) {
            Debug("Writing version string '%S'", &version_string);
            Debug("Writing frame %lu", _header.gui_frame);
        }
        write<uint64_t>(_header.gui_frame);
        auto consecutive = Tracker::instance()->consecutive();
        write<uint32_t>((uint32_t)consecutive.size());
        for (auto &c : consecutive) {
            write<uint32_t>(c.start);
            write<uint32_t>(c.end);
        }
        
        write<Size2>(Tracker::average().bounds().size());
        write<uint64_t>(FAST_SETTINGS(video_length));
        
        uint64_t bytes = Tracker::average().cols * Tracker::average().rows;
        write_data(bytes, (const char*)Tracker::average().data());
        
        auto [start, end] = FAST_SETTINGS(analysis_range);
        write<int64_t>(start);
        write<int64_t>(end);
    }
    
    uint64_t ResultsFormat::write_data(uint64_t num_bytes, const char *buffer) {
        if(current_offset() - last_callback > estimated_size * 0.01) {
            _update_progress("", min(1, double(current_offset()+num_bytes)/double(estimated_size)), "");
            last_callback = current_offset();
        }
        
        return DataFormat::write_data(num_bytes, buffer);
    }
    
    uint64_t ResultsFormat::estimate_individual_size(const Individual& val) {
        static const uint64_t physical_properties_size = (2+1+1)*sizeof(float);
        const uint64_t pack_size =
        4 + sizeof(data_long_t)*2
        + sizeof(uchar)*3
        + val._basic_stuff.size() * (sizeof(data_long_t)+physical_properties_size+sizeof(uint32_t)+(val._basic_stuff.empty() ? 100 : (*val._basic_stuff.begin())->blob.lines.size())*1.1*sizeof(pv::ShortHorizontalLine))
        + val._posture_stuff.size() * (sizeof(data_long_t)+sizeof(uint64_t)+((val._posture_stuff.empty() || !val._posture_stuff.front()->cached_pp_midline ?SETTING(midline_resolution).value<uint32_t>() : (*val._posture_stuff.begin())->cached_pp_midline->size()) * sizeof(float) * 2 + sizeof(float) * 5 + sizeof(uint64_t))+physical_properties_size+((val._posture_stuff.empty() || !val._posture_stuff.front()->outline ? 0 : val._posture_stuff.front()->outline->size()*1.1)*sizeof(uint16_t) + sizeof(float)*2+sizeof(uint64_t)))
        + val._basic_stuff.size() * sizeof(decltype(Individual::BasicStuff::thresholded_size)) + sizeof(uint64_t);
        
        return pack_size;
    }
    
    void ResultsFormat::write_file(const std::vector<track::FrameProperties> &frames, const std::unordered_map<long_t, Tracker::set_of_individuals_t > &active_individuals_frame, const std::unordered_map<idx_t, Individual *> &individuals, const std::vector<std::string>& exclude_settings)
    {
        estimated_size = sizeof(uint64_t)*3 + frames.size() * (sizeof(data_long_t)+sizeof(CompatibilityFrameProperties)) + active_individuals_frame.size() * (sizeof(data_long_t)+sizeof(uint64_t)+(active_individuals_frame.empty() ? individuals.size() : active_individuals_frame.begin()->second.size())*sizeof(data_long_t));
        
        _expected_individuals = individuals.size();
        for (auto& fish: individuals) {
            estimated_size += estimate_individual_size(*fish.second);
        }
        
        if(!SETTING(quiet)) {
            auto str = Meta::toStr(FileSize(estimated_size));
            Debug("Estimating %S for the whole file.", &str);
        }
        std::string text = default_config::generate_delta_config(true, exclude_settings);
        write<std::string>(text);
        write<std::string>(SETTING(cmd_line).value<std::string>());
        
        // write recognition data
        const auto &recognition = *Tracker::recognition();
        write<uint64_t>(recognition.data().size());
        for(auto && [frame, map] : recognition.data()) {
            write<data_long_t>(frame);
            write<uint64_t>(map.size());
            
            for(auto && [bid, vector] : map) {
                write<uint32_t>(bid);
                write<uint64_t>(vector.size());
                write_data(sizeof(float) * vector.size(), (const char*)vector.data());
            }
        }
        
        // write frame properties
        write<uint64_t>(frames.size());
        if(!SETTING(quiet))
            Debug("Writing %ld frames", frames.size());
        for (auto &p : frames)
            write<track::FrameProperties>(p);
        
        // write number of individuals
        write<uint64_t>(_expected_individuals);
        
        _N_written = 0;
        for (auto& fish: individuals) {
            write<Individual>(*fish.second);
            ++_N_written;
        }
        
        // write active individuals per frame
        write<uint64_t>(active_individuals_frame.size());
        for (auto &p : active_individuals_frame) {
            write<data_long_t>(p.first);
            write<uint64_t>(p.second.size());
            for(auto &fish : p.second) {
                write<data_long_t>(fish->identity().ID());
            }
        }
    }
    
    Path TrackingResults::expected_filename() {        
        file::Path filename = SETTING(filename).value<Path>().filename().to_string();
        filename = filename.extension().to_string() == "pv" ?
        filename.replace_extension("results") : filename.add_extension("results");
        return pv::DataLocation::parse("output", filename);
    }
    
    void TrackingResults::save(std::function<void (const std::string &, float, const std::string &)> update_progress, Path filename, const std::vector<std::string>& exclude_settings) const {
        if(filename.empty())
            filename = expected_filename();
        
        if(!filename.remove_filename().empty() && !filename.remove_filename().exists())
            filename.remove_filename().create_folder();
        
        // add a temporary extension, so that we dont overwrite initially
        // (until we're certain its done)
        filename = filename.add_extension("tmp01");
        
        ResultsFormat file(filename.str(), update_progress);
        file.header().gui_frame = SETTING(gui_frame).value<long_t>();
        file.start_writing(true);
        file.write_file(_tracker._added_frames, _tracker._active_individuals_frame, _tracker._individuals, exclude_settings);
        file.close();
        
        // go back from .tmp01 to .results
        if(filename.move_to(filename.remove_extension())) {
            filename = filename.remove_extension();
            if(!SETTING(quiet)) {
                DebugHeader("Finished writing '%S'.", &filename.str());
                DebugCallback("Finished writing '%S'.", &filename.str());
            }
        } else
            U_EXCEPTION("Cannot move '%S' to '%S' (but results have been saved, you just have to rename the file).", &filename.str(), &filename.remove_extension().str());
    }
    
    void TrackingResults::clean_up() {
        _tracker._individuals.clear();
        _tracker._added_frames.clear();
        _tracker.clear_properties();
        _tracker._active_individuals.clear();
        _tracker._active_individuals_frame.clear();
        _tracker._startFrame = -1;
        _tracker._endFrame = -1;
        _tracker._max_individuals = 0;
        _tracker._consecutive.clear();
        FOI::clear();
    }
    
    ResultsFormat::Header TrackingResults::load_header(const file::Path &filename) {
        bytes_per_second = samples = percent_read = 0;
        
        if(!SETTING(quiet))
            Debug("Trying to open results '%S' (retrieve header only)", &filename.str());
        ResultsFormat file(filename.str(), [](const auto&, auto, const auto&){});
        file.start_reading();
        return file.header();
    }

void TrackingResults::update_fois(const std::function<void(const std::string&, float, const std::string&)>& update_progress) {
    const auto number_fish = FAST_SETTINGS(track_max_individuals);
    data_long_t prev = 0;
    data_long_t n = 0;
    double prev_time = _tracker.start_frame() == -1 ? 0:  _tracker.properties(_tracker.start_frame())->time;
    
    //auto it = _tracker._active_individuals_frame.begin();
    if(_tracker._active_individuals_frame.size() != _tracker._added_frames.size()) {
        U_EXCEPTION("This is unexpected (%d != %d).", _tracker._active_individuals_frame.size(), _tracker._added_frames.size());
    }
    
    const track::FrameProperties* prev_props = nullptr;
    data_long_t prev_frame = -1;
    
    std::unordered_map<long_t, Individual::segment_map::const_iterator> iterator_map;
    
    for(const auto &props : _tracker._added_frames) {
        prev_time = props.time;
        
        // number of individuals actually assigned in this frame
        /*n = 0;
        for(const auto &fish : it->second) {
            n += fish->has(props.frame) ? 1 : 0;
        }*/
        n = props.active_individuals;
        
        // update tracker with the numbers
        //assert(it->first == props.frame);
        auto &active = _tracker._active_individuals_frame.at(props.frame);
        if(prev_props && prev_frame - props.frame > 1)
            prev_props = nullptr;
        
        _tracker.update_consecutive(active, props.frame, false);
        _tracker.update_warnings(props.frame, props.time, number_fish, (long_t)n, (long_t)prev, &props, prev_props, active, iterator_map);
        
        prev = n;
        prev_props = &props;
        prev_frame = props.frame;
        
        if(props.frame % max(1u, uint64_t(_tracker._added_frames.size() * 0.1)) == 0) {
            update_progress("FOIs...", props.frame / float(_tracker.end_frame()), Meta::toStr(props.frame)+" / "+Meta::toStr(_tracker.end_frame()));
            if(!SETTING(quiet))
                Debug("\tupdate_fois %d / %d\r", props.frame, _tracker.end_frame());
        }
    }
    
    if(_tracker.recognition() && FAST_SETTINGS(recognition_enable)) {
        update_progress("Finding segments...", -1, "");
        _tracker.recognition()->update_dataset_quality();
    }
}
    
    void TrackingResults::load(std::function<void(const std::string&, float, const std::string&)> update_progress, Path filename) {
        Timer loading_timer;
        
        if (filename.empty())
            filename = expected_filename();
        else if (!filename.exists())
            Error("Cannot find '%S' as requested. Trying standard paths.", &filename.str());

        if(!filename.exists()) {
            file::Path file = SETTING(filename).value<Path>();
            file = file.extension().to_string() == "pv" ?
            file.replace_extension("results") : file.add_extension("results");
            
            //file = pv::DataLocation::parse("input", filename.filename());
            if(file.exists()) {
                Warning("Not loading from the output folder, but from the input folder because '%S' could not be found, but '%S' could.", &filename.str(), &file.str());
                filename = file;
            } else
                Warning("Searched at '%S', but also couldnt be found.", &file.str());
        }
        
        bytes_per_second = samples = percent_read = 0;
        
        if(!SETTING(quiet))
            Debug("Trying to open results '%S'", &filename.str());
        ResultsFormat file(filename.str(), update_progress);
        file.start_reading();
        
        auto tmp = _tracker.individuals();
        clean_up();
        
        data_long_t biggest_id = -1;
        
        Tracker::instance()->_individual_add_iterator_map.clear();
        Tracker::instance()->_segment_map_known_capacity.clear();

        for (auto& p : tmp)
            delete p.second;

        // read recognition data
        auto &recognition = *Tracker::recognition();
        recognition.data().clear();
        
        if(file._header.version >= ResultsFormat::V_13) {
            std::vector<float> tmp;
            uint64_t L;
            file.read<uint64_t>(L);
            data_long_t frame;
            uint64_t size, vsize;
            uint32_t bid;
            
            for(uint64_t i=0; i<L; ++i) {
                file.read<data_long_t>(frame);
                file.read<uint64_t>(size);
                
                for(uint64_t j=0; j<size; ++j) {
                    file.read<uint32_t>(bid);
                    file.read<uint64_t>(vsize);
                    tmp.resize(vsize);
                    file.read_data(vsize * sizeof(float), (char*)tmp.data());
                    
                    recognition.data()[(long_t)frame][bid] = tmp;
                }
            }
        }
        
        // read frame properties
        uint64_t L;
        file.read<uint64_t>(L);
        
        track::FrameProperties props;
        CompatibilityFrameProperties comp;
        data_long_t frameIndex;
        bool check_analysis_range = SETTING(analysis_range).value<std::pair<long_t, long_t>>().first != -1 || SETTING(analysis_range).value<std::pair<long_t, long_t>>().second != -1;
        
        auto analysis_range = Tracker::analysis_range();
        _tracker.clear_properties();
        file._property_cache = std::make_shared<CacheHints>(L);
        
        for (uint64_t i=0; i<L; i++) {
            file.read<data_long_t>(frameIndex);
            
            if(file._header.version >= ResultsFormat::Versions::V_2)
                file.read<track::FrameProperties>(props);
            else {
                file.read<CompatibilityFrameProperties>(comp);
                props.org_timestamp = comp.timestamp;
                props.time = comp.time;
            }
            
            props.frame = frameIndex;
            
            if(check_analysis_range && (frameIndex > analysis_range.end || frameIndex < analysis_range.start))
                continue;
            
            if(_tracker._startFrame == -1)
                _tracker._startFrame = frameIndex;
            _tracker._endFrame = frameIndex;
            
            _tracker.add_next_frame(props);
        }
        
        for(auto &prop : _tracker.frames())
            file._property_cache->push(prop.frame, &prop);
        
        // read the individuals
        std::map<long_t, Individual*> map_id_ptr;
        std::vector<Individual*> fishes;
        
        file.read<uint64_t>(L);
        fishes.resize(L);
        file._expected_individuals = L;
        file._N_written = 0;
        
        for (uint64_t i=0; i<L; i++) {
            fishes[i] = nullptr;
            
            file.read<Individual*>(fishes[i]);
            
            if(SETTING(terminate)) {
                clean_up();
                return;
            }
        }
        
        file._generic_pool.wait();
        if(file._exception_ptr) //! an exception happened inside the generic pool
            std::rethrow_exception(file._exception_ptr);
        
        for(auto &fish : fishes) {
            if(fish) {
                if(biggest_id < fish->identity().ID())
                    biggest_id = fish->identity().ID();
                map_id_ptr[fish->identity().ID()] = fish;
                _tracker._individuals[fish->identity().ID()] = fish;
            }
        }
        
        track::Identity::set_running_id(uint32_t(biggest_id+1));
        
        uint64_t n;
        data_long_t ID;
        file.read<uint64_t>(L);
        for (uint64_t i=0; i<L; i++) {
            Tracker::set_of_individuals_t active;
            
            file.read<data_long_t>(frameIndex);
            file.read<uint64_t>(n);
            
            for (uint64_t j=0; j<n; j++) {
                file.read<data_long_t>(ID);
                
                if(check_analysis_range && (frameIndex > analysis_range.end || frameIndex < analysis_range.start))
                    continue;
                
                auto it = map_id_ptr.find((long_t)ID);
                if (it == map_id_ptr.end())
                    U_EXCEPTION("Cannot find individual with ID %ld in map.", ID);
                active.insert(it->second);
            }
            
            if(check_analysis_range && (frameIndex > analysis_range.end || frameIndex < analysis_range.start))
                continue;
            
            _tracker._active_individuals_frame[(long_t)frameIndex] = active;
            _tracker._active_individuals = active;
            
            _tracker._max_individuals = max(_tracker._max_individuals, active.size());
        }
        
        _tracker._inactive_individuals.clear();
        for(auto&& [id, fish] : _tracker._individuals) {
            if(_tracker._active_individuals.find(fish) == _tracker._active_individuals.end()) {
                _tracker._inactive_individuals.insert(id);
            }
        }
        
        file.close();
        
        update_progress("post processing...", -1, "");
        file._post_pool.wait();
        
        if(file._header.version < ResultsFormat::V_31) {
            //! have to regenerate the number of individuals / frame
            long_t n = 0;
            
            for(auto &props : _tracker._added_frames) {
                // number of individuals actually assigned in this frame
                n = 0;
                for(const auto &fish : _tracker._active_individuals_frame.at(props.frame)) {
                    n += fish->has(props.frame);
                }
                props.active_individuals = n;
            }
        }
        
        update_fois(update_progress);
        
        /*for(long_t i=_tracker.start_frame(); i<=_tracker.end_frame(); i++) {
            long_t n = _tracker.found_individuals_frame(i);
            
            auto props = _tracker.properties(i);
            prev_time = props->time;
            _tracker.update_consecutive(_tracker.active_individuals(i), i, true);
            _tracker.update_warnings(i, props->time, number_fish, n, prev);
            
            prev = n;
            
            //_tracker.generate_pairdistances(i);
        }*/
        
        {
            sprite::Map config;
            GlobalSettings::docs_map_t docs;
            config.set_do_print(false);
            
            default_config::get(config, docs, NULL);
            try {
                default_config::load_string_with_deprecations(filename, file.header().settings, config, AccessLevelType::STARTUP, true);
                
            } catch(const cmn::illegal_syntax& e) {
                Error("Illegal syntax in .results settings.");
            }
            
            if(config.has("gui_focus_group")) {
                SETTING(gui_focus_group) = config["gui_focus_group"].value<std::vector<idx_t>>();
            } else
                SETTING(gui_focus_group) = std::vector<idx_t>{};
            
            SETTING(gui_frame).value<long_t>() = (long_t)file.header().gui_frame;
        }
        
        if((file.header().analysis_range.start != -1 || file.header().analysis_range.end != -1) && SETTING(analysis_range).value<std::pair<long_t, long_t>>() == std::pair<long_t,long_t>{-1,-1})
        {
            SETTING(analysis_range) = std::pair<long_t, long_t>(file.header().analysis_range.start, file.header().analysis_range.end);
        }
        
        if(Recognition::recognition_enabled()) {
            if(GUI::instance()) {
                /*update_progress("apply network...", -1, "");
                
                Tracker::instance()->check_segments_identities(false, [](float){}, [](const std::string&t, const std::function<void()>& fn, const std::string&b) {
                    if(GUI::instance())
                        GUI::work().add_queue(t, fn, b);
                });
                
                Tracker::instance()->thread_pool().enqueue([](){
                    Tracker::recognition()->update_dataset_quality();
                });*/
            }
        }
        
        if(!SETTING(quiet)) {
            Debug("Successfully read file '%S' (version:V_%d gui_frame:%lu start:%lu end:%lu)", &file.filename().str(), (int)file._header.version+1, file.header().gui_frame, Tracker::start_frame(), Tracker::end_frame());
        
            DurationUS duration{uint64_t(loading_timer.elapsed() * 1000 * 1000)};
            auto str = Meta::toStr(duration);
            DebugHeader("FINISHED READING PROGRAM STATE IN %S", &str);
        }
    }
}