-
Tristan Walter authoredTristan Walter authored
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);
}
}
}