atomorph/src/thread.cpp

1237 lines
40 KiB
C++

/*
* See Copyright Notice in atomorph.h
*/
#include <cstring>
#include "atomorph.h"
namespace am {
thread::thread() {
signal_stop = false;
signal_pause = false;
running = false;
paused = true;
iterations = 0;
seconds = 0.0;
blob_map = nullptr;
blob_map_w = 0;
blob_map_h = 0;
blob_map_e = 0.0;
chain_map_e = 0.0;
clear();
}
thread::~thread() {
if (step_thread.joinable()) {
pause();
stop();
}
clear();
}
bool thread::clear() {
if (running && !paused) return false;
if (blob_map) {
for ( size_t i = 0; i < blob_map_w; ++i) {
delete [] blob_map[i];
}
delete [] blob_map;
blob_map = nullptr;
}
blob_map_w = 0;
blob_map_h = 0;
blob_map_e = 0.0;
best_blob_map_e = 0.0;
best_e = std::numeric_limits<double>::infinity();
chain_map_e = std::numeric_limits<double>::infinity();
deviant= false;
while (!frames.empty()) {
frame f = frames.begin()->second;
while (!f.blobs.empty()) {
delete f.blobs.back();
f.blobs.pop_back();
}
frames.erase(frames.begin());
}
frames.clear();
while (!chains.empty()) {
chain c = chains.begin()->second;
clear_chain(&c);
chains.erase(chains.begin());
}
chains.clear();
state = STATE_BLOB_DETECTION;
identifier = SIZE_MAX;
counter = 1;
bbox_x1 = UINT16_MAX;
bbox_y1 = UINT16_MAX;
bbox_x2 = 0;
bbox_y2 = 0;
bbox_d = 0;
return true;
}
void thread::set_seed(unsigned seed) {
if (running && !paused) return;
this->seed = seed;
std::default_random_engine e(seed);
e1 = e;
}
void thread::set_frame (size_t nr, frame * frame_pt) {
if (running && !paused) return;
frames[nr].pixels.insert(frame_pt->pixels.begin(), frame_pt->pixels.end());
frames[nr].x = frame_pt->x;
frames[nr].y = frame_pt->y;
frames[nr].r = frame_pt->r;
frames[nr].g = frame_pt->g;
frames[nr].b = frame_pt->b;
frames[nr].a = frame_pt->a;
frames[nr].first_expansion=0;
frames[nr].first_dust =0;
// Refresh frame indexes:
std::map<size_t, frame>::iterator it;
size_t index = 0;
for (it=frames.begin(); it!=frames.end(); ++it) {
it->second.index = index++;
}
}
void thread::resume(size_t iterations) {
if (!is_paused()) return;
this->iterations = iterations;
resume();
}
void thread::resume(double seconds) {
if (!is_paused()) return;
this->seconds = seconds;
resume();
}
void thread::start(size_t iterations) {
if (is_running()) return;
this->iterations = iterations;
start();
resume();
}
void thread::start(double seconds) {
if (is_running()) return;
this->seconds = seconds;
start();
resume();
}
void thread::step() {
switch (state) {
case STATE_BLOB_DETECTION: if (blobify()) {
state = STATE_BLOB_UNIFICATION;
counter = 0;
}
break;
case STATE_BLOB_UNIFICATION: if (unify()) {
state = STATE_BLOB_MATCHING;
counter = 0;
}
break;
case STATE_BLOB_MATCHING: if (skip_state || match()) {
if (!init_morph()) {
state = STATE_DONE;
}
else {
state = STATE_ATOM_MORPHING;
}
counter = 0;
}
break;
case STATE_ATOM_MORPHING: if (skip_state || morph()) {
state = STATE_DONE;
counter = 0;
}
break;
default: break;
}
skip_state = false;
counter++;
return;
}
void thread::run() {
std::chrono::steady_clock::time_point start,end;
start = std::chrono::steady_clock::now();
while (!signal_stop) {
if (!signal_pause && !paused) {
step();
if (iterations > 0 && --iterations == 0) {
// When the defined number of steps have been made, automatically pause the thread.
signal_pause = true;
}
if (seconds > 0.0) {
// When worked more than the defined number of seconds, automatically pause the thread.
end = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() > (seconds * 1000000000)) {
signal_pause = true;
seconds = 0.0;
}
}
}
else {
paused = true;
signal_pause = false;
std::this_thread::sleep_for(std::chrono::milliseconds(0));
start = std::chrono::steady_clock::now();
}
}
running = false;
signal_stop = false;
signal_pause = false;
paused = true;
}
bool thread::can_expand(size_t frame, size_t from_pos, size_t to_pos) {
if (!has_pixel(frame, to_pos)) return false;
pixel p1 = get_pixel(frame, from_pos);
pixel p2 = get_pixel(frame, to_pos);
if (color_distance(p1.c, p2.c) <= blob_threshold) return true;
return false;
}
pixel thread::get_pixel (size_t f, size_t pos) {
if (!has_pixel(f,pos)) return create_pixel(0,0,0,0,0,0);
return frames[f].pixels[pos];
}
// Returns false when not finished.
bool thread::blobify_frame(size_t frame_key) {
frame* f = &frames[frame_key];
size_t sz = f->blobs.size();
size_t pos,to_pos,x,y,b;
if (sz == 0) {
// First step is to interpret all pixels as atomic blobs.
std::map<size_t, pixel>::iterator it;
for (it=f->pixels.begin(); it!=f->pixels.end(); ++it) {
pos = it->first;
blob* new_blob = new blob;
new_blob->surface.insert(pos);
new_blob->x = it->second.x;
new_blob->y = it->second.y;
new_blob->r = (it->second.c.r/255.0);
new_blob->g = (it->second.c.g/255.0);
new_blob->b = (it->second.c.b/255.0);
new_blob->a = (it->second.c.a/255.0);
new_blob->group = pos;
x = pos % (UINT16_MAX+1);
y = pos / (UINT16_MAX+1);
// Add reasonable border.
if (x < UINT16_MAX && has_pixel(frame_key, (to_pos = xy2pos(x+1, y )))) new_blob->border.insert(to_pos);
if (x > 0 && has_pixel(frame_key, (to_pos = xy2pos(x-1, y )))) new_blob->border.insert(to_pos);
if (y < UINT16_MAX && has_pixel(frame_key, (to_pos = xy2pos(x , y+1)))) new_blob->border.insert(to_pos);
if (y > 0 && has_pixel(frame_key, (to_pos = xy2pos(x , y-1)))) new_blob->border.insert(to_pos);
f->blobs.push_back(new_blob);
f->owners[pos] = new_blob;
}
std::shuffle(f->blobs.begin(), f->blobs.end(), e1);
sz = f->blobs.size();
for (size_t bb = 0; bb<sz; ++bb) {
f->blobs[bb]->index = bb;
}
if (f->blobs.size() == 0 || blob_max_size == 1) return true;
return false;
}
Again: // For popping all sequential nullptrs from the end of the vector.
sz = f->blobs.size();
b = 0;
if (sz > 1) {
// Always take the last blob from the initially shuffled vector
// because popping the last element is fast.
b = sz-1;
}
else return true;
blob* bl = (f->blobs[b]);
if (!bl || bl->surface.size() == 0) {
// This blob is empty, remove it.
f->blobs.pop_back();
delete bl;
goto Again;
}
if (f->blobs.size() <= blob_number) return true;
std::set<size_t>::iterator it;
std::vector<size_t> erased_border;
std::set<blob *> checked;
bool best_found = false;
double best_dist = 0.0;
blob * best_match = nullptr;
size_t best_equal = 0;
if (bl->surface.size() >= blob_min_size) {
// This blob has met its minimum size, prevent it from being unified later.
bl->unified = true;
}
bool check_threshold = true;
if (bl->surface.size() < blob_max_size) {
IgnoreColourThreshold:
for (it=bl->border.begin(); it!=bl->border.end(); ++it) {
pos = *it;
if (bl->surface.count(pos) != 0
|| f->owners.count(pos) == 0
|| checked.count(f->owners[pos]) != 0
|| !has_pixel(frame_key, pos)) {
// Don't expand into itself and empty positions.
if (check_threshold && bl->surface.size() >= blob_min_size) {
erased_border.push_back(pos);
}
}
else {
// Find out which blob currently owns this pixel.
blob *owner = f->owners[pos];
checked.insert(owner);
if (owner != bl) {
double dist;
color c1 = create_color(owner->r,owner->g,owner->b,owner->a);
color c2 = create_color(bl->r, bl->g, bl->b, bl->a);
dist = color_distance(c1, c2);
if ((dist <= blob_threshold || !check_threshold) && (!best_found || dist <= best_dist)) {
if (best_found && dist == best_dist) {
std::uniform_int_distribution<size_t> uniform_dist_border (0, best_equal);
best_equal++;
if (uniform_dist_border(e1) != 0) continue;
}
else {
best_equal = 1;
}
best_found = true;
best_match = owner;
best_dist = dist;
}
}
}
}
}
if (best_found) {
// Merge with this blob.
size_t surface_1 = bl->surface.size();
size_t surface_2 = best_match->surface.size();
double weight = (surface_2) / double(surface_1 + surface_2);
bl->x = ((1.0 - weight) * bl->x + weight * best_match->x);
bl->y = ((1.0 - weight) * bl->y + weight * best_match->y);
bl->r = ((1.0 - weight) * bl->r + weight * best_match->r);
bl->g = ((1.0 - weight) * bl->g + weight * best_match->g);
bl->b = ((1.0 - weight) * bl->b + weight * best_match->b);
bl->a = ((1.0 - weight) * bl->a + weight * best_match->a);
for (it=best_match->surface.begin(); it!=best_match->surface.end(); ++it) {
f->owners[*it] = bl;
bl->surface.insert(*it);
}
for (it=best_match->border.begin(); it!=best_match->border.end(); ++it) {
bl->border.insert(*it);
}
best_match->surface.clear();
best_match->border .clear();
size_t blob_index = best_match->index;
f->blobs[blob_index] = bl;
bl->index = blob_index;
bl->unified = (bl->unified || best_match->unified);
delete best_match;
f->blobs.pop_back();
}
else {
size_t bb = f->first_expansion;
if (bl->surface.size() < blob_min_size && bl->border.size() > 0 && check_threshold) {
// This outlier has neighbours but it is undersized. Discard colour threshold constraint.
check_threshold = false;
checked.clear();
goto IgnoreColourThreshold;
}
bl->border.clear(); // Don't attempt to expand any more.
if (bb < sz) {
// Swap it with last random blob.
blob *other = f->blobs[bb];
if (other) other->index = b;
f->blobs[b] = other;
f->blobs[bb] = bl;
bl->index = bb;
f->first_expansion++;
}
else return true; // None of the remaining blobs can expand any more.
}
// Remove erased borders.
size_t esz = erased_border.size();
for (size_t i=0; i<esz; ++i) {
bl->border.erase(erased_border[i]);
}
return false;
}
bool thread::blobify() {
bool done = true;
std::map<size_t, frame>::iterator it;
for (it=frames.begin(); it!=frames.end(); ++it) {
done &= blobify_frame(it->first);
}
return done;
}
// Returns false when not finished.
bool thread::unify_frame(size_t frame_key) {
frame* f = &frames[frame_key];
size_t fexp; // Only unify blobs that cannot expand any more.
size_t sz;
size_t bb;
blob *bl, *ubl;
blob *best;
size_t score = 0;
uint16_t ubl_x, ubl_y;
size_t samples;
bool done = true;
size_t blobs_before = 0;
std::set<size_t>::iterator it;
// Remove all sequential nullptrs from the end of blobs.
while (!f->blobs.empty() && f->blobs.back() == nullptr) {
f->blobs.pop_back();
}
blobs_before = f->blobs.size();
if (blob_box_samples == 0 || blobs_before <= blob_number) return true;
Again:
ubl = nullptr;
best = nullptr;
samples = 0;
NotEnoughSamples:
sz = f->blobs.size();
fexp = f->first_expansion;
for (bb = f->first_dust; bb<fexp; ++bb) {
if ((bl = f->blobs[bb]) == nullptr) continue;
if (bl == ubl) {
// Cycle has ended.
goto Samples;
}
if (bl->unified) {
size_t fd = f->first_dust;
if (bb != fd) {
// Swap this already unified blob with current first dust
// and increase the first dust index by 1.
blob *fdb = f->blobs[fd];
if (fdb) fdb->index = bb;
bl->index = fd;
f->blobs[fd] = bl;
f->blobs[bb] = fdb;
}
f->first_dust++;
goto Again;
}
if (samples >= blob_box_samples) {
Samples:
samples = 0;
if (best == nullptr) {
// Nothing suitable for merging.
if (done) {
ubl->unified = true;
}
bb = ubl->index;
ubl = nullptr;
continue;
}
else {
// Enough samples gathered, now merge with the best one.
size_t bi = best->index;
size_t surface_1 = ubl->surface.size();
size_t surface_2 = best->surface.size();
double weight = (surface_2) / double(surface_1 + surface_2);
ubl->x = ((1.0 - weight) * ubl->x + weight * best->x);
ubl->y = ((1.0 - weight) * ubl->y + weight * best->y);
ubl->r = ((1.0 - weight) * ubl->r + weight * best->r);
ubl->g = ((1.0 - weight) * ubl->g + weight * best->g);
ubl->b = ((1.0 - weight) * ubl->b + weight * best->b);
ubl->a = ((1.0 - weight) * ubl->a + weight * best->a);
for (it=best->surface.begin(); it!=best->surface.end(); ++it) {
f->owners[*it] = ubl;
ubl->surface.insert(*it);
}
best->surface.clear();
best->border .clear();
ubl ->border .clear(); // Not needed because it's a pixel dust cloud anyway.
f->blobs[bi] = nullptr;
delete best;
best = nullptr;
if (sz == fexp) {
// sz should always equal to fexp if everything
// has been properly blobified before unifying.
// This optimization is only then possible.
if (f->blobs.back()) {
// Last element is not nullptr, swap it with blobs[bi]
// so that the latter can be safely popped later.
size_t last_index = f->blobs.back()->index;
f->blobs[bi] = f->blobs.back();
f->blobs[bi]->index = bi;
f->blobs[last_index] = nullptr;
}
while (!f->blobs.empty() && f->blobs.back() == nullptr) {
f->blobs.pop_back();
sz--;
fexp--;
f->first_expansion--;
}
}
done = false;
if (sz <= blob_number) {
ubl->unified = true;
return false;
}
}
bb = ubl->index; // Next, take a blob right after ubl.
ubl = nullptr;
continue;
}
if (ubl == nullptr) {
ubl = bl;
ubl_x = round(ubl->x);
ubl_y = round(ubl->y);
continue;
}
samples++;
std::pair<size_t, size_t> bounds_x = std::minmax(ubl_x, uint16_t(round(bl->x)));
std::pair<size_t, size_t> bounds_y = std::minmax(ubl_y, uint16_t(round(bl->y)));
if (bounds_x.first + blob_box_grip >= bounds_x.second
&& bounds_y.first + blob_box_grip >= bounds_y.second) {
size_t dx,dy,current_score;
dx = (bounds_x.second - bounds_x.first);
dy = (bounds_y.second - bounds_y.first);
current_score = dx*dx+dy*dy;
if (best == nullptr
|| current_score < score) {
best = bl;
score = current_score;
continue;
}
}
}
if (samples > 0 && ubl) {
goto NotEnoughSamples;
}
return done;
}
bool thread::unify() {
bool done = true;
std::map<size_t, frame>::iterator it;
for (it=frames.begin(); it!=frames.end(); ++it) {
done = (done && unify_frame(it->first));
}
return done;
}
bool thread::match() {
if (blob_map == nullptr) {
// Find the frame with the largest number of blobs.
std::map<size_t, frame>::iterator it;
size_t blob_count = 0;
size_t frame_count= 0;
for (it=frames.begin(); it!=frames.end(); ++it) {
frame *f = &(it->second);
if (blob_count < f->blobs.size()) {
blob_count = f->blobs.size();
}
frame_count++;
}
blob_map_w = blob_count;
blob_map_h = frame_count;
size_t i;
blob_map = new (std::nothrow) blob** [blob_map_w];
if (blob_map) {
for (i = 0 ; i < blob_map_w ; ++i ) {
blob_map[i] = new (std::nothrow) blob* [blob_map_h];
if (blob_map[i] == nullptr) {
for(size_t j = 0; j<=i; ++j) {
delete [] blob_map[j];
}
delete [] blob_map;
blob_map = nullptr;
return false;
}
}
// Fill blob map with pointers to real blobs.
size_t f=0;
for (it=frames.begin(); it!=frames.end(); ++it) {
frame *fp = &(it->second);
// Add empty blobs if needed.
while (fp->blobs.size() < blob_count) {
blob* new_blob = new blob;
new_blob->x = fp->x;
new_blob->y = fp->y;
new_blob->r = fp->r;
new_blob->g = fp->g;
new_blob->b = fp->b;
new_blob->a = fp->a;
new_blob->unified = true;
new_blob->index = fp->blobs.size();
fp->blobs.push_back(new_blob);
}
size_t blobs = fp->blobs.size();
std::shuffle(fp->blobs.begin(), fp->blobs.end(), e1);
for (size_t b=0; b<blobs; ++b) {
blob_map[b][f] = fp->blobs[b];
fp->blobs[b]->group = b;
fp->blobs[b]->index = b;
}
++f;
}
blob_map_e = get_energy(blob_map);
}
return false;
}
if (blob_map_h == 0 || blob_map_w <= 1) return true;
// Pick 2 blobs randomly and swap them if it would decrease the energy.
std::uniform_int_distribution<size_t> uniform_dist_x(0, blob_map_w - 1);
std::uniform_int_distribution<size_t> uniform_dist_y(0, blob_map_h - 1);
size_t x1,x2;
size_t y = uniform_dist_y(e1);
size_t y_next = (y+1) % blob_map_h;
size_t y_prev = (y>0 ? y-1 : blob_map_h - 1);
x1 = uniform_dist_x(e1);
do {
x2 = uniform_dist_x(e1);
} while (x1 == x2);
blob* x1_y_prev = blob_map[x1][y_prev];
blob* x2_y_prev = blob_map[x2][y_prev];
blob* x1_y = blob_map[x1][y ];
blob* x2_y = blob_map[x2][y ];
blob* x1_y_next = blob_map[x1][y_next];
blob* x2_y_next = blob_map[x2][y_next];
bool x1_volatile = x1_y->surface.empty();
bool x2_volatile = x2_y->surface.empty();
if (x1_volatile && x2_volatile) {
return false; // No point to swap empty blobs.
}
double x1_e_before = blob_distance(x1_y_prev, x1_y) + blob_distance(x1_y, x1_y_next);
double x2_e_before = blob_distance(x2_y_prev, x2_y) + blob_distance(x2_y, x2_y_next);
double x1_e_after = blob_distance(x2_y_prev, x1_y) + blob_distance(x1_y, x2_y_next);
double x2_e_after = blob_distance(x1_y_prev, x2_y) + blob_distance(x2_y, x1_y_next);
double c1 = x1_e_before + x2_e_before;
double c2 = x1_e_after + x2_e_after;
if (c1 >= c2 || (degenerate && (counter % degenerate) == 0)) {
blob *buf = blob_map[x2][y];
blob_map[x2][y] = blob_map[x1][y];
blob_map[x1][y] = buf;
double gain = c1 - c2;
blob_map_e -= gain;
if (blob_map_e < 0.0) blob_map_e = 0.0;
if (blob_map_e < best_e) {
best_e = blob_map_e;
best_blob_map_e = best_e;
counter = 0;
if (deviant) {
// Refresh all groups.
for (size_t j=0; j<blob_map_h; ++j) {
for (size_t i=0; i<blob_map_w; ++i) {
blob_map[i][j]->group = i;
}
}
}
else {
blob_map[x1][y]->group = x1;
blob_map[x2][y]->group = x2;
}
deviant = false;
}
else deviant = true;
}
if (best_e == 0.0) return true;
return false;
}
bool thread::init_morph() {
size_t i,j;
if (chains.empty() && blob_map) {
chain_map_e = 0.0;
// Refresh blob map according to groups.
// Needed in case deviant was left TRUE so
// that the blob map might be messed up.
std::map<size_t, frame>::iterator it;
size_t frame_index = 0;
for (it=frames.begin(); it!=frames.end(); ++it) {
std::vector<blob*> *blobs = &(it->second.blobs);
size_t blob_count = blobs->size();
for (i=0; i<blob_count; ++i) {
blob_map[blobs->at(i)->group][frame_index] = blobs->at(i);
}
frame_index++;
}
// Make sure that volatile blobs have their positions averaged.
for (i=0; i<blob_map_w; ++i) {
std::vector<blob *> to_be_fixed;
for (j=0; j<blob_map_h; ++j) {
to_be_fixed.push_back(blob_map[i][j]);
}
fix_volatiles(&to_be_fixed);
to_be_fixed.clear();
}
for (i=0; i<blob_map_w; ++i) {
size_t max_size=0;
// Find out the largest blob.
for (j=0; j<blob_map_h; ++j) {
if (blob_map[i][j]->surface.size() > max_size) {
max_size = blob_map[i][j]->surface.size();
}
}
size_t point_count = max_size * point_density;
// Then create a blob chain that includes all the blobs that share the same group.
for (j=0; j<blob_map_h; ++j) {
if (chains[i].points == nullptr) {
if (!renew_chain( &(chains[i]), point_count, blob_map_h)) {
chains.erase(i);
return false;
}
}
// Remember the maximum surface area this blob can take:
chains[i].max_surface = max_size;
// Initialize the points in this chain at key frame j.
pixel px;
size_t p, pos;
std::vector<point> points;
blob *bl = blob_map[i][j];
point *pt;
std::set<size_t>::iterator it;
size_t frame_key;
std::map<size_t, frame>::iterator fit = frames.begin();
assert(frames.size() > j);
std::advance(fit, j);
frame_key = fit->first;
std::uniform_int_distribution<uint8_t> uniform_dist_byte(0, UINT8_MAX);
p = 0;
for (it=bl->surface.begin(); it!=bl->surface.end(); ++it) {
pos = *it;
px = get_pixel(frame_key, pos);
pt = &(chains[i].points[p][j]);
pt->s.x = px.x;
pt->s.y = px.y;
pt->s.flags = HAS_PIXEL|HAS_FLUID;
pt->s.x_fract = 0;
pt->s.y_fract = 0;
points.push_back(*pt);
++p;
}
if (points.empty()) {
point volatile_point;
volatile_point.s.x = std::round(bl->x);
volatile_point.s.y = std::round(bl->y);
volatile_point.s.flags = HAS_FLUID; // This is still needed for the trajectories of fluid particles.
volatile_point.s.x_fract= 0;
volatile_point.s.y_fract= 0;
chains[i].points[p][j] = volatile_point;
points.push_back(volatile_point);
++p;
}
std::shuffle(points.begin(), points.end(), e1);
size_t psz = points.size();
for (; p<chains[i].width; ++p) {
pt = &(chains[i].points[p][j]);
pt->s.x = points[p%psz].s.x;
pt->s.y = points[p%psz].s.y;
pt->s.flags = points[p%psz].s.flags;
pt->s.flags &= ~HAS_FLUID; // Duplicates must not represent fluid particles.
pt->s.x_fract = uniform_dist_byte(e1);
pt->s.y_fract = uniform_dist_byte(e1);
}
#ifdef ATOMORPH_OPENCV
// Initiate Locality Sensitive Hashing:
//cv::flann::LinearIndexParams indexParams;
cv::flann::KDTreeIndexParams indexParams(16);
cv::Mat * features = new cv::Mat(chains[i].width, 2, CV_32F);
if (features) {
for (size_t f=0; f<chains[i].width; ++f) {
float x,y;
point2xy(chains[i].points[f][j], &x, &y);
features->at<float>(f, 0) = x;
features->at<float>(f, 1) = y;
chains[i].places[f][j] = f;
}
cv::flann::Index *kdtree = new cv::flann::Index(*features, indexParams, cvflann::FLANN_DIST_L1);
chains[i].kdtrees[j] = (void *) kdtree;
chains[i].feature[j] = (void *) features;
}
#endif
chains[i].energy = get_energy(&(chains[i]));
chain_map_e += chains[i].energy;
}
}
if (chains.empty()) return false;
}
// These extra checks are done here in advance to have the morph
// function as fast as possible, without wasting any time on the
// sanity checks.
if (blob_map_h == 0 || blob_map_w == 0) return false;
size_t max_w = 0;
for (i=0; i<blob_map_w; ++i) {
chain *ch = &(chains[i]);
if (ch->height == 0) return false;
if (ch->width > max_w) max_w = ch->width;
}
if (max_w <= 1) return false;
return true;
}
#ifdef ATOMORPH_OPENCV
bool thread::morph() {
size_t x1, x2;
chain *ch = &(chains[counter % blob_map_w]);
if (ch->width <= 1) return false;
std::uniform_int_distribution<size_t> uniform_dist_x(0, ch->width - 1);
std::uniform_int_distribution<size_t> uniform_dist_y(0, ch->height- 1);
size_t y = uniform_dist_y(e1);
size_t y_next = (y+1) % ch->height;
size_t y_prev = (y>0 ? y-1 : ch->height - 1);
x1 = uniform_dist_x(e1);
do {
x2 = uniform_dist_x(e1);
} while (x1 == x2);
cv::flann::Index *kdtree = (cv::flann::Index *) ch->kdtrees[y_next];
const size_t knn = 2; // Number of nearest neighbors to search for
std::vector<float> query1; // Search near the source
std::vector<float> query2; // Search near the destination
std::vector<int> index1(knn);
std::vector<int> index2(knn);
std::vector<float> dist_1(knn);
std::vector<float> dist_2(knn);
float qx1, qx2, qy1, qy2;
point2xy(ch->points[x1][y] ,&qx1, &qy1);
query1.push_back(qx1);
query1.push_back(qy1);
point2xy(ch->points[x1][y_next] ,&qx2, &qy2);
query2.push_back(qx2);
query2.push_back(qy2);
kdtree->knnSearch(query1, index1, dist_1, knn, cv::flann::SearchParams(1));
kdtree->knnSearch(query2, index2, dist_2, knn, cv::flann::SearchParams(1));
std::vector<size_t> x2s; // candidates
while (!index1.empty()) {
size_t xn = ch->places[index1.back()][y_next];
if (xn != x1) {
x2s.push_back(xn);
}
index1.pop_back();
}
while (!index2.empty()) {
size_t xn = ch->places[index2.back()][y_next];
if (xn != x1) {
x2s.push_back(xn);
}
index2.pop_back();
}
x2s.push_back(x2);
std::uniform_int_distribution<size_t> uniform_dist_x2s(0, x2s.size() - 1);
x2 = x2s[uniform_dist_x2s(e1)];
point **map = ch->points;
point x1_y_prev = map[x1][y_prev];
point x2_y_prev = map[x2][y_prev];
point x1_y = map[x1][y ];
point x2_y = map[x2][y ];
point x1_y_next = map[x1][y_next];
point x2_y_next = map[x2][y_next];
double x1_e_before = point_distance(x1_y_prev, x1_y) + point_distance(x1_y, x1_y_next);
double x2_e_before = point_distance(x2_y_prev, x2_y) + point_distance(x2_y, x2_y_next);
double x1_e_after = point_distance(x2_y_prev, x1_y) + point_distance(x1_y, x2_y_next);
double x2_e_after = point_distance(x1_y_prev, x2_y) + point_distance(x2_y, x1_y_next);
double c1 = x1_e_before + x2_e_before;
double c2 = x1_e_after + x2_e_after;
if (c1 >= c2) {
// Make sure point map indexes are mapped correctly:
size_t **plm = ch->places;
plm[x2][y] = x1;
plm[x1][y] = x2;
point buf = map[x2][y];
map[x2][y] = map[x1][y];
map[x1][y] = buf;
double gain = c1 - c2;
chain_map_e -= gain;
if (chain_map_e < 0.0) chain_map_e = 0.0;
}
return false;
}
#else
std::mutex morph_mutex;
void morph_asynch(point **map, size_t width, size_t height, size_t repeat, unsigned rng_seed, double *gain) {
std::mt19937 gen(rng_seed);
std::uniform_int_distribution<size_t> uniform_dist_x(0, width -1);
std::uniform_int_distribution<size_t> uniform_dist_y(0, height-1);
size_t y = uniform_dist_y(gen);
size_t y_next = (y+1) % (height);
size_t y_prev = (y>0 ? y-1 : height-1);
for (size_t i=0; i<repeat; ++i) {
size_t x1, x2;
x1 = uniform_dist_x(gen);
do {
x2 = uniform_dist_x(gen);
} while (x1 == x2);
point x1_y_prev = map[x1][y_prev];
point x2_y_prev = map[x2][y_prev];
point x1_y = map[x1][y ];
point x2_y = map[x2][y ];
point x1_y_next = map[x1][y_next];
point x2_y_next = map[x2][y_next];
double x1_e_before = point_distance(x1_y_prev, x1_y) + point_distance(x1_y, x1_y_next);
double x2_e_before = point_distance(x2_y_prev, x2_y) + point_distance(x2_y, x2_y_next);
double x1_e_after = point_distance(x2_y_prev, x1_y) + point_distance(x1_y, x2_y_next);
double x2_e_after = point_distance(x1_y_prev, x2_y) + point_distance(x2_y, x1_y_next);
double c1 = x1_e_before + x2_e_before;
double c2 = x1_e_after + x2_e_after;
if (c1 >= c2) {
std::lock_guard<std::mutex> lock(morph_mutex);
// Make sure no other thread has touched these points yet:
if (sizeof(point) == 8) {
if (map[x1][y].word != x1_y.word
|| map[x2][y].word != x2_y.word) continue;
}
else {
if (!std::memcmp(&map[x1][y], &x1_y, sizeof(point))
|| !std::memcmp(&map[x2][y], &x2_y, sizeof(point))) continue;
}
point buf = map[x2][y];
map[x2][y] = map[x1][y];
map[x1][y] = buf;
*gain += c1 - c2;
}
}
}
bool thread::morph() {
chain *ch = &(chains[counter % blob_map_w]);
if (ch->width <= 1) return false;
std::uniform_int_distribution<unsigned> uniform_dist_seed(0, std::numeric_limits<unsigned>::max());
point **map = ch->points;
double gain = 0.0;
if (thread_count == 0) {
morph_asynch(map, ch->width, ch->height, cycle_length, uniform_dist_seed(e1), &gain);
}
else {
std::vector<std::thread> workers;
for (size_t i=0; i<thread_count; ++i) {
workers.push_back(std::thread(morph_asynch, map, ch->width, ch->height, cycle_length, uniform_dist_seed(e1), &gain));
}
while (!workers.empty()) {
workers.back().join();
workers.pop_back();
}
}
chain_map_e-=gain;
return false;
}
#endif
size_t thread::get_blob_count (size_t f) {
if (!has_frame(f)) return 0;
return frames[f].blobs.size();
}
size_t thread::get_blob_count () {
size_t count = 0;
std::map<size_t, frame>::iterator it;
for (it=frames.begin(); it!=frames.end(); ++it) {
count += get_blob_count(it->first);
}
return count;
}
double thread::get_energy(struct blob ***map) {
if (blob_map_h == 0
|| blob_map_w == 0) return 0.0;
blob *pframe_blob, *cframe_blob;
double e=0.0;
for (size_t i=0; i<blob_map_w; ++i) {
pframe_blob = map[i][blob_map_h-1];
for (size_t j=0; j<blob_map_h; ++j) {
cframe_blob = map[i][j];
e += blob_distance(pframe_blob, cframe_blob);
pframe_blob = cframe_blob;
}
}
return e;
}
double thread::get_energy(chain *ch){
size_t w = ch->width;
size_t h = ch->height;
if (w <= 1 || h == 0) return 0.0;
point **map = ch->points;
double e=0.0;
for (size_t i=0; i<w; ++i) {
for (size_t j=0; j<h; ++j) {
size_t next = (j+1)%h;
e += point_distance(map[i][j], map[i][next]);
}
}
return e;
}
void thread::set_bbox(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
if (running && !paused) return;
if (x1 > x2 || y1 > y2) return;
bbox_x1 = x1;
bbox_x2 = x2;
bbox_y1 = y1;
bbox_y2 = y2;
int dx = bbox_x2 - bbox_x1;
int dy = bbox_y2 - bbox_y1;
bbox_d = dx*dx + dy*dy;
}
void thread::set_blob_weights(unsigned char rgba, unsigned char size, unsigned char xy) {
if (running && !paused) return;
double sum = rgba*rgba + size*size + xy*xy;
if (sum == 0.0) return;
blob_rgba_weight = double(rgba*rgba)/sum;
blob_size_weight = double(size*size)/sum;
blob_xy_weight = 1.0 - (blob_rgba_weight + blob_size_weight);
}
double thread::blob_distance(const blob *b1, const blob *b2) {
size_t sz1 = b1->surface.size();
size_t sz2 = b2->surface.size();
size_t szs = sz1+sz2;
double pix_dist = 0.0;
double col_dist = 0.0;
double siz_dist = 0.0;
if (szs > 0) {
siz_dist = fabs(double(sz1)-sz2)/double(szs);
}
if (sz1 > 0 && sz2 > 0) {
pixel p1,p2;
p1 = create_pixel(b1->x, b1->y, create_color(b1->r, b1->g, b1->b, b1->a));
p2 = create_pixel(b2->x, b2->y, create_color(b2->r, b2->g, b2->b, b2->a));
pix_dist = sqrt(double(pixel_distance(p1, p2))/bbox_d);
col_dist = color_distance(p1.c, p2.c);
}
return (blob_xy_weight * pix_dist +
blob_rgba_weight * col_dist +
blob_size_weight * siz_dist);
}
double thread::get_energy() {
if (!paused) return std::numeric_limits<double>::infinity();
double e = -1.0;
switch (state) {
case STATE_BLOB_MATCHING: e = best_blob_map_e; break;
case STATE_ATOM_MORPHING: e = chain_map_e; break;
default: break;
}
return e;
}
void thread::fix_volatiles (std::vector<blob *> *to_be_fixed) {
size_t sz = to_be_fixed->size();
if (sz <= 1) return;
size_t i = 0;
bool started = false;
std::vector<blob *> volatiles;
blob *first_static = nullptr;
blob *previous_static = nullptr;
while (1) {
i = (i+1)%sz;
blob *bl = to_be_fixed->at(i);
bool empty = bl->surface.empty();
if (!started) {
if (empty) {
if (i == 0) break; // All are empty.
continue;
}
started = true;
first_static = bl;
previous_static = bl;
continue;
}
if (empty) {
volatiles.push_back(bl);
continue;
}
if (!volatiles.empty()) {
// Fix the range between previous_static and bl.
size_t vsz = volatiles.size();
for (size_t v=0; v<vsz; ++v) {
double t = (v+1.0) / double(vsz+1.0);
volatiles[v]->x = t*bl->x + (1.0 - t)*previous_static->x;
volatiles[v]->y = t*bl->y + (1.0 - t)*previous_static->y;
}
volatiles.clear();
}
previous_static = bl;
if (previous_static == first_static) break; // Cycle has ended.
}
}
}