--- /dev/null
+
+#########################################################################
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the version 3 of the GNU General Public License #
+# as published by the Free Software Foundation. #
+# #
+# This program is distributed in the hope that it will be useful, but #
+# WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
+# General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+# Written by Francois Fleuret, (C) IDIAP #
+# Contact <francois.fleuret@idiap.ch> for comments & bug reports #
+#########################################################################
+
+CXX=g++-4.1
+
+ifeq ($(STATIC),yes)
+ LDFLAGS=-static -lm -ljpeg -lpng -lz
+else
+ LDFLAGS=-lm -ljpeg -lpng
+endif
+
+ifeq ($(DEBUG),yes)
+ OPTIMIZE_FLAG = -ggdb3 -DDEBUG
+else
+ OPTIMIZE_FLAG = -ggdb3 -O3
+endif
+
+ifeq ($(PROFILE),yes)
+ PROFILE_FLAG = -pg
+endif
+
+CXXFLAGS = -Wall $(OPTIMIZE_FLAG) $(PROFILE_FLAG)
+
+# ifeq ($(CURSES),yes)
+# LDFLAGS=$(LDFLAGS) -lncurses
+# CXXFLAGS = $(CXXFLAGS) -DWITH_CURSES
+# endif
+
+all: folding list_to_pool TAGS
+
+TAGS: *.cc *.h
+ etags --members -l c++ *.cc *.h
+
+folding: misc.o interval.o gaussian.o fusion_sort.o global.o tools.o \
+ progress_bar.o chrono.o \
+ jpeg_misc.o rgb_image.o rgb_image_subpixel.o param_parser.o \
+ shared.o \
+ storable.o \
+ image.o rich_image.o \
+ labelled_image.o \
+ labelled_image_pool.o labelled_image_pool_file.o labelled_image_pool_subset.o \
+ pose.o pose_cell.o pose_cell_set.o pose_cell_scored_set.o \
+ parsing.o parsing_pool.o \
+ pose_cell_hierarchy.o pose_cell_hierarchy_reader.o \
+ pi_referential.o pi_feature.o pi_feature_family.o \
+ sample_set.o \
+ shared_responses.o \
+ classifier.o classifier_reader.o \
+ detector.o \
+ loss_machine.o decision_tree.o boosted_classifier.o \
+ error_rates.o \
+ materials.o \
+ folding.o
+ $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
+
+list_to_pool: misc.o interval.o fusion_sort.o shared.o global.o progress_bar.o \
+ storable.o \
+ jpeg_misc.o rgb_image.o param_parser.o \
+ image.o rich_image.o \
+ pose.o pose_cell.o \
+ labelled_image.o labelled_image_pool.o \
+ list_to_pool.o
+ $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
+
+Makefile.depend: *.h *.cc Makefile
+ $(CC) -M *.cc > Makefile.depend
+
+archive:
+ cd .. && tar zcvf folding-gpl.tgz folding/*.{cc,h,sh,txt} folding/Makefile folding/gpl-3.0.txt
+
+clean:
+ \rm -f folding list_to_pool *.o Makefile.depend TAGS
+
+-include Makefile.depend
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "classifier_reader.h"
+#include "fusion_sort.h"
+
+#include "boosted_classifier.h"
+#include "tools.h"
+
+BoostedClassifier::BoostedClassifier(int nb_weak_learners) {
+ _loss_type = global.loss_type;
+ _nb_weak_learners = nb_weak_learners;
+ _weak_learners = 0;
+}
+
+BoostedClassifier::BoostedClassifier() {
+ _loss_type = global.loss_type;
+ _nb_weak_learners = 0;
+ _weak_learners = 0;
+}
+
+BoostedClassifier::~BoostedClassifier() {
+ if(_weak_learners) {
+ for(int w = 0; w < _nb_weak_learners; w++)
+ delete _weak_learners[w];
+ delete[] _weak_learners;
+ }
+}
+
+scalar_t BoostedClassifier::response(SampleSet *sample_set, int n_sample) {
+ scalar_t r = 0;
+ for(int w = 0; w < _nb_weak_learners; w++) {
+ r += _weak_learners[w]->response(sample_set, n_sample);
+ ASSERT(!isnan(r));
+ }
+ return r;
+}
+
+void BoostedClassifier::train(LossMachine *loss_machine,
+ SampleSet *sample_set, scalar_t *train_responses) {
+
+ if(_weak_learners) {
+ cerr << "Can not re-train a BoostedClassifier" << endl;
+ exit(1);
+ }
+
+ int nb_pos = 0, nb_neg = 0;
+
+ for(int s = 0; s < sample_set->nb_samples(); s++) {
+ if(sample_set->label(s) > 0) nb_pos++;
+ else if(sample_set->label(s) < 0) nb_neg++;
+ }
+
+ _weak_learners = new DecisionTree *[_nb_weak_learners];
+
+ (*global.log_stream) << "With " << nb_pos << " positive and " << nb_neg << " negative samples." << endl;
+
+ for(int w = 0; w < _nb_weak_learners; w++) {
+
+ _weak_learners[w] = new DecisionTree();
+ _weak_learners[w]->train(loss_machine, sample_set, train_responses);
+
+ for(int n = 0; n < sample_set->nb_samples(); n++)
+ train_responses[n] += _weak_learners[w]->response(sample_set, n);
+
+ (*global.log_stream) << "Weak learner " << w
+ << " depth " << _weak_learners[w]->depth()
+ << " nb_leaves " << _weak_learners[w]->nb_leaves()
+ << " train loss " << loss_machine->loss(sample_set, train_responses)
+ << endl;
+
+ }
+
+ (*global.log_stream) << "Built a classifier with " << _nb_weak_learners << " weak_learners." << endl;
+}
+
+void BoostedClassifier::tag_used_features(bool *used) {
+ for(int w = 0; w < _nb_weak_learners; w++)
+ _weak_learners[w]->tag_used_features(used);
+}
+
+void BoostedClassifier::re_index_features(int *new_indexes) {
+ for(int w = 0; w < _nb_weak_learners; w++)
+ _weak_learners[w]->re_index_features(new_indexes);
+}
+
+void BoostedClassifier::read(istream *is) {
+ if(_weak_learners) {
+ cerr << "Can not read over an existing BoostedClassifier" << endl;
+ exit(1);
+ }
+
+ read_var(is, &_nb_weak_learners);
+ _weak_learners = new DecisionTree *[_nb_weak_learners];
+ for(int w = 0; w < _nb_weak_learners; w++) {
+ _weak_learners[w] = new DecisionTree();
+ _weak_learners[w]->read(is);
+ (*global.log_stream) << "Read tree " << w << " of depth "
+ << _weak_learners[w]->depth() << " with "
+ << _weak_learners[w]->nb_leaves() << " leaves." << endl;
+ }
+
+ (*global.log_stream)
+ << "Read BoostedClassifier containing " << _nb_weak_learners << " weak learners." << endl;
+}
+
+void BoostedClassifier::write(ostream *os) {
+ unsigned int id;
+ id = CLASSIFIER_BOOSTED;
+ write_var(os, &id);
+
+ write_var(os, &_nb_weak_learners);
+ for(int w = 0; w < _nb_weak_learners; w++)
+ _weak_learners[w]->write(os);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+
+ This class is an implementation of the Classifier with a boosting of
+ tree. It works with samples from R^n and has no concept of the
+ pi-features.
+
+*/
+
+#ifndef BOOSTED_CLASSIFIER_H
+#define BOOSTED_CLASSIFIER_H
+
+#include "classifier.h"
+#include "sample_set.h"
+#include "decision_tree.h"
+#include "loss_machine.h"
+
+class BoostedClassifier : public Classifier {
+public:
+
+ int _loss_type;
+ int _nb_weak_learners;
+ DecisionTree **_weak_learners;
+
+public:
+
+ BoostedClassifier(int nb_weak_learners);
+ BoostedClassifier();
+ virtual ~BoostedClassifier();
+
+ virtual scalar_t response(SampleSet *sample_set, int n_sample);
+ virtual void train(LossMachine *loss_machine, SampleSet *train, scalar_t *response);
+
+ virtual void tag_used_features(bool *used);
+ virtual void re_index_features(int *new_indexes);
+
+ virtual void read(istream *is);
+ virtual void write(ostream *os);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include <string.h>
+
+#include "chrono.h"
+
+Chrono::Chrono() : _nb_ticks_per_second(scalar_t(sysconf(_SC_CLK_TCK))),
+ _nb(0), _current(-1) { }
+
+void Chrono::print(ostream *os) {
+ scalar_t total_durations = 0;
+ for(int n = 0; n < _nb; n++) total_durations += _durations[n];
+ for(int n = 0; n < _nb; n++)
+ (*os) << "INFO CHRONO_" << _labels[n]
+ << " " << scalar_t(_durations[n] * 100) / total_durations << "%"
+ << " " << _durations[n] << " seconds"
+ << endl;
+}
+
+void Chrono::start(const char *label) {
+ if(_current >= 0) {
+ struct tms tmp;
+ times(&tmp);
+ _durations[_current] += scalar_t(tmp.tms_stime - _last.tms_stime)/_nb_ticks_per_second;
+ }
+
+ if(label) {
+ _current = 0;
+ while(_current < _nb && strcmp(_labels[_current], label) != 0)
+ _current++;
+ if(_current == _nb) {
+ if(_nb < _nb_max) {
+ strncpy(_labels[_nb], label, buffer_size);
+ _durations[_nb] = 0;
+ _nb++;
+ } else {
+ cerr << "Too many timers." << endl;
+ exit(1);
+ }
+ }
+ } else _current = -1;
+
+ times(&_last);
+}
+
+void Chrono::stop() { start(0); }
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef CHRONO_H
+#define CHRONO_H
+
+#include <sys/times.h>
+#include "misc.h"
+
+class Chrono {
+ struct tms _last;
+ scalar_t _nb_ticks_per_second;
+ static const int _nb_max = 32;
+ int _nb, _current;
+ char _labels[_nb_max][buffer_size];
+ scalar_t _durations[_nb_max];
+public:
+ Chrono();
+ void print(ostream *os);
+ void start(const char *label);
+ void stop();
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "classifier.h"
+
+Classifier::~Classifier() { }
+
+void Classifier::extract_pi_feature_family(PiFeatureFamily *full_pi_feature_family,
+ PiFeatureFamily *extracted_pi_feature_family) {
+
+ bool *used_features = new bool[full_pi_feature_family->nb_features()];
+ int *new_feature_indexes = new int[full_pi_feature_family->nb_features()];
+
+ for(int f = 0; f < full_pi_feature_family->nb_features(); f++)
+ used_features[f] = false;
+
+ tag_used_features(used_features);
+
+ extracted_pi_feature_family->extract(full_pi_feature_family,
+ used_features,
+ new_feature_indexes);
+
+ re_index_features(new_feature_indexes);
+
+ delete[] new_feature_indexes;
+ delete[] used_features;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+
+ This class is mostly able to learn a classifier from a SampleSet and
+ to provide a scalar response on any test sample. Additional methods
+ are required for persistence and select the possibly very few used
+ features.
+
+*/
+
+#ifndef CLASSIFIER_H
+#define CLASSIFIER_H
+
+#include "storable.h"
+#include "pi_feature_family.h"
+#include "sample_set.h"
+#include "pose_cell_hierarchy.h"
+#include "labelled_image_pool.h"
+#include "loss_machine.h"
+
+class Classifier : public Storable {
+public:
+ virtual ~Classifier();
+
+ // Evaluate on a sample
+ virtual scalar_t response(SampleSet *sample_set, int n_sample) = 0;
+
+ // Train the classifier
+ virtual void train(LossMachine *loss_machine, SampleSet *train, scalar_t *response) = 0;
+
+ // Tag in the boolean array which features are actually used
+ virtual void tag_used_features(bool *used) = 0;
+ // Change the indexes of the used features
+ virtual void re_index_features(int *new_indexes) = 0;
+
+ // Storage
+ virtual void read(istream *is) = 0;
+ virtual void write(ostream *os) = 0;
+
+ void extract_pi_feature_family(PiFeatureFamily *full_pi_feature_family,
+ PiFeatureFamily *extracted_pi_feature_family);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "classifier_reader.h"
+#include "misc.h"
+#include "boosted_classifier.h"
+
+Classifier *read_classifier(istream *is) {
+ Classifier *result;
+ unsigned int id;
+
+ read_var(is, &id);
+
+ switch(id) {
+ case CLASSIFIER_BOOSTED:
+ result = new BoostedClassifier();
+ break;
+// case CLASSIFIER_BAYESIAN:
+// result = new BayesianClassifier();
+// break;
+ default:
+ cerr << "Unknown classifier ID " << id << endl;
+ exit(1);
+ }
+
+ result->read(is);
+ return result;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef CLASSIFIER_READER_H
+#define CLASSIFIER_READER_H
+
+#include "classifier.h"
+
+enum { CLASSIFIER_BOOSTED, CLASSIFIER_BAYESIAN };
+
+Classifier *read_classifier(istream *is);
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "decision_tree.h"
+#include "fusion_sort.h"
+
+DecisionTree::DecisionTree() {
+ _feature_index = -1;
+ _threshold = 0;
+ _weight = 0;
+ _subtree_greater = 0;
+ _subtree_lesser = 0;
+}
+
+DecisionTree::~DecisionTree() {
+ if(_subtree_lesser)
+ delete _subtree_lesser;
+ if(_subtree_greater)
+ delete _subtree_greater;
+}
+
+int DecisionTree::nb_leaves() {
+ if(_subtree_lesser ||_subtree_greater)
+ return _subtree_lesser->nb_leaves() + _subtree_greater->nb_leaves();
+ else
+ return 1;
+}
+
+int DecisionTree::depth() {
+ if(_subtree_lesser ||_subtree_greater)
+ return 1 + max(_subtree_lesser->depth(), _subtree_greater->depth());
+ else
+ return 1;
+}
+
+scalar_t DecisionTree::response(SampleSet *sample_set, int n_sample) {
+ if(_subtree_lesser && _subtree_greater) {
+ if(sample_set->feature_value(n_sample, _feature_index) < _threshold)
+ return _subtree_lesser->response(sample_set, n_sample);
+ else
+ return _subtree_greater->response(sample_set, n_sample);
+ } else {
+ return _weight;
+ }
+}
+
+void DecisionTree::pick_best_split(SampleSet *sample_set, scalar_t *loss_derivatives) {
+
+ int nb_samples = sample_set->nb_samples();
+
+ scalar_t *responses = new scalar_t[nb_samples];
+ int *indexes = new int[nb_samples];
+ int *sorted_indexes = new int[nb_samples];
+
+ scalar_t max_abs_sum = 0;
+ _feature_index = -1;
+
+ for(int f = 0; f < sample_set->nb_features(); f++) {
+ scalar_t sum = 0;
+
+ for(int s = 0; s < nb_samples; s++) {
+ indexes[s] = s;
+ responses[s] = sample_set->feature_value(s, f);
+ sum += loss_derivatives[s];
+ }
+
+ indexed_fusion_sort(nb_samples, indexes, sorted_indexes, responses);
+
+ int t, u = sorted_indexes[0];
+ for(int s = 0; s < nb_samples - 1; s++) {
+ t = u;
+ u = sorted_indexes[s + 1];
+ sum -= 2 * loss_derivatives[t];
+
+ if(responses[t] < responses[u] && abs(sum) > max_abs_sum) {
+ max_abs_sum = abs(sum);
+ _feature_index = f;
+ _threshold = (responses[t] + responses[u])/2;
+ }
+ }
+ }
+
+ delete[] indexes;
+ delete[] sorted_indexes;
+ delete[] responses;
+}
+
+void DecisionTree::train(LossMachine *loss_machine,
+ SampleSet *sample_set,
+ scalar_t *current_responses,
+ scalar_t *loss_derivatives,
+ int depth) {
+
+ if(_subtree_lesser || _subtree_greater || _feature_index >= 0) {
+ cerr << "You can not re-train a tree." << endl;
+ abort();
+ }
+
+ int nb_samples = sample_set->nb_samples();
+
+ int nb_pos = 0, nb_neg = 0;
+ for(int s = 0; s < sample_set->nb_samples(); s++) {
+ if(sample_set->label(s) > 0) nb_pos++;
+ else if(sample_set->label(s) < 0) nb_neg++;
+ }
+
+ (*global.log_stream) << "Training tree" << endl;
+ (*global.log_stream) << " nb_samples " << nb_samples << endl;
+ (*global.log_stream) << " depth " << depth << endl;
+ (*global.log_stream) << " nb_pos = " << nb_pos << endl;
+ (*global.log_stream) << " nb_neg = " << nb_neg << endl;
+
+ if(depth >= global.tree_depth_max)
+ (*global.log_stream) << " Maximum depth reached." << endl;
+ if(nb_pos < min_nb_samples_for_split)
+ (*global.log_stream) << " Not enough positive samples." << endl;
+ if(nb_neg < min_nb_samples_for_split)
+ (*global.log_stream) << " Not enough negative samples." << endl;
+
+ if(depth < global.tree_depth_max &&
+ nb_pos >= min_nb_samples_for_split &&
+ nb_neg >= min_nb_samples_for_split) {
+
+ pick_best_split(sample_set, loss_derivatives);
+
+ if(_feature_index >= 0) {
+ int indexes[nb_samples];
+ scalar_t *parted_current_responses = new scalar_t[nb_samples];
+ scalar_t *parted_loss_derivatives = new scalar_t[nb_samples];
+
+ int nb_lesser = 0, nb_greater = 0;
+ int nb_lesser_pos = 0, nb_lesser_neg = 0, nb_greater_pos = 0, nb_greater_neg = 0;
+
+ for(int s = 0; s < nb_samples; s++) {
+ if(sample_set->feature_value(s, _feature_index) < _threshold) {
+ indexes[nb_lesser] = s;
+ parted_current_responses[nb_lesser] = current_responses[s];
+ parted_loss_derivatives[nb_lesser] = loss_derivatives[s];
+
+ if(sample_set->label(s) > 0)
+ nb_lesser_pos++;
+ else if(sample_set->label(s) < 0)
+ nb_lesser_neg++;
+
+ nb_lesser++;
+ } else {
+ nb_greater++;
+
+ indexes[nb_samples - nb_greater] = s;
+ parted_current_responses[nb_samples - nb_greater] = current_responses[s];
+ parted_loss_derivatives[nb_samples - nb_greater] = loss_derivatives[s];
+
+ if(sample_set->label(s) > 0)
+ nb_greater_pos++;
+ else if(sample_set->label(s) < 0)
+ nb_greater_neg++;
+ }
+ }
+
+ if((nb_lesser_pos >= min_nb_samples_for_split ||
+ nb_lesser_neg >= min_nb_samples_for_split) &&
+ (nb_greater_pos >= min_nb_samples_for_split ||
+ nb_greater_neg >= min_nb_samples_for_split)) {
+
+ _subtree_lesser = new DecisionTree();
+
+ {
+ SampleSet sub_sample_set(sample_set, nb_lesser, indexes);
+
+ _subtree_lesser->train(loss_machine,
+ &sub_sample_set,
+ parted_current_responses,
+ parted_loss_derivatives,
+ depth + 1);
+ }
+
+ _subtree_greater = new DecisionTree();
+
+ {
+ SampleSet sub_sample_set(sample_set, nb_greater, indexes + nb_lesser);
+
+ _subtree_greater->train(loss_machine,
+ &sub_sample_set,
+ parted_current_responses + nb_lesser,
+ parted_loss_derivatives + nb_lesser,
+ depth + 1);
+ }
+ }
+
+ delete[] parted_current_responses;
+ delete[] parted_loss_derivatives;
+ } else {
+ (*global.log_stream) << "Could not find a feature for split." << endl;
+ }
+ }
+
+ if(!(_subtree_greater && _subtree_lesser)) {
+ scalar_t *tmp_responses = new scalar_t[nb_samples];
+ for(int s = 0; s < nb_samples; s++)
+ tmp_responses[s] = 1;
+
+ _weight = loss_machine->optimal_weight(sample_set, tmp_responses, current_responses);
+
+ const scalar_t max_weight = 10.0;
+
+ if(_weight > max_weight) {
+ _weight = max_weight;
+ } else if(_weight < - max_weight) {
+ _weight = - max_weight;
+ }
+
+ (*global.log_stream) << " _weight " << _weight << endl;
+
+ delete[] tmp_responses;
+ }
+}
+
+void DecisionTree::train(LossMachine *loss_machine,
+ SampleSet *sample_set,
+ scalar_t *current_responses) {
+
+ scalar_t *loss_derivatives = new scalar_t[sample_set->nb_samples()];
+
+ loss_machine->get_loss_derivatives(sample_set, current_responses, loss_derivatives);
+
+ train(loss_machine, sample_set, current_responses, loss_derivatives, 0);
+
+ delete[] loss_derivatives;
+}
+
+//////////////////////////////////////////////////////////////////////
+
+void DecisionTree::tag_used_features(bool *used) {
+ if(_subtree_lesser && _subtree_greater) {
+ used[_feature_index] = true;
+ _subtree_lesser->tag_used_features(used);
+ _subtree_greater->tag_used_features(used);
+ }
+}
+
+void DecisionTree::re_index_features(int *new_indexes) {
+ if(_subtree_lesser && _subtree_greater) {
+ _feature_index = new_indexes[_feature_index];
+ _subtree_lesser->re_index_features(new_indexes);
+ _subtree_greater->re_index_features(new_indexes);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+void DecisionTree::read(istream *is) {
+ if(_subtree_lesser || _subtree_greater) {
+ cerr << "You can not read in an existing tree." << endl;
+ abort();
+ }
+
+ read_var(is, &_feature_index);
+ read_var(is, &_threshold);
+ read_var(is, &_weight);
+
+ int split;
+ read_var(is, &split);
+
+ if(split) {
+ _subtree_lesser = new DecisionTree();
+ _subtree_lesser->read(is);
+ _subtree_greater = new DecisionTree();
+ _subtree_greater->read(is);
+ }
+}
+
+void DecisionTree::write(ostream *os) {
+
+ write_var(os, &_feature_index);
+ write_var(os, &_threshold);
+ write_var(os, &_weight);
+
+ int split;
+ if(_subtree_lesser && _subtree_greater) {
+ split = 1;
+ write_var(os, &split);
+ _subtree_lesser->write(os);
+ _subtree_greater->write(os);
+ } else {
+ split = 0;
+ write_var(os, &split);
+ }
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef DECISION_TREE_H
+#define DECISION_TREE_H
+
+#include "misc.h"
+#include "classifier.h"
+#include "sample_set.h"
+#include "loss_machine.h"
+
+class DecisionTree : public Classifier {
+
+ int _feature_index;
+ scalar_t _threshold;
+ scalar_t _weight;
+
+ DecisionTree *_subtree_lesser, *_subtree_greater;
+
+ static const int min_nb_samples_for_split = 5;
+
+ void pick_best_split(SampleSet *sample_set,
+ scalar_t *loss_derivatives);
+
+ void train(LossMachine *loss_machine,
+ SampleSet *sample_set,
+ scalar_t *current_responses,
+ scalar_t *loss_derivatives,
+ int depth);
+
+public:
+
+ DecisionTree();
+ ~DecisionTree();
+
+ int nb_leaves();
+ int depth();
+
+ scalar_t response(SampleSet *sample_set, int n_sample);
+
+ void train(LossMachine *loss_machine,
+ SampleSet *sample_set,
+ scalar_t *current_responses);
+
+ void tag_used_features(bool *used);
+ void re_index_features(int *new_indexes);
+
+ void read(istream *is);
+ void write(ostream *os);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "tools.h"
+#include "detector.h"
+#include "global.h"
+#include "classifier_reader.h"
+#include "pose_cell_hierarchy_reader.h"
+
+Detector::Detector() {
+ _hierarchy = 0;
+ _nb_levels = 0;
+ _nb_classifiers_per_level = 0;
+ _thresholds = 0;
+ _nb_classifiers = 0;
+ _classifiers = 0;
+ _pi_feature_families = 0;
+}
+
+
+Detector::~Detector() {
+ if(_hierarchy) {
+ delete[] _thresholds;
+ for(int q = 0; q < _nb_classifiers; q++) {
+ delete _classifiers[q];
+ delete _pi_feature_families[q];
+ }
+ delete[] _classifiers;
+ delete[] _pi_feature_families;
+ delete _hierarchy;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+// Training
+
+void Detector::train_classifier(int level,
+ LossMachine *loss_machine,
+ ParsingPool *parsing_pool,
+ PiFeatureFamily *pi_feature_family,
+ Classifier *classifier) {
+
+ // Randomize the pi-feature family
+
+ PiFeatureFamily full_pi_feature_family;
+
+ full_pi_feature_family.resize(global.nb_features_for_boosting_optimization);
+ full_pi_feature_family.randomize(level);
+
+ int nb_positives = parsing_pool->nb_positive_cells();
+
+ int nb_negatives_to_sample =
+ parsing_pool->nb_positive_cells() * global.nb_negative_samples_per_positive;
+
+ SampleSet *sample_set = new SampleSet(full_pi_feature_family.nb_features(),
+ nb_positives + nb_negatives_to_sample);
+
+ scalar_t *responses = new scalar_t[nb_positives + nb_negatives_to_sample];
+
+ (*global.log_stream) << "Collecting the sampled training set." << endl;
+
+ parsing_pool->weighted_sampling(loss_machine,
+ &full_pi_feature_family,
+ sample_set,
+ responses);
+
+ (*global.log_stream) << "Training the classifier." << endl;
+
+ (*global.log_stream) << "Initial train_loss "
+ << loss_machine->loss(sample_set, responses)
+ << endl;
+
+ classifier->train(loss_machine, sample_set, responses);
+ classifier->extract_pi_feature_family(&full_pi_feature_family, pi_feature_family);
+
+ delete[] responses;
+ delete sample_set;
+}
+
+void Detector::train(LabelledImagePool *train_pool,
+ LabelledImagePool *validation_pool,
+ LabelledImagePool *hierarchy_pool) {
+
+ if(_hierarchy) {
+ cerr << "Can not re-train a Detector" << endl;
+ exit(1);
+ }
+
+ _hierarchy = new PoseCellHierarchy(hierarchy_pool);
+
+ int nb_violations;
+
+ nb_violations = _hierarchy->nb_incompatible_poses(train_pool);
+
+ if(nb_violations > 0) {
+ cout << "The hierarchy is incompatible with the training set ("
+ << nb_violations
+ << " violations)." << endl;
+ exit(1);
+ }
+
+ nb_violations = _hierarchy->nb_incompatible_poses(validation_pool);
+
+ if(nb_violations > 0) {
+ cout << "The hierarchy is incompatible with the validation set ("
+ << nb_violations << " violations)."
+ << endl;
+ exit(1);
+ }
+
+ _nb_levels = _hierarchy->nb_levels();
+ _nb_classifiers_per_level = global.nb_classifiers_per_level;
+ _nb_classifiers = _nb_levels * _nb_classifiers_per_level;
+ _thresholds = new scalar_t[_nb_classifiers];
+ _classifiers = new Classifier *[_nb_classifiers];
+ _pi_feature_families = new PiFeatureFamily *[_nb_classifiers];
+
+ for(int q = 0; q < _nb_classifiers; q++) {
+ _classifiers[q] = new BoostedClassifier(global.nb_weak_learners_per_classifier);
+ _pi_feature_families[q] = new PiFeatureFamily();
+ }
+
+ ParsingPool *train_parsing, *validation_parsing;
+
+ train_parsing = new ParsingPool(train_pool,
+ _hierarchy,
+ global.proportion_negative_cells_for_training);
+
+ if(global.write_validation_rocs) {
+ validation_parsing = new ParsingPool(validation_pool,
+ _hierarchy,
+ global.proportion_negative_cells_for_training);
+ } else {
+ validation_parsing = 0;
+ }
+
+ LossMachine *loss_machine = new LossMachine(global.loss_type);
+
+ cout << "Building a detector." << endl;
+
+ global.bar.init(&cout, _nb_classifiers);
+
+ for(int l = 0; l < _nb_levels; l++) {
+
+ if(l > 0) {
+ train_parsing->down_one_level(loss_machine, _hierarchy, l);
+ if(validation_parsing) {
+ validation_parsing->down_one_level(loss_machine, _hierarchy, l);
+ }
+ }
+
+ for(int c = 0; c < _nb_classifiers_per_level; c++) {
+ int q = l * _nb_classifiers_per_level + c;
+
+ (*global.log_stream) << "Building classifier " << q << " (level " << l << ")" << endl;
+
+ // Train the classifier
+
+ train_classifier(l,
+ loss_machine,
+ train_parsing,
+ _pi_feature_families[q], _classifiers[q]);
+
+ // Update the cell responses on the training set
+
+ (*global.log_stream) << "Updating training cell responses." << endl;
+
+ train_parsing->update_cell_responses(_pi_feature_families[q],
+ _classifiers[q]);
+
+ // Save the ROC curves on the training set
+
+ char buffer[buffer_size];
+
+ sprintf(buffer, "%s/train_%05d.roc",
+ global.result_path,
+ (q + 1) * global.nb_weak_learners_per_classifier);
+ ofstream out(buffer);
+ train_parsing->write_roc(&out);
+
+ if(validation_parsing) {
+
+ // Update the cell responses on the validation set
+
+ (*global.log_stream) << "Updating validation cell responses." << endl;
+
+ validation_parsing->update_cell_responses(_pi_feature_families[q],
+ _classifiers[q]);
+
+ // Save the ROC curves on the validation set
+
+ sprintf(buffer, "%s/validation_%05d.roc",
+ global.result_path,
+ (q + 1) * global.nb_weak_learners_per_classifier);
+ ofstream out(buffer);
+ validation_parsing->write_roc(&out);
+ }
+
+ _thresholds[q] = 0.0;
+
+ global.bar.refresh(&cout, q);
+ }
+ }
+
+ global.bar.finish(&cout);
+
+ delete loss_machine;
+ delete train_parsing;
+ delete validation_parsing;
+}
+
+void Detector::compute_thresholds(LabelledImagePool *validation_pool, scalar_t wanted_tp) {
+ LabelledImage *image;
+ int nb_targets_total = 0;
+
+ for(int i = 0; i < validation_pool->nb_images(); i++) {
+ image = validation_pool->grab_image(i);
+ nb_targets_total += image->nb_targets();
+ validation_pool->release_image(i);
+ }
+
+ scalar_t *responses = new scalar_t[_nb_classifiers * nb_targets_total];
+
+ int tt = 0;
+
+ for(int i = 0; i < validation_pool->nb_images(); i++) {
+ image = validation_pool->grab_image(i);
+ image->compute_rich_structure();
+
+ PoseCell current_cell;
+
+ for(int t = 0; t < image->nb_targets(); t++) {
+
+ scalar_t response = 0;
+
+ for(int l = 0; l < _nb_levels; l++) {
+
+ // We get the next-level cell for that target
+
+ PoseCellSet cell_set;
+
+ cell_set.erase_content();
+ if(l == 0) {
+ _hierarchy->add_root_cells(image, &cell_set);
+ } else {
+ _hierarchy->add_subcells(l, ¤t_cell, &cell_set);
+ }
+
+ int nb_compliant = 0;
+
+ for(int c = 0; c < cell_set.nb_cells(); c++) {
+ if(cell_set.get_cell(c)->contains(image->get_target_pose(t))) {
+ current_cell = *(cell_set.get_cell(c));
+ nb_compliant++;
+ }
+ }
+
+ if(nb_compliant != 1) {
+ cerr << "INCONSISTENCY (" << nb_compliant << " should be one)" << endl;
+ abort();
+ }
+
+ for(int c = 0; c < _nb_classifiers_per_level; c++) {
+ int q = l * _nb_classifiers_per_level + c;
+ SampleSet *sample_set = new SampleSet(_pi_feature_families[q]->nb_features(), 1);
+ sample_set->set_sample(0, _pi_feature_families[q], image, ¤t_cell, 0);
+ response +=_classifiers[q]->response(sample_set, 0);
+ delete sample_set;
+ responses[tt + nb_targets_total * q] = response;
+ }
+
+ }
+
+ tt++;
+ }
+
+ validation_pool->release_image(i);
+ }
+
+ ASSERT(tt == nb_targets_total);
+
+ // Here we have in responses[] all the target responses after every
+ // classifier
+
+ int *still_detected = new int[nb_targets_total];
+ int *indexes = new int[nb_targets_total];
+ int *sorted_indexes = new int[nb_targets_total];
+
+ for(int t = 0; t < nb_targets_total; t++) {
+ still_detected[t] = 1;
+ indexes[t] = t;
+ }
+
+ int current_nb_fn = 0;
+
+ for(int q = 0; q < _nb_classifiers; q++) {
+
+ scalar_t wanted_tp_at_this_classifier
+ = exp(log(wanted_tp) * scalar_t(q + 1) / scalar_t(_nb_classifiers));
+
+ int wanted_nb_fn_at_this_classifier
+ = int(nb_targets_total * (1 - wanted_tp_at_this_classifier));
+
+ (*global.log_stream) << "q = " << q
+ << " wanted_tp_at_this_classifier = " << wanted_tp_at_this_classifier
+ << " wanted_nb_fn_at_this_classifier = " << wanted_nb_fn_at_this_classifier
+ << endl;
+
+ indexed_fusion_sort(nb_targets_total, indexes, sorted_indexes,
+ responses + q * nb_targets_total);
+
+ for(int t = 0; (current_nb_fn < wanted_nb_fn_at_this_classifier) && (t < nb_targets_total - 1); t++) {
+ int u = sorted_indexes[t];
+ int v = sorted_indexes[t+1];
+ _thresholds[q] = responses[v + nb_targets_total * q];
+ if(still_detected[u]) {
+ still_detected[u] = 0;
+ current_nb_fn++;
+ }
+ }
+ }
+
+ delete[] still_detected;
+ delete[] indexes;
+ delete[] sorted_indexes;
+
+ { ////////////////////////////////////////////////////////////////////
+ // Sanity check
+
+ int nb_positives = 0;
+
+ for(int t = 0; t < nb_targets_total; t++) {
+ int positive = 1;
+ for(int q = 0; q < _nb_classifiers; q++) {
+ if(responses[t + nb_targets_total * q] < _thresholds[q]) positive = 0;
+ }
+ if(positive) nb_positives++;
+ }
+
+ scalar_t actual_tp = scalar_t(nb_positives) / scalar_t(nb_targets_total);
+
+ (*global.log_stream) << "Overall detection rate " << nb_positives << "/" << nb_targets_total
+ << " "
+ << "actual_tp = " << actual_tp
+ << " "
+ << "wanted_tp = " << wanted_tp
+ << endl;
+
+ if(actual_tp < wanted_tp) {
+ cerr << "INCONSISTENCY" << endl;
+ abort();
+ }
+ } ////////////////////////////////////////////////////////////////////
+
+ delete[] responses;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Parsing
+
+void Detector::parse_rec(RichImage *image, int level,
+ PoseCell *cell, scalar_t current_response,
+ PoseCellScoredSet *result) {
+
+ if(level == _nb_levels) {
+ result->add_cell_with_score(cell, current_response);
+ return;
+ }
+
+ PoseCellSet cell_set;
+ cell_set.erase_content();
+
+ if(level == 0) {
+ _hierarchy->add_root_cells(image, &cell_set);
+ } else {
+ _hierarchy->add_subcells(level, cell, &cell_set);
+ }
+
+ scalar_t *responses = new scalar_t[cell_set.nb_cells()];
+ int *keep = new int[cell_set.nb_cells()];
+
+ for(int c = 0; c < cell_set.nb_cells(); c++) {
+ responses[c] = current_response;
+ keep[c] = 1;
+ }
+
+ for(int a = 0; a < _nb_classifiers_per_level; a++) {
+ int q = level * _nb_classifiers_per_level + a;
+ SampleSet *samples = new SampleSet(_pi_feature_families[q]->nb_features(), 1);
+ for(int c = 0; c < cell_set.nb_cells(); c++) {
+ if(keep[c]) {
+ samples->set_sample(0, _pi_feature_families[q], image, cell_set.get_cell(c), 0);
+ responses[c] += _classifiers[q]->response(samples, 0);
+ keep[c] = responses[c] >= _thresholds[q];
+ }
+ }
+ delete samples;
+ }
+
+ for(int c = 0; c < cell_set.nb_cells(); c++) {
+ if(keep[c]) {
+ parse_rec(image, level + 1, cell_set.get_cell(c), responses[c], result);
+ }
+ }
+
+ delete[] keep;
+ delete[] responses;
+}
+
+void Detector::parse(RichImage *image, PoseCellScoredSet *result_cell_set) {
+ result_cell_set->erase_content();
+ parse_rec(image, 0, 0, 0, result_cell_set);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Storage
+
+void Detector::read(istream *is) {
+ if(_hierarchy) {
+ cerr << "Can not read over an existing Detector" << endl;
+ exit(1);
+ }
+
+ read_var(is, &_nb_levels);
+ read_var(is, &_nb_classifiers_per_level);
+
+ _nb_classifiers = _nb_levels * _nb_classifiers_per_level;
+
+ _classifiers = new Classifier *[_nb_classifiers];
+ _pi_feature_families = new PiFeatureFamily *[_nb_classifiers];
+ _thresholds = new scalar_t[_nb_classifiers];
+
+ for(int q = 0; q < _nb_classifiers; q++) {
+ cout << "Read classifier " << q << endl;
+ _pi_feature_families[q] = new PiFeatureFamily();
+ _pi_feature_families[q]->read(is);
+ _classifiers[q] = read_classifier(is);
+ read_var(is, &_thresholds[q]);
+ }
+
+ _hierarchy = read_hierarchy(is);
+
+ (*global.log_stream) << "Read Detector" << endl
+ << " _nb_levels " << _nb_levels << endl
+ << " _nb_classifiers_per_level " << _nb_classifiers_per_level << endl;
+}
+
+void Detector::write(ostream *os) {
+ write_var(os, &_nb_levels);
+ write_var(os, &_nb_classifiers_per_level);
+
+ for(int q = 0; q < _nb_classifiers; q++) {
+ _pi_feature_families[q]->write(os);
+ _classifiers[q]->write(os);
+ write_var(os, &_thresholds[q]);
+ }
+
+ _hierarchy->write(os);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+
+ This is the main class, a folded hierarchy of classifiers.
+
+*/
+
+#ifndef DETECTOR_H
+#define DETECTOR_H
+
+#include "storable.h"
+#include "boosted_classifier.h"
+#include "labelled_image_pool.h"
+#include "parsing_pool.h"
+#include "pose_cell_scored_set.h"
+
+class Detector : public Storable {
+public:
+ PoseCellHierarchy *_hierarchy;
+ int _nb_levels, _nb_classifiers_per_level, _nb_classifiers;
+ scalar_t *_thresholds;
+ Classifier **_classifiers;
+ PiFeatureFamily **_pi_feature_families;
+
+ void free();
+
+ virtual void train_classifier(int level,
+ LossMachine *loss_machine,
+ ParsingPool *parsing,
+ PiFeatureFamily *pi_feature_family,
+ Classifier *classifier);
+
+ virtual void parse_rec(RichImage *image, int level,
+ PoseCell *cell, scalar_t response,
+ PoseCellScoredSet *result);
+
+public:
+
+ Detector();
+ virtual ~Detector();
+
+ inline int nb_levels() { return _nb_levels; }
+
+ // The validation set is only used to save ROCs curves during
+ // training for monitoring the learning
+
+ virtual void train(LabelledImagePool *train_pool,
+ LabelledImagePool *validation_pool,
+ LabelledImagePool *hierarchy_pool);
+
+ virtual void compute_thresholds(LabelledImagePool *validation_pool, scalar_t wanted_tp);
+
+ virtual void parse(RichImage *image, PoseCellScoredSet *result_cell_set);
+
+ virtual void read(istream *is);
+ virtual void write(ostream *os);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "error_rates.h"
+#include "fusion_sort.h"
+#include "pose_cell_hierarchy.h"
+#include "boosted_classifier.h"
+#include "parsing_pool.h"
+#include "chrono.h"
+#include "materials.h"
+
+void compute_errors_on_one_image(int level,
+ LabelledImage *image,
+ PoseCellSet *cell_set,
+ int *nb_fns, int *nb_fas) {
+
+ int hit[image->nb_targets()];
+
+ for(int t = 0; t < image->nb_targets(); t++) {
+ hit[t] = 0;
+ }
+
+ Pose pose;
+
+ for(int c = 0; c < cell_set->nb_cells(); c++) {
+ cell_set->get_cell(c)->get_centroid(&pose);
+
+ int false_positive = 1;
+
+ for(int t = 0; t < image->nb_targets(); t++) {
+ if(pose.hit(level, image->get_target_pose(t))) {
+ hit[t] = 1;
+ false_positive = 0;
+ }
+ }
+
+ if(false_positive) (*nb_fas)++;
+ }
+
+ for(int t = 0; t < image->nb_targets(); t++) {
+ if(!hit[t]) (*nb_fns)++;
+ }
+}
+
+void print_decimated_error_rate(int level, LabelledImagePool *pool, Detector *detector) {
+ LabelledImage *image;
+ PoseCellScoredSet result_cell_set;
+
+ int nb_fns = 0, nb_fas = 0, nb_targets = 0;
+ long int total_surface = 0;
+
+ cout << "Testing the detector." << endl;
+
+ global.bar.init(&cout, pool->nb_images());
+ for(int i = 0; i < pool->nb_images(); i++) {
+ image = pool->grab_image(i);
+ total_surface += image->width() * image->height();
+ image->compute_rich_structure();
+
+ detector->parse(image, &result_cell_set);
+ result_cell_set.decimate_collide(level);
+ result_cell_set.decimate_hit(level);
+
+ compute_errors_on_one_image(level, image, &result_cell_set, &nb_fns, &nb_fas);
+
+ if(global.write_parse_images) {
+ char buffer[buffer_size];
+ sprintf(buffer, "%s/parse-%04d.png", global.result_path, i);
+ cout << "LEVEL = " << level << endl;
+ write_image_with_detections(buffer,
+ image,
+ &result_cell_set, level);
+ }
+
+ nb_targets += image->nb_targets();
+ pool->release_image(i);
+ global.bar.refresh(&cout, i);
+ }
+ global.bar.finish(&cout);
+
+ scalar_t fn_rate = scalar_t(nb_fns)/scalar_t(nb_targets);
+ scalar_t nb_fas_per_vga = (scalar_t(nb_fas) / scalar_t(total_surface)) * scalar_t(640 * 480);
+
+ (*global.log_stream)
+ << "INFO DECIMATED_NB_FALSE_NEGATIVES " << nb_fns << endl
+ << "INFO DECIMATED_NB_TARGETS " << nb_targets << endl
+ << "INFO DECIMATED_FALSE_NEGATIVE_RATE " << fn_rate << endl
+ << "INFO DECIMATED_NB_FALSE_POSITIVES " << nb_fas << endl
+ << "INFO DECIMATED_NB_FALSE_POSITIVES_PER_VGA " << nb_fas_per_vga << endl
+ << "INFO NB_SCENES " << pool->nb_images() << endl
+ << "INFO TOTAL_SURFACE " << total_surface << endl;
+ ;
+}
+
+void parse_scene(Detector *detector, const char *image_name) {
+ RGBImage tmp;
+ tmp.read_jpg(image_name);
+ RichImage image(tmp.width(), tmp.height());
+
+ for(int y = 0; y < tmp.height(); y++) {
+ for(int x = 0; x < tmp.width(); x++) {
+ image.set_value(x, y, int(scalar_t(tmp.pixel(x, y, 0)) * 0.2989 +
+ scalar_t(tmp.pixel(x, y, 1)) * 0.5870 +
+ scalar_t(tmp.pixel(x, y, 2)) * 0.1140));
+ }
+ }
+
+ image.compute_rich_structure();
+
+ PoseCellScoredSet cell_set;
+ detector->parse(&image, &cell_set);
+ cell_set.decimate_hit(detector->nb_levels() - 1);
+
+ cout << "RESULT " << image_name << endl;
+ for(int c = 0; c < cell_set.nb_cells(); c++) {
+ cout << "ALARM " << c << endl;
+ Pose alarm;
+ cell_set.get_cell(c)->get_centroid(&alarm);
+ alarm.print(&cout);
+ }
+ cout << "END_RESULT" << endl;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef ERROR_RATES_H
+#define ERROR_RATES_H
+
+#include "misc.h"
+#include "labelled_image_pool.h"
+#include "detector.h"
+
+void print_decimated_error_rate(int level, LabelledImagePool *pool, Detector *detector);
+
+void parse_scene(Detector *detector, const char *image_name);
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include <iostream>
+#include <fstream>
+#include <cmath>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+using namespace std;
+
+#include "misc.h"
+#include "param_parser.h"
+#include "global.h"
+#include "labelled_image_pool_file.h"
+#include "labelled_image_pool_subset.h"
+#include "tools.h"
+#include "detector.h"
+#include "pose_cell_hierarchy.h"
+#include "error_rates.h"
+#include "materials.h"
+
+//////////////////////////////////////////////////////////////////////
+
+void check(bool condition, const char *message) {
+ if(!condition) {
+ cerr << message << endl;
+ exit(1);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+int main(int argc, char **argv) {
+ char *new_argv[argc];
+ int new_argc = 0;
+
+#ifdef DEBUG
+ cout << endl;
+ cout << "**********************************************************************" << endl;
+ cout << "** COMPILED IN DEBUG MODE **" << endl;
+ cout << "**********************************************************************" << endl;
+ cout << endl;
+#endif
+
+ cout << "-- ARGUMENTS ---------------------------------------------------------" << endl;
+ for(int i = 0; i < argc; i++)
+ cout << (i > 0 ? " " : "") << argv[i] << (i < argc - 1 ? " \\" : "")
+ << endl;
+
+ {
+ ParamParser parser;
+ global.init_parser(&parser);
+ parser.parse_options(argc, argv, false, &new_argc, new_argv);
+ global.read_parser(&parser);
+ (*global.log_stream)
+ << "-- PARAMETERS --------------------------------------------------------"
+ << endl;
+ parser.print_all(global.log_stream);
+ }
+
+ nice(global.niceness);
+
+ (*global.log_stream) << "INFO RANDOM_SEED " << global.random_seed << endl;
+ srand48(global.random_seed);
+
+ LabelledImagePool *main_pool = 0;
+ LabelledImagePool *train_pool = 0, *validation_pool = 0, *hierarchy_pool = 0;
+ LabelledImagePool *test_pool = 0;
+ Detector *detector = 0;
+
+ {
+ char buffer[buffer_size];
+ gethostname(buffer, buffer_size);
+ (*global.log_stream) << "INFO HOSTNAME " << buffer << endl;
+ }
+
+ for(int c = 1; c < new_argc; c++) {
+
+ if(strcmp(new_argv[c], "open-pool") == 0) {
+ cout
+ << "-- OPENING POOL ------------------------------------------------------"
+ << endl;
+
+ check(!main_pool, "Pool already opened.");
+ check(global.pool_name[0], "No pool file.");
+
+ main_pool = new LabelledImagePoolFile(global.pool_name);
+
+ bool for_test[main_pool->nb_images()];
+ bool for_train[main_pool->nb_images()];
+ bool for_validation[main_pool->nb_images()];
+ bool for_hierarchy[main_pool->nb_images()];
+
+ for(int n = 0; n < main_pool->nb_images(); n++) {
+ for_test[n] = false;
+ for_train[n] = false;
+ for_validation[n] = false;
+ scalar_t r = drand48();
+ if(r < global.proportion_for_train)
+ for_train[n] = true;
+ else if(r < global.proportion_for_train + global.proportion_for_validation)
+ for_validation[n] = true;
+ else if(global.proportion_for_test < 0 ||
+ r < global.proportion_for_train +
+ global.proportion_for_validation +
+ global.proportion_for_test)
+ for_test[n] = true;
+ for_hierarchy[n] = for_train[n] || for_validation[n];
+ }
+
+ train_pool = new LabelledImagePoolSubset(main_pool, for_train);
+ validation_pool = new LabelledImagePoolSubset(main_pool, for_validation);
+ hierarchy_pool = new LabelledImagePoolSubset(main_pool, for_hierarchy);
+
+ if(global.test_pool_name[0]) {
+ test_pool = new LabelledImagePoolFile(global.test_pool_name);
+ } else {
+ test_pool = new LabelledImagePoolSubset(main_pool, for_test);
+ }
+
+ cout << "Using "
+ << train_pool->nb_images() << " images for train, "
+ << validation_pool->nb_images() << " images for validation, "
+ << hierarchy_pool->nb_images() << " images for the hierarchy and "
+ << test_pool->nb_images() << " images for test."
+ << endl;
+
+ }
+
+ else if(strcmp(new_argv[c], "write-target-poses") == 0) {
+ check(main_pool, "No pool available.");
+ LabelledImage *image;
+ for(int p = 0; p < main_pool->nb_images(); p++) {
+ image = main_pool->grab_image(p);
+ for(int t = 0; t < image->nb_targets(); t++) {
+ cout << "IMAGE " << p << " TARGET " << t << endl;
+ image->get_target_pose(t)->print(&cout);
+ }
+ main_pool->release_image(p);
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(new_argv[c], "train-detector") == 0) {
+ cout << "-- TRAIN DETECTOR ----------------------------------------------------" << endl;
+ check(train_pool, "No train pool available.");
+ check(validation_pool, "No validation pool available.");
+ check(hierarchy_pool, "No hierarchy pool available.");
+ check(!detector, "Existing detector, can not train another one.");
+ detector = new Detector();
+ detector->train(train_pool, validation_pool, hierarchy_pool);
+ }
+
+ else if(strcmp(new_argv[c], "compute-thresholds") == 0) {
+ cout << "-- COMPUTE THRESHOLDS ------------------------------------------------" << endl;
+ check(validation_pool, "No validation pool available.");
+ check(detector, "No detector.");
+ detector->compute_thresholds(validation_pool, global.wanted_true_positive_rate);
+ }
+
+ else if(strcmp(new_argv[c], "check-hierarchy") == 0) {
+ cout << "-- CHECK HIERARCHY ---------------------------------------------------" << endl;
+ PoseCellHierarchy *h = new PoseCellHierarchy(hierarchy_pool);
+ cout << "Train incompatible poses " << h->nb_incompatible_poses(train_pool) << endl;
+ cout << "Validation incompatible poses " << h->nb_incompatible_poses(validation_pool) << endl;
+ delete h;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(new_argv[c], "validate-detector") == 0) {
+ cout << "-- VALIDATE DETECTOR -------------------------------------------------" << endl;
+
+ check(validation_pool, "No validation pool available.");
+ check(detector, "No detector.");
+
+ print_decimated_error_rate(global.nb_levels - 1, validation_pool, detector);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(new_argv[c], "test-detector") == 0) {
+ cout << "-- TEST DETECTOR -----------------------------------------------------" << endl;
+
+ check(test_pool, "No test pool available.");
+ check(detector, "No detector.");
+
+ if(test_pool->nb_images() > 0) {
+ print_decimated_error_rate(global.nb_levels - 1, test_pool, detector);
+ } else {
+ cout << "No test image." << endl;
+ }
+ }
+
+ else if(strcmp(new_argv[c], "parse-images") == 0) {
+ cout << "-- PARSING IMAGES -----------------------------------------------------" << endl;
+ check(detector, "No detector.");
+ while(!cin.eof()) {
+ char image_name[buffer_size];
+ cin.getline(image_name, buffer_size);
+ if(strlen(image_name) > 0) {
+ parse_scene(detector, image_name);
+ }
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(new_argv[c], "sequence-test-detector") == 0) {
+ cout << "-- SEQUENCE TEST DETECTOR --------------------------------------------" << endl;
+
+ check(test_pool, "No test pool available.");
+ check(detector, "No detector.");
+
+ if(test_pool->nb_images() > 0) {
+
+ for(int n = 0; n < global.nb_wanted_true_positive_rates; n++) {
+ scalar_t r = global.wanted_true_positive_rate *
+ scalar_t(n + 1) / scalar_t(global.nb_wanted_true_positive_rates);
+ cout << "Testint at tp " << r
+ << " (" << n + 1 << "/" << global.nb_wanted_true_positive_rates << ")"
+ << endl;
+ (*global.log_stream) << "INFO THRESHOLD_FOR_TP " << r << endl;
+ detector->compute_thresholds(validation_pool, r);
+ print_decimated_error_rate(global.nb_levels - 1, test_pool, detector);
+ }
+ } else {
+ cout << "No test image." << endl;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(new_argv[c], "write-detector") == 0) {
+ cout << "-- WRITE DETECTOR ----------------------------------------------------" << endl;
+ ofstream out(global.detector_name);
+ if(out.fail()) {
+ cerr << "Can not write to " << global.detector_name << endl;
+ exit(1);
+ }
+ check(detector, "No detector available.");
+ detector->write(&out);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(new_argv[c], "read-detector") == 0) {
+ cout << "-- READ DETECTOR -----------------------------------------------------" << endl;
+
+ check(!detector, "Existing detector, can not load another one.");
+
+ ifstream in(global.detector_name);
+ if(in.fail()) {
+ cerr << "Can not read from " << global.detector_name << endl;
+ exit(1);
+ }
+
+ detector = new Detector();
+ detector->read(&in);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(new_argv[c], "write-pool-images") == 0) {
+ cout << "-- WRITING POOL IMAGES -----------------------------------------------" << endl;
+ check(global.nb_images > 0, "You must set nb_images to a positive value.");
+ check(train_pool, "No train pool available.");
+ write_pool_images_with_poses_and_referentials(train_pool, detector);
+ }
+
+ else if(strcmp(new_argv[c], "produce-materials") == 0) {
+ cout << "-- PRODUCING MATERIALS -----------------------------------------------" << endl;
+
+ check(hierarchy_pool, "No hierarchy pool available.");
+ check(test_pool, "No test pool available.");
+
+ PoseCellHierarchy *hierarchy;
+
+ cout << "Creating hierarchy" << endl;
+
+ hierarchy = new PoseCellHierarchy(hierarchy_pool);
+
+ LabelledImage *image;
+ for(int p = 0; p < test_pool->nb_images(); p++) {
+ image = test_pool->grab_image(p);
+ if(image->width() == 640 && image->height() == 480) {
+ PoseCellSet pcs;
+ hierarchy->add_root_cells(image, &pcs);
+ cout << "WE HAVE " << pcs.nb_cells() << " CELLS" << endl;
+ exit(0);
+ test_pool->release_image(p);
+ }
+ }
+
+ delete hierarchy;
+
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else {
+ cerr << "Unknown action " << new_argv[c] << endl;
+ exit(1);
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ }
+
+ delete detector;
+
+ delete train_pool;
+ delete validation_pool;
+ delete hierarchy_pool;
+ delete test_pool;
+
+ delete main_pool;
+
+ cout << "-- FINISHED ----------------------------------------------------------" << endl;
+
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "fusion_sort.h"
+#include <string.h>
+
+inline void indexed_fusion(int na, int *ia, int nb, int *ib, int *ic, scalar_t *values) {
+ int *ma = ia + na, *mb = ib + nb;
+ while(ia < ma && ib < mb)
+ if(values[*ia] <= values[*ib]) *(ic++) = *(ia++);
+ else *(ic++) = *(ib++);
+ while(ia < ma) *(ic++) = *(ia++);
+ while(ib < mb) *(ic++) = *(ib++);
+}
+
+void indexed_fusion_sort(int n, int *from, int *result, scalar_t *values) {
+ ASSERT(n > 0);
+ if(n == 1) result[0] = from[0];
+ else {
+ int k = n/2;
+ indexed_fusion_sort(k, from, result, values);
+ indexed_fusion_sort(n - k, from + k, result + k, values);
+ memcpy((void *) from, (void *) result, n * sizeof(int));
+ indexed_fusion(k, from, n - k, from + k, result, values);
+ }
+}
+
+// Sorting in decreasing order
+
+inline void indexed_fusion_dec(int na, int *ia,
+ int nb, int *ib,
+ int *ic, scalar_t *values) {
+ int *ma = ia + na, *mb = ib + nb;
+ while(ia < ma && ib < mb)
+ if(values[*ia] > values[*ib]) *(ic++) = *(ia++);
+ else *(ic++) = *(ib++);
+ while(ia < ma) *(ic++) = *(ia++);
+ while(ib < mb) *(ic++) = *(ib++);
+}
+
+void indexed_fusion_dec_sort(int n, int *from, int *result, scalar_t *values) {
+ ASSERT(n > 0);
+ if(n == 1) result[0] = from[0];
+ else {
+ int k = n/2;
+ indexed_fusion_dec_sort(k, from, result, values);
+ indexed_fusion_dec_sort(n - k, from + k, result + k, values);
+ memcpy((void *) from, (void *) result, n * sizeof(int));
+ indexed_fusion_dec(k, from, n - k, from + k, result, values);
+ }
+}
+
+void fusion_two_cells(int n1, scalar_t *cell1, int n2, scalar_t *cell2, scalar_t *result) {
+ scalar_t *max1 = cell1 + n1, *max2 = cell2 + n2;
+ while(cell1 < max1 && cell2 < max2) {
+ if(*(cell1) <= *(cell2)) *(result++) = *(cell1++);
+ else *(result++) = *(cell2++);
+ }
+ while(cell1 < max1) *(result++) = *(cell1++);
+ while(cell2 < max2) *(result++) = *(cell2++);
+}
+
+void fusion_sort(int n, scalar_t *from, scalar_t *result) {
+ if(n > 1) {
+ fusion_sort(n/2, from, result);
+ fusion_sort(n - n/2, from + n/2, result + n/2);
+ memcpy(from, result, sizeof(scalar_t) * n);
+ fusion_two_cells(n/2, from, n - n/2, from + n/2, result);
+ } else result[0] = from[0];
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef FUSION_SORT_H
+#define FUSION_SORT_H
+
+#include "misc.h"
+
+void indexed_fusion_sort(int n, int *from, int *result, scalar_t *values);
+void indexed_fusion_dec_sort(int n, int *from, int *result, scalar_t *values);
+void fusion_sort(int n, scalar_t *from, scalar_t *result);
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "gaussian.h"
+
+Gaussian::Gaussian() {
+ _nb_samples = 0;
+ _sum = 0.0;
+ _sum_sq = 0.0;
+}
+
+void Gaussian::add_sample(scalar_t x) {
+ _nb_samples++;
+ _sum += x;
+ _sum_sq += x * x;
+}
+
+scalar_t Gaussian::expectation() {
+ return _sum / scalar_t(_nb_samples);
+}
+
+scalar_t Gaussian::variance() {
+ scalar_t e = _sum / scalar_t(_nb_samples);
+ return (_sum_sq - _sum * e) / scalar_t(_nb_samples - 1);
+}
+
+scalar_t Gaussian::standard_deviation() {
+ return sqrt(variance());
+}
+
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef GAUSSIAN_H
+#define GAUSSIAN_H
+
+#include "misc.h"
+
+class Gaussian {
+ int _nb_samples;
+ scalar_t _sum, _sum_sq;
+public:
+ Gaussian();
+ void add_sample(scalar_t x);
+ scalar_t expectation();
+ scalar_t variance();
+ scalar_t standard_deviation();
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include <string.h>
+
+#include "global.h"
+
+Global global;
+
+Global::Global() {
+ log_stream = 0;
+}
+
+Global::~Global() {
+ delete log_stream;
+}
+
+void Global::init_parser(ParamParser *parser) {
+ // The nice level of the process
+ parser->add_association("niceness", "5", false);
+
+ // Seed to initialize the random generator
+ parser->add_association("random-seed", "0", false);
+
+ // Should the pictures be b&w
+ parser->add_association("pictures-for-article", "no", false);
+
+ // The name of the image pool to use
+ parser->add_association("pool-name", "", false);
+ // The name of the test image pool to use
+ parser->add_association("test-pool-name", "", false);
+ // From where to load or where to save the detector
+ parser->add_association("detector-name", "default.det", false);
+ // Where to put the generated files
+ parser->add_association("result-path", "/tmp/", false);
+
+ // What kind of loss for the boosting
+ parser->add_association("loss-type", "exponential", false);
+
+ // How many images to produce/process
+ parser->add_association("nb-images", "-1", false);
+
+ // What is the maximum tree depth
+ parser->add_association("tree-depth-max", "1", false);
+ // What is the proportion of negative cells we actually use during training
+ parser->add_association("proportion-negative-cells-for-training", "0.025", false);
+ // How many negative samples to sub-sample for boosting every classifier
+ parser->add_association("nb-negative-samples-per-positive", "10", false);
+ // How many features we will look at for boosting optimization
+ parser->add_association("nb-features-for-boosting-optimization", "10000", false);
+ // Do we allow head-belly registration
+ parser->add_association("force-head-belly-independence", "no", false);
+ // How many weak-learners in every classifier
+ parser->add_association("nb-weak-learners-per-classifier", "10", false);
+ // How many classifiers per level
+ parser->add_association("nb-classifiers-per-level", "25", false);
+ // How many levels
+ parser->add_association("nb-levels", "1", false);
+
+ // Proportion of images from the pool to use for training
+ parser->add_association("proportion-for-train", "0.5", false);
+ // Proportion of images from the pool to use for validation
+ parser->add_association("proportion-for-validation", "0.25", false);
+ // Proportion of images from the pool to use for test (negative
+ // means everything else)
+ parser->add_association("proportion-for-test", "0.25", false);
+ // During training, should we write the ROC curve estimated on the
+ // validation set (which cost a bit of computation)
+ parser->add_association("write-validation-rocs", "no", false);
+
+ // Should we write down the PNGs for the results of the parsing
+ parser->add_association("write-parse-images", "no", false);
+
+ // Should we write down the PNGs for the tags
+ parser->add_association("write-tag-images", "no", false);
+
+ // What is the wanted true overall positive rate
+ parser->add_association("wanted-true-positive-rate", "0.5", false);
+ // How many rates to try for the sequence of tests
+ parser->add_association("nb-wanted-true-positive-rates", "10", false);
+
+ // What is the minimum radius of the heads to detect. This is used
+ // as the reference size.
+ parser->add_association("min-head-radius", "25", false);
+ // What is the maximum size of the heads to detect.
+ parser->add_association("max-head-radius", "200", false);
+ // How many translation cell for one scale when generating the "top
+ // level" cells for an image.
+ parser->add_association("root-cell-nb-xy-per-scale", "5", false);
+
+ // What is the minimum size of the windows
+ parser->add_association("pi-feature-window-min-size", "0.1", false);
+
+ // How many scales between two powers of two for the multi-scale
+ // images
+ parser->add_association("nb-scales-per-power-of-two", "5", false);
+
+ // Should we display a progress bar for lengthy operations
+ parser->add_association("progress-bar", "yes", false);
+}
+
+void Global::read_parser(ParamParser *parser) {
+ niceness = parser->get_association_int("niceness");
+ random_seed = parser->get_association_int("random-seed");
+ pictures_for_article = parser->get_association_bool("pictures-for-article");
+
+ strncpy(pool_name, parser->get_association("pool-name"), buffer_size);
+ strncpy(test_pool_name, parser->get_association("test-pool-name"), buffer_size);
+ strncpy(detector_name, parser->get_association("detector-name"), buffer_size);
+ strncpy(result_path, parser->get_association("result-path"), buffer_size);
+
+ char buffer[buffer_size];
+ sprintf(buffer, "%s/log", result_path);
+ log_stream = new ofstream(buffer);
+
+ char *l = parser->get_association("loss-type");
+ if(strcmp(l, "exponential") == 0)
+ loss_type = LOSS_EXPONENTIAL;
+ else if(strcmp(l, "ev-regularized") == 0)
+ loss_type = LOSS_EV_REGULARIZED;
+ else if(strcmp(l, "hinge") == 0)
+ loss_type = LOSS_HINGE;
+ else if(strcmp(l, "logistic") == 0)
+ loss_type = LOSS_LOGISTIC;
+ else {
+ cerr << "Unknown loss type." << endl;
+ exit(1);
+ }
+
+ nb_images = parser->get_association_int("nb-images");
+ tree_depth_max = parser->get_association_int("tree-depth-max");
+ nb_weak_learners_per_classifier = parser->get_association_int("nb-weak-learners-per-classifier");
+ nb_classifiers_per_level = parser->get_association_int("nb-classifiers-per-level");
+ nb_levels = parser->get_association_int("nb-levels");
+ proportion_negative_cells_for_training = parser->get_association_scalar("proportion-negative-cells-for-training");
+ nb_negative_samples_per_positive = parser->get_association_int("nb-negative-samples-per-positive");
+ nb_features_for_boosting_optimization = parser->get_association_int("nb-features-for-boosting-optimization");
+ force_head_belly_independence = parser->get_association_bool("force-head-belly-independence");
+ proportion_for_train = parser->get_association_scalar("proportion-for-train");
+ proportion_for_validation = parser->get_association_scalar("proportion-for-validation");
+ proportion_for_test = parser->get_association_scalar("proportion-for-test");
+ write_validation_rocs = parser->get_association_bool("write-validation-rocs");
+ write_parse_images = parser->get_association_bool("write-parse-images");
+ write_tag_images = parser->get_association_bool("write-tag-images");
+ wanted_true_positive_rate = parser->get_association_scalar("wanted-true-positive-rate");
+ nb_wanted_true_positive_rates = parser->get_association_int("nb-wanted-true-positive-rates");
+
+ min_head_radius = parser->get_association_scalar("min-head-radius");
+ max_head_radius = parser->get_association_scalar("max-head-radius");
+ root_cell_nb_xy_per_scale = parser->get_association_int("root-cell-nb-xy-per-scale");
+
+ pi_feature_window_min_size = parser->get_association_scalar("pi-feature-window-min-size");
+
+ nb_scales_per_power_of_two = parser->get_association_int("nb-scales-per-power-of-two");
+
+ bar.set_visible(parser->get_association_bool("progress-bar"));
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef GLOBAL_H
+#define GLOBAL_H
+
+#include <iostream>
+
+using namespace std;
+
+#include "misc.h"
+#include "param_parser.h"
+#include "progress_bar.h"
+
+enum { LOSS_EXPONENTIAL,
+ LOSS_EV_REGULARIZED,
+ LOSS_HINGE,
+ LOSS_LOGISTIC };
+
+class Global {
+public:
+ int niceness;
+ int random_seed;
+ int pictures_for_article;
+
+ char pool_name[buffer_size];
+ char test_pool_name[buffer_size];
+ char detector_name[buffer_size];
+ char result_path[buffer_size];
+
+ char materials_image_numbers[buffer_size];
+ char materials_pf_numbers[buffer_size];
+
+ int loss_type;
+
+ int nb_images;
+
+ int tree_depth_max;
+
+ int nb_weak_learners_per_classifier;
+ int nb_classifiers_per_level;
+ int nb_levels;
+
+ scalar_t proportion_negative_cells_for_training;
+ int nb_negative_samples_per_positive;
+ int nb_features_for_boosting_optimization;
+ int force_head_belly_independence;
+
+ scalar_t proportion_for_train;
+ scalar_t proportion_for_validation;
+ scalar_t proportion_for_test;
+ bool write_validation_rocs;
+ bool write_parse_images;
+ bool write_tag_images;
+ scalar_t wanted_true_positive_rate;
+ int nb_wanted_true_positive_rates;
+
+ int nb_scales_per_power_of_two;
+ scalar_t min_head_radius;
+ scalar_t max_head_radius;
+ int root_cell_nb_xy_per_scale;
+
+ scalar_t pi_feature_window_min_size;
+
+ ProgressBar bar;
+
+ Global();
+ ~Global();
+
+ void init_parser(ParamParser *parser);
+ void read_parser(ParamParser *parser);
+
+ ostream *log_stream;
+
+ inline int scale_to_discrete_log_scale(scalar_t scale_ratio) {
+ return int(floor(log(scale_ratio) / log(2.0) * nb_scales_per_power_of_two));
+ }
+
+ inline scalar_t discrete_log_scale_to_scale(int discrete_scale) {
+ return exp( - scalar_t(discrete_scale) * log(2.0) / scalar_t(nb_scales_per_power_of_two));
+ }
+};
+
+extern Global global;
+
+#endif
--- /dev/null
+#!/bin/bash
+
+#########################################################################
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the version 3 of the GNU General Public License #
+# as published by the Free Software Foundation. #
+# #
+# This program is distributed in the hope that it will be useful, but #
+# WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
+# General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+# Written and (C) by Francois Fleuret #
+# Contact <francois.fleuret@idiap.ch> for comments & bug reports #
+#########################################################################
+
+echo "Parsing the log files"
+
+for p in hb h+b; do
+ grep ^INFO results/${p}-*/log | grep "FALSE_NEGATIVE_RATE\|PER_VGA" | \
+ sed -e "s/[^0-9A-Z_ .]//g" | \
+ awk '{
+ if($2 == "DECIMATED_FALSE_NEGATIVE_RATE") {
+ printf(1-$3)
+ } else {
+ printf(" "$3"\n")
+ }
+ }' | sort -g > /tmp/${p}
+
+done
+
+if [[ ! -s /tmp/hb ]] || [[ ! -s /tmp/h+b ]]; then
+ echo "Not enough data points." >&2
+ exit 1
+fi
+
+######################################################################
+
+echo "Generating the graph per se"
+
+GRAPH_NAME="/tmp/roc.eps"
+
+gnuplot<<EOF
+ set terminal postscript enhanced eps "Helvetica" 20
+ set key 80,0.25
+ set output "${GRAPH_NAME}"
+ set logscale x
+ set xlabel "Number of false alarms per 640x480"
+ set ylabel "True positive rate"
+ set grid
+
+ plot [1e-3:100][0.0:1.0] \
+ '/tmp/hb' using 2:1 title "HB" pt 7 ps 1.0 lc 1 lw 1,\
+ '/tmp/h+b' using 2:1 title "H+B" pt 7 ps 1.0 lc 3 lw 1
+EOF
+
+######################################################################
+
+echo "Graph saved in ${GRAPH_NAME}"
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "image.h"
+
+Image::Image(int width, int height) {
+ _width = width;
+ _height = height;
+ _content = new unsigned char[_width * _height];
+}
+
+Image::Image() {
+ _width = 0;
+ _height = 0;
+ _content = 0;
+}
+
+Image::~Image() {
+ delete[] _content;
+}
+
+void Image::crop(int xmin, int ymin, int width, int height) {
+ ASSERT(xmin >= 0 && xmin + width <= _width &&
+ ymin >= 0 && ymin + height <= _height);
+ unsigned char *new_content = new unsigned char[width * height];
+ for(int y = 0; y < height; y++) {
+ for(int x = 0; x < width; x++) {
+ new_content[x + (y * width)] = _content[x + xmin + _width * (y + ymin)];
+ }
+ }
+ delete[] _content;
+ _content = new_content;
+ _width = width;
+ _height = height;
+}
+
+void Image::to_rgb(RGBImage *image) {
+ int c;
+ for(int y = 0; y < _height; y++) {
+ for(int x = 0; x < _width; x++) {
+ c = value(x, y);
+ image->set_pixel(x, y, c, c, c);
+ }
+ }
+}
+
+void Image::read(istream *in) {
+ delete[] _content;
+ read_var(in, &_width);
+ read_var(in, &_height);
+ _content = new unsigned char[_width * _height];
+ in->read((char *) _content, sizeof(unsigned char) * _width * _height);
+}
+
+void Image::write(ostream *out) {
+ write_var(out, &_width);
+ write_var(out, &_height);
+ out->write((char *) _content, sizeof(unsigned char) * _width * _height);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef IMAGE_H
+#define IMAGE_H
+
+#include "storable.h"
+#include "rgb_image.h"
+#include "misc.h"
+
+class Image : public Storable {
+protected:
+ int _width, _height;
+ unsigned char *_content;
+public:
+
+ inline int width() { return _width; }
+ inline int height() { return _height; }
+
+ inline unsigned char value(int x, int y) {
+ if(x >= 0 && x < _width && y >= 0 && y < _height)
+ return _content[x + (y * _width)];
+ else
+ return 0;
+ }
+
+ inline void set_value(int x, int y, unsigned char v) {
+ if(x >= 0 && x < _width && y >= 0 && y < _height)
+ _content[x + (y * _width)] = v;
+ else abort();
+ }
+
+ Image();
+ Image(int width, int height);
+
+ virtual ~Image();
+
+ virtual void crop(int xmin, int ymin, int width, int height);
+ virtual void to_rgb(RGBImage *image);
+
+ virtual void read(istream *in);
+ virtual void write(ostream *out);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "interval.h"
+
+void Interval::set(scalar_t x) {
+ min = x;
+ max = x;
+}
+
+void Interval::set(scalar_t a, scalar_t b) {
+ min = a;
+ max = b;
+}
+
+void Interval::set(Interval *i) {
+ min = i->min;
+ max = i->max;
+}
+
+void Interval::set_subinterval(Interval *i, int k, int nb) {
+ min = i->min + ((i->max - i->min) * scalar_t(k))/scalar_t(nb);
+ max = i->min + ((i->max - i->min) * scalar_t(k + 1))/scalar_t(nb);
+}
+
+void Interval::swallow(Interval *i) {
+ min = ::min(min, i->min);
+ max = ::max(max, i->max);
+}
+
+ostream &operator << (ostream &out, const Interval &i) {
+ return out << "[" << i.min << ", " << i.max << "]";
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef INTERVAL_H
+#define INTERVAL_H
+
+#include "misc.h"
+
+class Interval {
+public:
+ scalar_t min, max;
+
+ void set(scalar_t x);
+ void set(scalar_t a, scalar_t b);
+ void set(Interval *i);
+
+ // Set this interval to the k-th of the nb regular subintervals of i
+ void set_subinterval(Interval *i, int k, int nb);
+
+ // Grow to contain i
+ void swallow(Interval *i);
+
+ inline bool contains(scalar_t x) {
+ return x >= min && x < max;
+ }
+
+ inline void include(scalar_t x) {
+ if(x < min) min = x;
+ if(x > max) max = x;
+ }
+
+ inline scalar_t middle() {
+ return (min + max) / 2;
+ }
+
+ inline scalar_t length() {
+ return max - min;
+ }
+};
+
+ostream &operator << (ostream &out, const Interval &i);
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "jpeg_misc.h"
+
+void my_error_exit (j_common_ptr cinfo) {
+ my_error_ptr myerr = (my_error_ptr) cinfo->err;
+ (*cinfo->err->output_message) (cinfo);
+ longjmp (myerr->setjmp_buffer, 1);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef JPEG_MISC_H
+#define JPEG_MISC_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <jpeglib.h>
+
+struct my_error_mgr {
+ struct jpeg_error_mgr pub;
+ jmp_buf setjmp_buffer;
+};
+
+typedef struct my_error_mgr *my_error_ptr;
+
+void my_error_exit (j_common_ptr cinfo);
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "labelled_image.h"
+
+LabelledImage::LabelledImage() : RichImage() {
+ _target_poses = 0;
+}
+
+LabelledImage::LabelledImage(int width, int height, int nb_targets) : RichImage(width, height) {
+ _nb_targets = nb_targets;
+ _target_poses = new Pose[_nb_targets];
+}
+
+LabelledImage::~LabelledImage() {
+ delete[] _target_poses;
+}
+
+int LabelledImage::pose_cell_label(PoseCell *cell) {
+ int positive = 0;
+ int negative = 1;
+
+ for(int t = 0; t < _nb_targets; t++) {
+ if(cell->contains(_target_poses + t))
+ positive = 1;
+ if(!cell->negative_for_train(_target_poses + t))
+ negative = 0;
+ }
+
+ if(positive) return 1;
+ if(negative) return -1;
+ return 0;
+}
+
+void LabelledImage::crop(int xmin, int ymin, int width, int height) {
+ RichImage::crop(xmin, ymin, width, height);
+ for(int t = 0; t < _nb_targets; t++) {
+ _target_poses[t].translate(- xmin, - ymin);
+ }
+}
+
+void LabelledImage::reduce() {
+ int xmin = _width, xmax = 0, ymin = _height, ymax = 0;
+ if(_nb_targets > 0) {
+ for(int t = 0; t < _nb_targets; t++) {
+ xmin = min(xmin, int(_target_poses[t]._bounding_box_xmin));
+ ymin = min(ymin, int(_target_poses[t]._bounding_box_ymin));
+ xmax = max(xmax, int(_target_poses[t]._bounding_box_xmax));
+ ymax = max(ymax, int(_target_poses[t]._bounding_box_ymax));
+ }
+ } else {
+ xmin = 0; ymin = 0;
+ xmax = 640; ymax = 480;
+ }
+ xmin = max(0, xmin);
+ ymin = max(0, ymin);
+ xmax = min(_width, xmax);
+ ymax = min(_height, ymax);
+ crop(xmin, ymin, xmax - xmin, ymax - ymin);
+}
+
+void LabelledImage::write(ostream *out) {
+ int v = file_format_version;
+ write_var(out, &v);
+ RichImage::write(out);
+ write_var(out, &_nb_targets);
+ for(int t = 0; t < _nb_targets; t++)
+ _target_poses[t].write(out);
+}
+
+void LabelledImage::read(istream *in) {
+ int v;
+ read_var(in, &v);
+ if(v != file_format_version) {
+ cerr << "Pool file format version " << file_format_version << " expected,"
+ << " the file is version " << v
+ << endl;
+ exit(1);
+ }
+ RichImage::read(in);
+ delete[] _target_poses;
+ read_var(in, &_nb_targets);
+ _target_poses = new Pose[_nb_targets];
+ for(int t = 0; t < _nb_targets; t++)
+ _target_poses[t].read(in);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef LABELLED_IMAGE_H
+#define LABELLED_IMAGE_H
+
+#include "rich_image.h"
+#include "pose.h"
+#include "pose_cell.h"
+
+class LabelledImage : public RichImage {
+ int _nb_targets;
+ Pose *_target_poses;
+
+ static const int file_format_version = 2;
+
+public:
+
+ LabelledImage();
+ LabelledImage(int width, int height, int nb_targets);
+
+ virtual ~LabelledImage();
+
+ inline int nb_targets() { return _nb_targets; }
+ inline Pose *get_target_pose(int n) { return _target_poses + n; }
+
+ // The label of a cell can be +1 if it contains a target and -1 if
+ // it is far enough from any target
+ int pose_cell_label(PoseCell *cell);
+
+ void crop(int xmin, int ymin, int width, int height);
+ void reduce();
+
+ virtual void write(ostream *out);
+ virtual void read(istream *in);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "labelled_image_pool.h"
+
+LabelledImagePool::~LabelledImagePool() { }
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef LABELLED_IMAGE_POOL_H
+#define LABELLED_IMAGE_POOL_H
+
+#include "shared.h"
+#include "labelled_image.h"
+
+class LabelledImagePool {
+public:
+ virtual ~LabelledImagePool();
+ virtual int nb_images() = 0;
+ virtual LabelledImage *grab_image(int n_image) = 0;
+ virtual void release_image(int n_image) = 0;
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "labelled_image_pool_file.h"
+
+LabelledImagePoolFile::LabelledImagePoolFile(char *file_name) {
+ _stream = new ifstream(file_name);
+
+ if(_stream->fail()) {
+ cerr << "Can not open image pool " << file_name << " for reading." << endl;
+ exit(1);
+ }
+
+ cout << "Opening image pool " << file_name << " ... ";
+ cout.flush();
+
+ LabelledImage dummy;
+
+ int nb_image_max = 1024;
+ int nb_targets = 0;
+
+ _nb_images = 0;
+ streampos *tmp_positions = new streampos[nb_image_max];
+
+ // This looks slightly ugly to me
+
+ _stream->seekg(0, ios::end);
+ streampos end = _stream->tellg();
+ _stream->seekg(0, ios::beg);
+
+ while(_stream->tellg() < end) {
+ grow(&nb_image_max, _nb_images, &tmp_positions, 2);
+ tmp_positions[_nb_images] = _stream->tellg();
+ dummy.read(_stream);
+ nb_targets += dummy.nb_targets();
+ _nb_images++;
+ }
+
+ _images = new LabelledImage *[_nb_images];
+ _image_stream_positions = new streampos[_nb_images];
+ _image_nb_refs = new int[_nb_images];
+
+ for(int i = 0; i < _nb_images; i++) {
+ _images[i] = 0;
+ _image_stream_positions[i] = tmp_positions[i];
+ _image_nb_refs[i] = 0;
+ }
+
+ delete[] tmp_positions;
+
+ cout << "done." << endl;
+ cout << "It contains " << _nb_images << " images and " << nb_targets << " targets." << endl;
+}
+
+LabelledImagePoolFile::~LabelledImagePoolFile() {
+#ifdef DEBUG
+ for(int i = 0; i < _nb_images; i++) if(_image_nb_refs[i] > 0) {
+ cerr << "Destroying a pool while images are grabbed." << endl;
+ abort();
+ }
+#endif
+ delete[] _images;
+ delete[] _image_stream_positions;
+ delete[] _image_nb_refs;
+ delete _stream;
+}
+
+int LabelledImagePoolFile::nb_images() {
+ return _nb_images;
+}
+
+LabelledImage *LabelledImagePoolFile::grab_image(int n_image) {
+ if(_image_nb_refs[n_image] == 0) {
+ _stream->seekg(_image_stream_positions[n_image]);
+ _images[n_image] = new LabelledImage();
+ _images[n_image]->read(_stream);
+ }
+ _image_nb_refs[n_image]++;
+ return _images[n_image];
+}
+
+void LabelledImagePoolFile::release_image(int n_image) {
+ ASSERT(_image_nb_refs[n_image] > 0);
+ _image_nb_refs[n_image]--;
+ if(_image_nb_refs[n_image] <= 0) delete _images[n_image];
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef LABELLED_IMAGE_POOL_FILE_H
+#define LABELLED_IMAGE_POOL_FILE_H
+
+#include "labelled_image_pool.h"
+#include "labelled_image.h"
+
+#include <fstream>
+
+class LabelledImagePoolFile : public LabelledImagePool {
+ int _nb_images;
+ LabelledImage **_images;
+ streampos *_image_stream_positions;
+ int *_image_nb_refs;
+ ifstream *_stream;
+
+public:
+ LabelledImagePoolFile(char *file_name);
+ virtual ~LabelledImagePoolFile();
+
+ virtual int nb_images();
+
+ // grab_image(n) does _NOT_ build the rich structure. One has to call
+ // compute_rich_structure() for that!
+ virtual LabelledImage *grab_image(int n_image);
+ virtual void release_image(int n_image);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "labelled_image_pool_subset.h"
+
+LabelledImagePoolSubset::LabelledImagePoolSubset(LabelledImagePool *mother_pool,
+ bool *used) {
+ _mother_pool = mother_pool;
+ _nb_images = 0;
+ for(int n = 0; n < _mother_pool->nb_images(); n++)
+ if(used[n]) _nb_images++;
+ _indexes = new int[_nb_images];
+ int k = 0;
+ for(int n = 0; n < _mother_pool->nb_images(); n++)
+ if(used[n]) _indexes[k++] = n;
+}
+
+LabelledImagePoolSubset::~LabelledImagePoolSubset() {
+ delete[] _indexes;
+}
+
+int LabelledImagePoolSubset::nb_images() {
+ return _nb_images;
+}
+
+LabelledImage *LabelledImagePoolSubset::grab_image(int n_image) {
+ return _mother_pool->grab_image(_indexes[n_image]);
+}
+
+void LabelledImagePoolSubset::release_image(int n_image) {
+ _mother_pool->release_image(_indexes[n_image]);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef LABELLED_IMAGE_POOL_SUBSET_H
+#define LABELLED_IMAGE_POOL_SUBSET_H
+
+#include "labelled_image_pool.h"
+
+class LabelledImagePoolSubset : public LabelledImagePool {
+ LabelledImagePool *_mother_pool;
+ int _nb_images;
+ int *_indexes;
+public:
+ LabelledImagePoolSubset(LabelledImagePool *mother_pool, bool *used);
+ virtual ~LabelledImagePoolSubset();
+ virtual int nb_images();
+ virtual LabelledImage *grab_image(int n_image);
+ virtual void release_image(int n_image);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include <fstream>
+
+using namespace std;
+
+#include "misc.h"
+#include "global.h"
+#include "param_parser.h"
+#include "labelled_image.h"
+
+void parse_warning(const char *message, char *file, int line) {
+ cerr << message << " " << file << ":" << line << "." << endl;
+ cerr.flush();
+}
+
+void parse_error(const char *message, char *file, int line) {
+ cerr << message << " " << file << ":" << line << "." << endl;
+ cerr.flush();
+ exit(1);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+void list_to_pool(char *main_path, char *list_name, char *pool_name) {
+ ifstream list_stream(list_name);
+
+ if(list_stream.fail()) {
+ cerr << "Can not open " << list_name << " for reading." << endl;
+ exit(1);
+ }
+
+ ofstream pool_stream(pool_name);
+
+ if(pool_stream.fail()) {
+ cerr << "Can not open " << pool_name << " for writing." << endl;
+ exit(1);
+ }
+
+ int line_number = 0;
+ int nb_scenes = 0;
+
+ int nb_cats = -1;
+ LabelledImage *current_image = 0;
+ bool open_current_cat = false;
+ bool head_defined = false;
+ bool bounding_box_defined = false;
+ bool body_defined = false;
+ int nb_ears = 0;
+
+ while(!list_stream.eof() && (global.nb_images < 0 || nb_scenes < global.nb_images)) {
+ char line[large_buffer_size], token[buffer_size], full_image_name[buffer_size];
+ list_stream.getline(line, large_buffer_size);
+ line_number++;
+ char *s = line;
+ s = next_word(token, s, buffer_size);
+
+ //////////////////////////////////////////////////////////////////////
+
+ if(strcmp(token, "SCENE") == 0) {
+
+ if(current_image) {
+ parse_error("Non-closed scene ", list_name, line_number);
+ }
+
+ if(s) {
+ s = next_word(token, s, buffer_size);
+ sprintf(full_image_name, "%s/%s", main_path, token);
+ } else {
+ parse_error("Image name is missing ", list_name, line_number);
+ }
+
+ cout << "Processing scene " << full_image_name << "." << endl;
+
+ int nb_cats_in_current_image = -1;
+
+ if(s) {
+ s = next_word(token, s, buffer_size);
+ nb_cats_in_current_image = atoi(token);
+ } else {
+ parse_error("Number of cats is missing ", list_name, line_number);
+ }
+
+ if(nb_cats_in_current_image < 0 || nb_cats_in_current_image > 100) {
+ parse_error("Weird number of cats ", list_name, line_number);
+ }
+
+ RGBImage tmp;
+ tmp.read_jpg(full_image_name);
+
+ current_image = new LabelledImage(tmp.width(),
+ tmp.height(),
+ nb_cats_in_current_image);
+
+ for(int y = 0; y < tmp.height(); y++) {
+ for(int x = 0; x < tmp.width(); x++) {
+ current_image->set_value(x, y,
+ int(scalar_t(tmp.pixel(x, y, 0)) * 0.2989 +
+ scalar_t(tmp.pixel(x, y, 1)) * 0.5870 +
+ scalar_t(tmp.pixel(x, y, 2)) * 0.1140));
+ }
+ }
+
+ nb_cats = 0;
+ }
+
+ else if(strcmp(token, "END_SCENE") == 0) {
+ if(current_image == 0)
+ parse_error("Non-open scene ", list_name, line_number);
+
+ if(nb_cats < current_image->nb_targets()) {
+ parse_warning("Less cats than advertised (some were ignored?), scene ignored",
+ list_name, line_number);
+ } else {
+ current_image->write(&pool_stream);
+ nb_scenes++;
+ }
+
+ delete current_image;
+ current_image = 0;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(token, "CAT") == 0) {
+ if(open_current_cat)
+ parse_error("Non-closed cat ", list_name, line_number);
+ if(current_image == 0)
+ parse_error("Cat without scene ", list_name, line_number);
+ if(nb_cats >= current_image->nb_targets())
+ parse_error("More cats than advertised", list_name, line_number);
+ open_current_cat = true;
+ head_defined = false;
+ bounding_box_defined = false;
+ body_defined = false;
+ nb_ears = 0;
+ }
+
+ else if(strcmp(token, "END_CAT") == 0) {
+ if(!open_current_cat)
+ parse_error("Undefined cat ", list_name, line_number);
+
+ if(!bounding_box_defined) {
+ parse_error("Undefined bounding box ", list_name, line_number);
+ }
+
+ if(head_defined && body_defined) {
+ if(current_image->get_target_pose(nb_cats)->_head_radius > global.min_head_radius &&
+ current_image->get_target_pose(nb_cats)->_head_radius < global.max_head_radius) {
+ nb_cats++;
+ } else {
+ cerr << "Cat ignored since the head radius ("
+ << current_image->get_target_pose(nb_cats)->_head_radius << ") is not in the tolerance ("
+ << global.min_head_radius << ", " << global.max_head_radius
+ << ") "
+ << list_name << ":" << line_number
+ << endl;
+ cerr.flush();
+ }
+ } else {
+ parse_warning("Cat ignored since either the body, head or belly are undefined",
+ list_name, line_number);
+ }
+
+ open_current_cat = false;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(token, "BODYBOX") == 0) {
+ if(!open_current_cat) parse_error("Undefined cat ", list_name, line_number);
+ if(bounding_box_defined) parse_error("Two bounding box", list_name, line_number);
+ int xmin = -1, ymin = -1, xmax = -1, ymax = -1;
+ if(s) { s = next_word(token, s, buffer_size); xmin = atoi(token); }
+ else parse_error("BODYBOX parameter xmin missing ", list_name, line_number);
+ if(s) { s = next_word(token, s, buffer_size); ymin = atoi(token); }
+ else parse_error("BODYBOX parameter ymin missing ", list_name, line_number);
+ if(s) { s = next_word(token, s, buffer_size); xmax = atoi(token); }
+ else parse_error("BODYBOX parameter xmax missing ", list_name, line_number);
+ if(s) { s = next_word(token, s, buffer_size); ymax = atoi(token); }
+ else parse_error("BODYBOX parameter ymax missing ", list_name, line_number);
+ current_image->get_target_pose(nb_cats)->_bounding_box_xmin = xmin;
+ current_image->get_target_pose(nb_cats)->_bounding_box_ymin = ymin;
+ current_image->get_target_pose(nb_cats)->_bounding_box_xmax = xmax;
+ current_image->get_target_pose(nb_cats)->_bounding_box_ymax = ymax;
+ bounding_box_defined = true;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+
+ else if(strcmp(token, "HEADELLIPSE") == 0) {
+ if(!open_current_cat) parse_error("Undefined cat ", list_name, line_number);
+ if(head_defined) parse_error("Two head definitions", list_name, line_number);
+ int xmin = -1, ymin = -1, xmax = -1, ymax = -1;
+ if(s) { s = next_word(token, s, buffer_size); xmin = atoi(token); }
+ else parse_error("HEADELLIPSE parameter xmin missing ", list_name, line_number);
+ if(s) { s = next_word(token, s, buffer_size); ymin = atoi(token); }
+ else parse_error("HEADELLIPSE parameter ymin missing ", list_name, line_number);
+ if(s) { s = next_word(token, s, buffer_size); xmax = atoi(token); }
+ else parse_error("HEADELLIPSE parameter xmax missing ", list_name, line_number);
+ if(s) { s = next_word(token, s, buffer_size); ymax = atoi(token); }
+ else parse_error("HEADELLIPSE parameter ymax missing ", list_name, line_number);
+ current_image->get_target_pose(nb_cats)->_head_xc = (xmin + xmax)/2;
+ current_image->get_target_pose(nb_cats)->_head_yc = (ymin + ymax)/2;
+ current_image->get_target_pose(nb_cats)->_head_radius = int(sqrt(scalar_t(xmax - xmin) * scalar_t(ymax - ymin))/2);
+ head_defined = true;
+ }
+
+ else if(strcmp(token, "BELLYLOCATION") == 0) {
+ if(!open_current_cat) parse_error("Undefined cat ", list_name, line_number);
+ int x1 = -1, y1 = -1;
+
+ if(s) { s = next_word(token, s, buffer_size); x1 = atoi(token); }
+ else parse_error("BELLYLOCATION parameter x1 missing ", list_name, line_number);
+ if(s) { s = next_word(token, s, buffer_size); y1 = atoi(token); }
+ else parse_error("BELLYLOCATION parameter y1 missing ", list_name, line_number);
+
+ if(body_defined) {
+ parse_error("More than one body location. ", list_name, line_number);
+ } else {
+
+ Pose *pose = current_image->get_target_pose(nb_cats);
+
+ pose->_body_xc = x1;
+ pose->_body_yc = y1;
+
+ body_defined = true;
+ }
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+int main(int argc, char **argv) {
+ char *new_argv[argc];
+ int new_argc = 0;
+
+ {
+ ParamParser parser;
+ global.init_parser(&parser);
+ parser.parse_options(argc, argv, false, &new_argc, new_argv);
+ global.read_parser(&parser);
+ cout << "-- PARAMETERS --------------------------------------------------------" << endl;
+ parser.print_all(&cout);
+ }
+
+ if(new_argc != 4) {
+ cerr << new_argv[0] << " <list file> <image path> <pool name>" << endl;
+ exit(1);
+ }
+
+ cout << "From list " << new_argv[1]
+ << " and image dir " << new_argv[2]
+ << ", generating pool " << new_argv[3]
+ << "." << endl;
+
+ cout << "-- GENERATING IMAGES -------------------------------------------------" << endl;
+
+ list_to_pool(new_argv[2], new_argv[1], new_argv[3]);
+
+ cout << "-- FINISHED ----------------------------------------------------------" << endl;
+
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "tools.h"
+#include "loss_machine.h"
+
+LossMachine::LossMachine(int loss_type) {
+ _loss_type = loss_type;
+}
+
+void LossMachine::get_loss_derivatives(SampleSet *samples,
+ scalar_t *responses,
+ scalar_t *derivatives) {
+
+ switch(_loss_type) {
+
+ case LOSS_EXPONENTIAL:
+ {
+ for(int n = 0; n < samples->nb_samples(); n++) {
+ derivatives[n] =
+ - samples->label(n) * exp( - samples->label(n) * responses[n]);
+ }
+ }
+ break;
+
+ case LOSS_EV_REGULARIZED:
+ {
+ scalar_t sum_pos = 0, sum_sq_pos = 0, nb_pos = 0, m_pos, v_pos;
+ scalar_t sum_neg = 0, sum_sq_neg = 0, nb_neg = 0, m_neg, v_neg;
+
+ for(int n = 0; n < samples->nb_samples(); n++) {
+ if(samples->label(n) > 0) {
+ sum_pos += responses[n];
+ sum_sq_pos += sq(responses[n]);
+ nb_pos += 1.0;
+ }
+ else if(samples->label(n) < 0) {
+ sum_neg += responses[n];
+ sum_sq_neg += sq(responses[n]);
+ nb_neg += 1.0;
+ }
+ }
+
+ m_pos = sum_pos / nb_pos;
+ v_pos = sum_sq_pos/(nb_pos - 1) - sq(sum_pos)/(nb_pos * (nb_pos - 1));
+
+ scalar_t loss_pos = nb_pos * exp(v_pos/2 - m_pos);
+
+ m_neg = sum_neg / nb_neg;
+ v_neg = sum_sq_neg/(nb_neg - 1) - sq(sum_neg)/(nb_neg * (nb_neg - 1));
+
+ scalar_t loss_neg = nb_neg * exp(v_neg/2 + m_neg);
+
+ for(int n = 0; n < samples->nb_samples(); n++) {
+ if(samples->label(n) > 0) {
+ derivatives[n] =
+ ( - 1/nb_pos + (responses[n] - m_pos)/(nb_pos - 1)) * loss_pos;
+ } else if(samples->label(n) < 0) {
+ derivatives[n] =
+ ( 1/nb_neg + (responses[n] - m_neg)/(nb_neg - 1)) * loss_neg;
+ }
+ }
+ }
+
+ break;
+
+ case LOSS_HINGE:
+ {
+ for(int n = 0; n < samples->nb_samples(); n++) {
+ if(samples->label(n) != 0 && samples->label(n) * responses[n] < 1)
+ derivatives[n] = 1;
+ else
+ derivatives[n] = 0;
+ }
+ }
+ break;
+
+ case LOSS_LOGISTIC:
+ {
+ for(int n = 0; n < samples->nb_samples(); n++) {
+ if(samples->label(n) == 0)
+ derivatives[n] = 0.0;
+ else
+ derivatives[n] = samples->label(n) * 1/(1 + exp(samples->label(n) * responses[n]));
+ }
+ }
+ break;
+
+ default:
+ cerr << "Unknown loss type in BoostedClassifier::get_loss_derivatives."
+ << endl;
+ exit(1);
+ }
+
+}
+
+scalar_t LossMachine::loss(SampleSet *samples, scalar_t *responses) {
+ scalar_t l = 0;
+
+ switch(_loss_type) {
+
+ case LOSS_EXPONENTIAL:
+ {
+ for(int n = 0; n < samples->nb_samples(); n++) {
+ l += exp( - samples->label(n) * responses[n]);
+ ASSERT(!isinf(l));
+ }
+ }
+ break;
+
+ case LOSS_EV_REGULARIZED:
+ {
+ scalar_t sum_pos = 0, sum_sq_pos = 0, nb_pos = 0, m_pos, v_pos;
+ scalar_t sum_neg = 0, sum_sq_neg = 0, nb_neg = 0, m_neg, v_neg;
+
+ for(int n = 0; n < samples->nb_samples(); n++) {
+ if(samples->label(n) > 0) {
+ sum_pos += responses[n];
+ sum_sq_pos += sq(responses[n]);
+ nb_pos += 1.0;
+ } else if(samples->label(n) < 0) {
+ sum_neg += responses[n];
+ sum_sq_neg += sq(responses[n]);
+ nb_neg += 1.0;
+ }
+ }
+
+ l = 0;
+
+ if(nb_pos > 0) {
+ m_pos = sum_pos / nb_pos;
+ v_pos = sum_sq_pos/(nb_pos - 1) - sq(sum_pos)/(nb_pos * (nb_pos - 1));
+ l += nb_pos * exp(v_pos/2 - m_pos);
+ }
+
+ if(nb_neg > 0) {
+ m_neg = sum_neg / nb_neg;
+ v_neg = sum_sq_neg/(nb_neg - 1) - sq(sum_neg)/(nb_neg * (nb_neg - 1));
+ l += nb_neg * exp(v_neg/2 + m_neg);
+ }
+
+ }
+ break;
+
+ case LOSS_HINGE:
+ {
+ for(int n = 0; n < samples->nb_samples(); n++) {
+ if(samples->label(n) != 0) {
+ if(samples->label(n) * responses[n] < 1)
+ l += (1 - samples->label(n) * responses[n]);
+ }
+ }
+ }
+ break;
+
+ case LOSS_LOGISTIC:
+ {
+ for(int n = 0; n < samples->nb_samples(); n++) {
+ if(samples->label(n) != 0) {
+ scalar_t u = - samples->label(n) * responses[n];
+ if(u > 20) {
+ l += u;
+ } if(u > -20) {
+ l += log(1 + exp(u));
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ cerr << "Unknown loss type in LossMachine::loss." << endl;
+ exit(1);
+ }
+
+ return l;
+}
+
+scalar_t LossMachine::optimal_weight(SampleSet *sample_set,
+ scalar_t *weak_learner_responses,
+ scalar_t *current_responses) {
+
+ switch(_loss_type) {
+
+ case LOSS_EXPONENTIAL:
+ {
+ scalar_t num = 0, den = 0, z;
+ for(int n = 0; n < sample_set->nb_samples(); n++) {
+ z = sample_set->label(n) * weak_learner_responses[n];
+ if(z > 0) {
+ num += exp( - sample_set->label(n) * current_responses[n]);
+ } else if(z < 0) {
+ den += exp( - sample_set->label(n) * current_responses[n]);
+ }
+ }
+
+ return 0.5 * log(num / den);
+ }
+ break;
+
+ case LOSS_EV_REGULARIZED:
+ {
+
+ scalar_t u = 0, du = -0.1;
+ scalar_t *responses = new scalar_t[sample_set->nb_samples()];
+
+ scalar_t l, prev_l = -1;
+
+ const scalar_t minimum_delta_for_optimization = 1e-5;
+
+ scalar_t shift = 0;
+
+ {
+ scalar_t sum_pos = 0, sum_sq_pos = 0, nb_pos = 0, m_pos, v_pos;
+ scalar_t sum_neg = 0, sum_sq_neg = 0, nb_neg = 0, m_neg, v_neg;
+
+ for(int n = 0; n < sample_set->nb_samples(); n++) {
+ if(sample_set->label(n) > 0) {
+ sum_pos += responses[n];
+ sum_sq_pos += sq(responses[n]);
+ nb_pos += 1.0;
+ } else if(sample_set->label(n) < 0) {
+ sum_neg += responses[n];
+ sum_sq_neg += sq(responses[n]);
+ nb_neg += 1.0;
+ }
+ }
+
+ if(nb_pos > 0) {
+ m_pos = sum_pos / nb_pos;
+ v_pos = sum_sq_pos/(nb_pos - 1) - sq(sum_pos)/(nb_pos * (nb_pos - 1));
+ shift = max(shift, v_pos/2 - m_pos);
+ }
+
+ if(nb_neg > 0) {
+ m_neg = sum_neg / nb_neg;
+ v_neg = sum_sq_neg/(nb_neg - 1) - sq(sum_neg)/(nb_neg * (nb_neg - 1));
+ shift = max(shift, v_neg/2 + m_neg);
+ }
+
+// (*global.log_stream) << "nb_pos = " << nb_pos << " nb_neg = " << nb_neg << endl;
+
+ }
+
+ int nb = 0;
+
+ while(nb < 100 && abs(du) > minimum_delta_for_optimization) {
+ nb++;
+
+// (*global.log_stream) << "l = " << l << " u = " << u << " du = " << du << endl;
+
+ u += du;
+ for(int s = 0; s < sample_set->nb_samples(); s++) {
+ responses[s] = current_responses[s] + u * weak_learner_responses[s] ;
+ }
+
+ {
+ scalar_t sum_pos = 0, sum_sq_pos = 0, nb_pos = 0, m_pos, v_pos;
+ scalar_t sum_neg = 0, sum_sq_neg = 0, nb_neg = 0, m_neg, v_neg;
+
+ for(int n = 0; n < sample_set->nb_samples(); n++) {
+ if(sample_set->label(n) > 0) {
+ sum_pos += responses[n];
+ sum_sq_pos += sq(responses[n]);
+ nb_pos += 1.0;
+ } else if(sample_set->label(n) < 0) {
+ sum_neg += responses[n];
+ sum_sq_neg += sq(responses[n]);
+ nb_neg += 1.0;
+ }
+ }
+
+ l = 0;
+
+ if(nb_pos > 0) {
+ m_pos = sum_pos / nb_pos;
+ v_pos = sum_sq_pos/(nb_pos - 1) - sq(sum_pos)/(nb_pos * (nb_pos - 1));
+ l += nb_pos * exp(v_pos/2 - m_pos - shift);
+ }
+
+ if(nb_neg > 0) {
+ m_neg = sum_neg / nb_neg;
+ v_neg = sum_sq_neg/(nb_neg - 1) - sq(sum_neg)/(nb_neg * (nb_neg - 1));
+ l += nb_neg * exp(v_neg/2 + m_neg - shift);
+ }
+
+ }
+
+ if(l > prev_l) du = du * -0.25;
+ prev_l = l;
+ }
+
+ delete[] responses;
+
+ return u;
+ }
+
+ case LOSS_HINGE:
+ case LOSS_LOGISTIC:
+ {
+
+ scalar_t u = 0, du = -0.1;
+ scalar_t *responses = new scalar_t[sample_set->nb_samples()];
+
+ scalar_t l, prev_l = -1;
+
+ const scalar_t minimum_delta_for_optimization = 1e-5;
+
+ int n = 0;
+ while(n < 100 && abs(du) > minimum_delta_for_optimization) {
+ n++;
+ u += du;
+ for(int s = 0; s < sample_set->nb_samples(); s++) {
+ responses[s] = current_responses[s] + u * weak_learner_responses[s] ;
+ }
+ l = loss(sample_set, responses);
+ if(l > prev_l) du = du * -0.25;
+ prev_l = l;
+ }
+
+ (*global.log_stream) << "END l = " << l << " du = " << du << endl;
+
+ delete[] responses;
+
+ return u;
+ }
+
+ default:
+ cerr << "Unknown loss type in LossMachine::optimal_weight." << endl;
+ exit(1);
+ }
+
+}
+
+void LossMachine::subsample(int nb, scalar_t *labels, scalar_t *responses,
+ int nb_to_sample, int *sample_nb_occurences, scalar_t *sample_responses,
+ int allow_duplicates) {
+
+ switch(_loss_type) {
+
+ case LOSS_EXPONENTIAL:
+ {
+ scalar_t *weights = new scalar_t[nb];
+
+ for(int n = 0; n < nb; n++) {
+ if(labels[n] == 0) {
+ weights[n] = 0;
+ } else {
+ weights[n] = exp( - labels[n] * responses[n]);
+ }
+ sample_nb_occurences[n] = 0;
+ sample_responses[n] = 0.0;
+ }
+
+ scalar_t total_weight;
+ int nb_sampled = 0, sum_sample_nb_occurences = 0;
+
+ int *sampled_indexes = new int[nb_to_sample];
+
+ (*global.log_stream) << "Sampling " << nb_to_sample << " samples." << endl;
+
+ do {
+ total_weight = robust_sampling(nb,
+ weights,
+ nb_to_sample,
+ sampled_indexes);
+
+ for(int k = 0; nb_sampled < nb_to_sample && k < nb_to_sample; k++) {
+ int i = sampled_indexes[k];
+ if(allow_duplicates || sample_nb_occurences[i] == 0) nb_sampled++;
+ sample_nb_occurences[i]++;
+ sum_sample_nb_occurences++;
+ }
+ } while(nb_sampled < nb_to_sample);
+
+ (*global.log_stream) << "nb_sampled = " << nb_sampled << " nb_to_sample = " << nb_to_sample << endl;
+
+ (*global.log_stream) << "Done." << endl;
+
+ delete[] sampled_indexes;
+
+ scalar_t unit_weight = log(total_weight / scalar_t(sum_sample_nb_occurences));
+
+ for(int n = 0; n < nb; n++) {
+ if(sample_nb_occurences[n] > 0) {
+ if(allow_duplicates) {
+ sample_responses[n] = - labels[n] * unit_weight;
+ } else {
+ sample_responses[n] = - labels[n] * (unit_weight + log(scalar_t(sample_nb_occurences[n])));
+ sample_nb_occurences[n] = 1;
+ }
+ }
+ }
+
+ delete[] weights;
+
+ }
+ break;
+
+ default:
+ cerr << "Unknown loss type in LossMachine::resample." << endl;
+ exit(1);
+ }
+
+
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef LOSS_MACHINE_H
+#define LOSS_MACHINE_H
+
+#include "misc.h"
+#include "sample_set.h"
+
+class LossMachine {
+ int _loss_type;
+
+public:
+ LossMachine(int loss_type);
+
+ void get_loss_derivatives(SampleSet *samples,
+ scalar_t *responses,
+ scalar_t *derivatives);
+
+ scalar_t loss(SampleSet *samples, scalar_t *responses);
+
+ scalar_t optimal_weight(SampleSet *sample_set,
+ scalar_t *weak_learner_responses,
+ scalar_t *current_responses);
+
+ // This method returns in sample_nb_occurences[k] the number of time
+ // the example k was sampled, and in sample_responses[k] the
+ // consistent response so that the overall loss remains the same. If
+ // allow_duplicates is set to 1, all samples will have an identical
+ // response (i.e. weight), but some may have more than one
+ // occurence. On the contrary, if allow_duplicates is 0, samples
+ // will all have only one occurence (or zero) but the responses may
+ // vary to account for the multiple sampling.
+
+ void subsample(int nb, scalar_t *labels, scalar_t *responses,
+ int nb_to_sample, int *sample_nb_occurences, scalar_t *sample_responses,
+ int allow_duplicates);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "materials.h"
+#include "boosted_classifier.h"
+#include "parsing_pool.h"
+#include "rgb_image_subpixel.h"
+
+void write_referential_png(char *filename,
+ int level,
+ RichImage *image,
+ PiReferential *referential,
+ PiFeature *pf) {
+
+ scalar_t s = global.discrete_log_scale_to_scale(referential->common_scale());
+
+ RGBImage result(int(image->width() * s), int(image->height() * s));
+
+ for(int y = 0; y < result.height(); y++) {
+ for(int x = 0; x < result.width(); x++) {
+ int c = 0;
+
+ // GRAYSCALES
+
+ for(int b = 0; b < RichImage::nb_gray_tags; b++) {
+ c += (b * 256)/RichImage::nb_gray_tags *
+ image->nb_tags_in_window(referential->common_scale(),
+ RichImage::first_gray_tag + b,
+ x, y,
+ x + 1, y + 1);
+ }
+
+ // EDGES
+
+ // for(int b = 0; b < RichImage::nb_edge_tags; b++) {
+ // c += image->nb_tags_in_window(referential->common_scale(),
+ // RichImage::first_edge_tag + b,
+ // x, y,
+ // x + 1, y + 1);
+ // }
+ // c = 255 - (c * 255)/RichImage::nb_edge_tags;
+
+ // THRESHOLDED VARIANCE
+
+ // c = image->nb_tags_in_window(referential->common_scale(),
+ // RichImage::variance_tag,
+ // x, y,
+ // x + 1, y + 1);
+ // c = (1 - c) * 255;
+
+ result.set_pixel(x, y, c, c, c);
+ }
+ }
+
+ RGBImageSubpixel result_sp(&result);
+
+ if(pf) {
+ pf->draw(&result_sp, 255, 255, 0, referential);
+ } else {
+ referential->draw(&result_sp, level);
+ }
+
+ (*global.log_stream) << "Writing " << filename << endl;
+ result_sp.write_png(filename);
+}
+
+void write_one_pi_feature_png(char *filename,
+ LabelledImage *image,
+ PoseCellHierarchy *hierarchy,
+ int nb_target,
+ int level,
+ PiFeature *pf) {
+
+ PoseCell target_cell;
+ hierarchy->get_containing_cell(image, level,
+ image->get_target_pose(nb_target), &target_cell);
+ PiReferential referential(&target_cell);
+ RGBImage result(image->width(), image->height());
+ image->to_rgb(&result);
+ RGBImageSubpixel result_sp(&result);
+ referential.draw(&result_sp, level);
+ // pf->draw(&result_sp, 255, 255, 0, &referential);
+ result_sp.write_png(filename);
+}
+
+void write_pool_images_with_poses_and_referentials(LabelledImagePool *pool,
+ Detector *detector) {
+ LabelledImage *image;
+ char buffer[buffer_size];
+
+ PoseCell target_cell;
+ Pose p;
+ PiFeature *pf;
+
+ PoseCellHierarchy *hierarchy = new PoseCellHierarchy(pool);
+
+ for(int i = 0; i < min(global.nb_images, pool->nb_images()); i++) {
+ image = pool->grab_image(i);
+ RGBImage result(image->width(), image->height());
+ image->to_rgb(&result);
+ RGBImageSubpixel result_sp(&result);
+
+ if(global.pictures_for_article) {
+ for(int t = 0; t < image->nb_targets(); t++) {
+ image->get_target_pose(t)->draw(8, 255, 255, 255,
+ hierarchy->nb_levels() - 1, &result_sp);
+
+ }
+ for(int t = 0; t < image->nb_targets(); t++) {
+ image->get_target_pose(t)->draw(4, 0, 0, 0,
+ hierarchy->nb_levels() - 1, &result_sp);
+ }
+ } else {
+ for(int t = 0; t < image->nb_targets(); t++) {
+ image->get_target_pose(t)->draw(4, 255, 128, 0,
+ hierarchy->nb_levels() - 1, &result_sp);
+ }
+ }
+
+ sprintf(buffer, "/tmp/truth-%05d.png", i);
+ cout << "Writing " << buffer << endl;
+ result_sp.write_png(buffer);
+ pool->release_image(i);
+ }
+
+ for(int i = 0; i < min(global.nb_images, pool->nb_images()); i++) {
+ image = pool->grab_image(i);
+
+ RGBImage result(image->width(), image->height());
+ image->to_rgb(&result);
+ RGBImageSubpixel result_sp(&result);
+
+ int u = 0;
+
+ // image->compute_rich_structure();
+
+ for(int t = 0; t < image->nb_targets(); t++) {
+
+ image->get_target_pose(t)->draw(4, 255, 0, 0,
+ hierarchy->nb_levels() - 1, &result_sp);
+
+ hierarchy->get_containing_cell(image,
+ hierarchy->nb_levels() - 1,
+ image->get_target_pose(t), &target_cell);
+
+ target_cell.get_centroid(&p);
+
+ p.draw(4, 0, 255, 0, hierarchy->nb_levels() - 1, &result_sp);
+
+ PiReferential referential(&target_cell);
+
+ sprintf(buffer, "/tmp/referential-%05d-%02d.png", i, u);
+ image->compute_rich_structure();
+ write_referential_png(buffer, hierarchy->nb_levels() - 1, image, &referential, 0);
+
+ if(detector) {
+ int nb_features = 100;
+ for(int f = 0; f < nb_features; f++)
+ if(f == 0 || f ==50 || f == 53) {
+ int n_family, n_feature;
+ if(f < nb_features/2) {
+ n_family = 0;
+ n_feature = f;
+ } else {
+ n_family = detector->_nb_classifiers_per_level;
+ n_feature = f - nb_features/2;
+ }
+ pf = detector->_pi_feature_families[n_family]->get_feature(n_feature);
+ sprintf(buffer, "/tmp/pf-%05d-%02d-%03d.png", i, u, f);
+ write_referential_png(buffer,
+ hierarchy->nb_levels() - 1,
+ image,
+ &referential,
+ pf);
+ }
+ }
+ u++;
+ }
+
+ // sprintf(buffer, "/tmp/image-%05d.png", i);
+ // cout << "Writing " << buffer << endl;
+ // result_sp.write_png(buffer);
+
+ // if(global.write_tag_images) {
+ // sprintf(buffer, "/tmp/image-%05d_tags.png", i);
+ // cout << "Writing " << buffer << endl;
+ // image->compute_rich_structure();
+ // image->write_tag_png(buffer);
+ // }
+
+ pool->release_image(i);
+ }
+
+ delete hierarchy;
+}
+
+void write_image_with_detections(const char *filename,
+ LabelledImage *image,
+ PoseCellSet *detections,
+ int level) {
+
+ RGBImage result(image->width(), image->height());
+
+ for(int y = 0; y < result.height(); y++) {
+ for(int x = 0; x < result.width(); x++) {
+ int c = image->value(x, y);
+ result.set_pixel(x, y, c, c, c);
+ }
+ }
+
+ RGBImageSubpixel result_sp(&result);
+
+ if(global.pictures_for_article) {
+ for(int a = 0; a < detections->nb_cells(); a++) {
+ Pose pose;
+ detections->get_cell(a)->get_centroid(&pose);
+ pose.draw(8, 255, 255, 255, level, &result_sp);
+ }
+ for(int a = 0; a < detections->nb_cells(); a++) {
+ Pose pose;
+ detections->get_cell(a)->get_centroid(&pose);
+ pose.draw(4, 0, 0, 0, level, &result_sp);
+ }
+ } else {
+ for(int a = 0; a < detections->nb_cells(); a++) {
+ Pose pose;
+ detections->get_cell(a)->get_centroid(&pose);
+ pose.draw(4, 255, 128, 0, level, &result_sp);
+ }
+ }
+
+ (*global.log_stream) << "Writing " << filename << endl;
+
+ result_sp.write_png(filename);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef MATERIALS_H
+#define MATERIALS_H
+
+#include "misc.h"
+#include "rich_image.h"
+#include "pi_feature.h"
+#include "pi_referential.h"
+#include "labelled_image_pool.h"
+#include "labelled_image.h"
+#include "pose_cell_set.h"
+#include "pose_cell_hierarchy.h"
+#include "detector.h"
+
+void write_pool_images_with_poses_and_referentials(LabelledImagePool *pool,
+ Detector *detector);
+
+void write_one_pi_feature_png(char *filename,
+ LabelledImage *image,
+ PoseCellHierarchy *hierarchy,
+ int nb_target,
+ int level,
+ PiFeature *pf);
+
+void write_image_with_detections(const char *filename,
+ LabelledImage *image,
+ PoseCellSet *detections,
+ int level);
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include <fstream>
+
+using namespace std;
+
+#include "misc.h"
+
+char *basename(char *name) {
+ char *result = name;
+ while(*name) {
+ if(*name == '/') result = name + 1;
+ name++;
+ }
+ return result;
+}
+
+char *next_word(char *buffer, char *r, int buffer_size) {
+ char *s;
+ s = buffer;
+
+ if(r != 0) {
+ while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
+ if(*r == '"') {
+ r++;
+ while((*r != '"') && (*r != '\0') &&
+ (s<buffer+buffer_size-1))
+ *s++ = *r++;
+ if(*r == '"') r++;
+ } else {
+ while((*r != '\r') && (*r != '\n') && (*r != '\0') &&
+ (*r != '\t') && (*r != ' ') && (*r != ',')) {
+ if(s == buffer + buffer_size) {
+ cerr << "Buffer overflow in next_word." << endl;
+ exit(1);
+ }
+ *s++ = *r++;
+ }
+ }
+
+ while((*r == ' ') || (*r == '\t') || (*r == ',')) r++;
+ if((*r == '\0') || (*r=='\r') || (*r=='\n')) r = 0;
+ }
+ *s = '\0';
+
+ return r;
+}
+
+scalar_t discrete_entropy(int *n, int nb) {
+ scalar_t s = 0, t = 0;
+ for(int k = 0; k < nb; k++) if(n[k] > 0) {
+ s += n[k] * log(scalar_t(n[k]));
+ t += n[k];
+ }
+ return (log(t) - s/scalar_t(t))/log(2.0);
+}
+
+void random_permutation(int *val, int nb) {
+ for(int k = 0; k < nb; k++) val[k] = k;
+ int i, t;
+ for(int k = 0; k < nb - 1; k++) {
+ i = int(drand48() * (nb - k)) + k;
+ t = val[i];
+ val[i] = val[k];
+ val[k] = t;
+ }
+}
+
+void tag_subset(bool *val, int nb_total, int nb_to_tag) {
+ ASSERT(nb_to_tag <= nb_total);
+ int index[nb_total];
+ random_permutation(index, nb_total);
+ for(int n = 0; n < nb_total; n++) val[n] = false;
+ for(int n = 0; n < nb_to_tag; n++) val[index[n]] = true;
+}
+
+int compare_couple(const void *a, const void *b) {
+ if(((Couple *) a)->value < ((Couple *) b)->value) return -1;
+ else if(((Couple *) a)->value > ((Couple *) b)->value) return 1;
+ else return 0;
+}
+
+void used_memory(size_t &size, size_t &resident,
+ size_t &share, size_t &text, size_t &lib, size_t &data,
+ size_t &dt) {
+ char buffer[buffer_size];
+ sprintf(buffer, "/proc/%d/statm", getpid());
+ ifstream in(buffer);
+ if(in.good()) {
+ in >> size >> resident >> share >> text >> lib >> data >> dt;
+ size_t ps = getpagesize();
+ size *= ps;
+ resident *= ps;
+ share *= ps;
+ text *= ps;
+ lib *= ps;
+ data *= ps;
+ dt *= ps;
+ } else {
+ size = 0;
+ resident = 0;
+ share = 0;
+ text = 0;
+ lib = 0;
+ data = 0;
+ dt = 0;
+ cerr << "Can not open " << buffer << " for reading the memory usage." << endl;
+ }
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef MISC_H
+#define MISC_H
+
+#include <iostream>
+#include <cmath>
+#include <fstream>
+#include <cfloat>
+#include <stdlib.h>
+#include <string.h>
+
+using namespace std;
+
+//typedef double scalar_t;
+typedef float scalar_t;
+const scalar_t SCALAR_MAX = FLT_MAX;
+const scalar_t SCALAR_MIN = FLT_MIN;
+
+const int buffer_size = 1024;
+const int large_buffer_size = 65536;
+
+using namespace std;
+
+#ifdef DEBUG
+#define ASSERT(x) if(!(x)) { \
+ std::cerr << "ASSERT FAILED IN " << __FILE__ << ":" << __LINE__ << endl; \
+ abort(); \
+}
+#else
+#define ASSERT(x)
+#endif
+
+template<class T>
+T smooth_min(T x, T y) {
+ T z = exp(x - y);
+ return 0.5 * (x + y - (x - y)/(1 + 1/z) - (y - x)/(1 + z));
+}
+
+template <class T>
+void write_var(ostream *os, const T *x) { os->write((char *) x, sizeof(T)); }
+
+template <class T>
+void read_var(istream *is, T *x) { is->read((char *) x, sizeof(T)); }
+
+template <class T>
+void grow(int *nb_max, int nb, T** current, int factor) {
+ ASSERT(*nb_max > 0);
+ if(nb == *nb_max) {
+ T *tmp = new T[*nb_max * factor];
+ memcpy(tmp, *current, *nb_max * sizeof(T));
+ delete[] *current;
+ *current = tmp;
+ *nb_max *= factor;
+ }
+}
+
+template <class T>
+inline T sq(T x) {
+ return x * x;
+}
+
+inline scalar_t log2(scalar_t x) {
+ return log(x)/log(2.0);
+}
+
+inline scalar_t xi(scalar_t x) {
+ if(x <= 0.0) return 0.0;
+ else return - x * log(x)/log(2.0);
+}
+
+scalar_t discrete_entropy(int *n, int nb);
+
+char *basename(char *name);
+
+char *next_word(char *buffer, char *r, int buffer_size);
+
+void random_permutation(int *val, int nb);
+void tag_subset(bool *val, int nb_total, int nb_to_tag);
+
+struct Couple {
+ int index;
+ scalar_t value;
+};
+
+int compare_couple(const void *a, const void *b);
+
+// size total program size
+// resident resident set size
+// share shared pages
+// text text (code)
+// lib library
+// data data/stack
+// dt dirty pages (unused in Linux 2.6)
+
+void used_memory(size_t &size, size_t &resident,
+ size_t &share, size_t &text, size_t &lib, size_t &data,
+ size_t &dt);
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+// All this is clearly non-optimal, loaded with news and deletes and
+// should be rewritten.
+
+#include <string.h>
+
+#include "param_parser.h"
+
+ParamParser::ParamParser() : _nb_max(10),
+ _nb(0),
+ _names(new char *[_nb_max]),
+ _values(new char *[_nb_max]),
+ _changed(new bool[_nb_max]) { }
+
+ParamParser::~ParamParser() {
+ for(int k = 0; k < _nb; k++) {
+ delete[] _names[k];
+ delete[] _values[k];
+ }
+ delete[] _names;
+ delete[] _values;
+ delete[] _changed;
+}
+
+void ParamParser::add_association(const char *variable_name, const char *variable_value, bool change) {
+ int n;
+
+ for(n = 0; n < _nb && strcmp(variable_name, _names[n]) != 0; n++);
+
+ if(n < _nb) {
+ delete[] _values[n];
+ _values[n] = new char[strlen(variable_value) + 1];
+ strcpy(_values[n], variable_value);
+ _changed[n] = change;
+ } else {
+ int nm;
+ nm = _nb_max; grow(&nm, _nb, &_names, 2);
+ nm = _nb_max; grow(&nm, _nb, &_values, 2);
+ grow(&_nb_max, _nb, &_changed, 2);
+
+ _names[_nb] = new char[strlen(variable_name) + 1];
+ strcpy(_names[_nb], variable_name);
+ _values[_nb] = new char[strlen(variable_value) + 1];
+ strcpy(_values[_nb], variable_value);
+ _changed[_nb] = change;
+ _nb++;
+ }
+}
+
+char *ParamParser::get_association(const char *variable_name) {
+ int n;
+ for(n = 0; n < _nb && strcmp(variable_name, _names[n]) != 0; n++);
+ if(n < _nb) return _values[n];
+ else {
+ cerr << "Unknown parameter \"" << variable_name << "\", existing ones are" << endl;
+ for(int n = 0; n < _nb; n++)
+ cerr << " \"" << _names[n] << "\"" << endl;
+ exit(1);
+ }
+}
+
+int ParamParser::get_association_int(const char *variable_name) {
+ char *u = get_association(variable_name);
+ char *s = u;
+ while(*s)
+ if((*s < '0' || *s > '9') && *s != '-') {
+ cerr << "Non-numerical value for " << variable_name << " (" << u << ")" << endl;
+ exit(1);
+ } else s++;
+ return atoi(u);
+}
+
+scalar_t ParamParser::get_association_scalar(const char *variable_name) {
+ char *u = get_association(variable_name);
+ char *s = u;
+ while(*s)
+ if((*s < '0' || *s > '9') && *s != '.' && *s != 'e' && *s != '-') {
+ cerr << "Non-numerical value for " << variable_name << " (" << u << ")" << endl;
+ exit(1);
+ } else s++;
+ return atof(u);
+}
+
+bool ParamParser::get_association_bool(const char *variable_name) {
+ char *value = get_association(variable_name);
+ if(strcasecmp(value, "") == 0 || strcasecmp(value, "y") == 0 || strcasecmp(value, "yes") == 0) return true;
+ if(strcasecmp(value, "n") == 0 || strcasecmp(value, "no") == 0) return false;
+ cerr << "Expects nothing (for yes), or y[es] or n[o] for a boolean argument and got '" << value << "'" << endl;
+ exit(1);
+}
+
+void ParamParser::parse_options(int argc, char **argv,
+ bool allow_undefined,
+ int *new_argc, char **new_argv) {
+
+ int i = 1;
+
+ if(new_argc && new_argv)
+ new_argv[(*new_argc)++] = argv[0];
+
+ while(i < argc) {
+ if(strncmp(argv[i], "--", 2) == 0) {
+ // This is so 70s! I luuuuv it!
+ char variable_name[buffer_size] = "", variable_value[buffer_size] = "";
+ char *o = argv[i] + 2, *s = variable_name, *u = variable_value;
+ while(*o && *o != '=') *s++ = *o++;
+ *s = '\0';
+ if(*o) { o++; while(*o) *u++ = *o++; }
+ *u = '\0';
+ if(!allow_undefined) get_association(variable_name);
+ add_association(variable_name, variable_value, true);
+ } else {
+ if(new_argc && new_argv)
+ new_argv[(*new_argc)++] = argv[i];
+ else {
+ cerr << "Can not parse " << argv[i] << endl;
+ exit(1);
+ }
+ }
+ i++;
+ }
+}
+
+void ParamParser::print_all(ostream *os) {
+ for(int n = 0; n < _nb; n++) {
+ (*os) << (_changed[n] ? " * " : " ") << "\"" << _names[n] << "\" \"" << _values[n] << "\"" << endl;
+ }
+}
+
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef PARAM_PARSER_H
+#define PARAM_PARSER_H
+
+#include <iostream>
+#include "misc.h"
+
+using namespace std;
+
+class ParamParser {
+ int _nb_max, _nb;
+ char **_names, **_values;
+ bool *_changed;
+public:
+ ParamParser();
+ ~ParamParser();
+ void add_association(const char *variable_name, const char *variable_value, bool change);
+ char *get_association(const char *variable_name);
+ int get_association_int(const char *variable_name);
+ scalar_t get_association_scalar(const char *variable_name);
+ bool get_association_bool(const char *variable_name);
+
+ void parse_options(int argc, char **argv, bool allow_undefined, int *new_argc, char **new_argv);
+ void print_all(ostream *os);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "parsing.h"
+#include "fusion_sort.h"
+
+Parsing::Parsing(LabelledImagePool *image_pool,
+ PoseCellHierarchy *hierarchy,
+ scalar_t proportion_negative_cells,
+ int image_index) {
+
+ _image_pool = image_pool;
+ _image_index = image_index;
+
+ PoseCellSet cell_set;
+ LabelledImage *image;
+
+ image = _image_pool->grab_image(_image_index);
+
+ hierarchy->add_root_cells(image, &cell_set);
+
+ int *kept = new int[cell_set.nb_cells()];
+
+ _nb_cells = 0;
+
+ for(int c = 0; c < cell_set.nb_cells(); c++) {
+ int l = image->pose_cell_label(cell_set.get_cell(c));
+ kept[c] = (l > 0) || (l < 0 && drand48() < proportion_negative_cells);
+ if(kept[c]) _nb_cells++;
+ }
+
+ _cells = new PoseCell[_nb_cells];
+ _responses = new scalar_t[_nb_cells];
+ _labels = new int[_nb_cells];
+ _nb_positives = 0;
+ _nb_negatives = 0;
+
+ int d = 0;
+ for(int c = 0; c < cell_set.nb_cells(); c++) {
+ if(kept[c]) {
+ _cells[d] = *(cell_set.get_cell(c));
+ _labels[d] = image->pose_cell_label(&_cells[d]);
+ _responses[d] = 0;
+ if(_labels[d] < 0) {
+ _nb_negatives++;
+ } else if(_labels[d] > 0) {
+ _nb_positives++;
+ }
+ d++;
+ }
+ }
+
+ delete[] kept;
+
+ _image_pool->release_image(_image_index);
+}
+
+Parsing::~Parsing() {
+ delete[] _cells;
+ delete[] _responses;
+ delete[] _labels;
+}
+
+void Parsing::down_one_level(PoseCellHierarchy *hierarchy,
+ int level, int *sample_nb_occurences, scalar_t *sample_responses) {
+ PoseCellSet cell_set;
+ LabelledImage *image;
+
+ int new_nb_cells = 0;
+ for(int c = 0; c < _nb_cells; c++) {
+ new_nb_cells += sample_nb_occurences[c];
+ }
+
+ PoseCell *new_cells = new PoseCell[new_nb_cells];
+ scalar_t *new_responses = new scalar_t[new_nb_cells];
+ int *new_labels = new int[new_nb_cells];
+
+ image = _image_pool->grab_image(_image_index);
+ int b = 0;
+
+ for(int c = 0; c < _nb_cells; c++) {
+
+ if(sample_nb_occurences[c] > 0) {
+
+ cell_set.erase_content();
+ hierarchy->add_subcells(level, _cells + c, &cell_set);
+
+ if(_labels[c] > 0) {
+ ASSERT(sample_nb_occurences[c] == 1);
+ int e = -1;
+ for(int d = 0; d < cell_set.nb_cells(); d++) {
+ if(image->pose_cell_label(cell_set.get_cell(d)) > 0) {
+ ASSERT(e < 0);
+ e = d;
+ }
+ }
+ ASSERT(e >= 0);
+ ASSERT(b < new_nb_cells);
+ new_cells[b] = *(cell_set.get_cell(e));
+ new_responses[b] = sample_responses[c];
+ new_labels[b] = 1;
+ b++;
+ }
+
+ else if(_labels[c] < 0) {
+ for(int d = 0; d < sample_nb_occurences[c]; d++) {
+ ASSERT(b < new_nb_cells);
+ new_cells[b] = *(cell_set.get_cell(int(drand48() * cell_set.nb_cells())));
+ new_responses[b] = sample_responses[c];
+ new_labels[b] = -1;
+ b++;
+ }
+ }
+
+ else {
+ cerr << "INCONSISTENCY" << endl;
+ abort();
+ }
+ }
+ }
+
+ ASSERT(b == new_nb_cells);
+
+ _image_pool->release_image(_image_index);
+
+ delete[] _cells;
+ delete[] _labels;
+ delete[] _responses;
+ _nb_cells = new_nb_cells;
+ _cells = new_cells;
+ _labels = new_labels;
+ _responses = new_responses;
+}
+
+void Parsing::update_cell_responses(PiFeatureFamily *pi_feature_family,
+ Classifier *classifier) {
+ LabelledImage *image;
+
+ image = _image_pool->grab_image(_image_index);
+ image->compute_rich_structure();
+
+ SampleSet *samples = new SampleSet(pi_feature_family->nb_features(), 1);
+
+ for(int c = 0; c < _nb_cells; c++) {
+ samples->set_sample(0, pi_feature_family, image, &_cells[c], 0);
+ _responses[c] += classifier->response(samples, 0);
+ ASSERT(!isnan(_responses[c]));
+ }
+
+ _image_pool->release_image(_image_index);
+ delete samples;
+}
+
+void Parsing::collect_samples(SampleSet *samples,
+ PiFeatureFamily *pi_feature_family,
+ int s,
+ int *to_collect) {
+ LabelledImage *image;
+
+ image = _image_pool->grab_image(_image_index);
+ image->compute_rich_structure();
+
+ for(int c = 0; c < _nb_cells; c++) {
+ if(to_collect[c]) {
+ samples->set_sample(s, pi_feature_family, image, &_cells[c], _labels[c]);
+ s++;
+ }
+ }
+
+ _image_pool->release_image(_image_index);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef PARSING_H
+#define PARSING_H
+
+/*
+
+ A Parsing is associated to a LabelledImage and stores responses over
+ cells.
+
+*/
+
+#include "fusion_sort.h"
+#include "pose_cell_hierarchy.h"
+#include "classifier.h"
+#include "labelled_image.h"
+
+class Parsing {
+ LabelledImagePool *_image_pool;
+ int _image_index;
+ int _nb_cells, _nb_positives, _nb_negatives;
+
+ PoseCell *_cells;
+ scalar_t *_responses;
+ int *_labels;
+
+public:
+
+ Parsing(LabelledImagePool *image_pool,
+ PoseCellHierarchy *hierarchy,
+ scalar_t proportion_negative_cells,
+ int image_index);
+
+ ~Parsing();
+
+ //////////////////////////////////////////////////////////////////////
+
+ inline int nb_cells() {
+ return _nb_cells;
+ }
+
+ inline int nb_positive_cells() {
+ return _nb_positives;
+ }
+
+ inline int nb_negative_cells() {
+ return _nb_negatives;
+ }
+
+ inline scalar_t response(int c) {
+ ASSERT(c >= 0 && c < _nb_cells);
+ return _responses[c];
+ }
+
+ inline int label(int c) {
+ ASSERT(c >= 0 && c < _nb_cells);
+ return _labels[c];
+ }
+
+ void down_one_level(PoseCellHierarchy *hierarchy, int level, int *sample_nb_occurences, scalar_t *sample_responses);
+
+ void update_cell_responses(PiFeatureFamily *pi_feature_family,
+ Classifier *classifier);
+
+ void collect_samples(SampleSet *samples,
+ PiFeatureFamily *pi_feature_family,
+ int s,
+ int *to_collect);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "parsing_pool.h"
+#include "tools.h"
+
+ParsingPool::ParsingPool(LabelledImagePool *image_pool, PoseCellHierarchy *hierarchy, scalar_t proportion_negative_cells) {
+ _nb_images = image_pool->nb_images();
+ _parsings = new Parsing *[_nb_images];
+
+ _nb_cells = 0;
+ _nb_positive_cells = 0;
+ _nb_negative_cells = 0;
+ for(int i = 0; i < _nb_images; i++) {
+ _parsings[i] = new Parsing(image_pool, hierarchy, proportion_negative_cells, i);
+ _nb_cells += _parsings[i]->nb_cells();
+ _nb_positive_cells += _parsings[i]->nb_positive_cells();
+ _nb_negative_cells += _parsings[i]->nb_negative_cells();
+ }
+ (*global.log_stream) << "ParsingPool initialized" << endl;
+ (*global.log_stream) << " _nb_cells = " << _nb_cells << endl;
+ (*global.log_stream) << " _nb_positive_cells = " << _nb_positive_cells << endl;
+ (*global.log_stream) << " _nb_negative_cells = " << _nb_negative_cells << endl;
+}
+
+ParsingPool::~ParsingPool() {
+ for(int i = 0; i < _nb_images; i++)
+ delete _parsings[i];
+ delete[] _parsings;
+}
+
+void ParsingPool::down_one_level(LossMachine *loss_machine, PoseCellHierarchy *hierarchy, int level) {
+ scalar_t *labels = new scalar_t[_nb_cells];
+ scalar_t *tmp_responses = new scalar_t[_nb_cells];
+
+ int c;
+
+ { ////////////////////////////////////////////////////////////////////
+ // Sanity check
+ scalar_t l = 0;
+ for(int i = 0; i < _nb_images; i++) {
+ for(int d = 0; d < _parsings[i]->nb_cells(); d++) {
+ if(_parsings[i]->label(d) != 0) {
+ l += exp( - _parsings[i]->label(d) * _parsings[i]->response(d));
+ }
+ }
+ }
+ (*global.log_stream) << "* INITIAL LOSS IS " << l << endl;
+ } ////////////////////////////////////////////////////////////////////
+
+ // Put the negative samples with their current responses, and all
+ // others to 0
+
+ c = 0;
+ for(int i = 0; i < _nb_images; i++) {
+ for(int d = 0; d < _parsings[i]->nb_cells(); d++) {
+ if(_parsings[i]->label(d) < 0) {
+ labels[c] = -1;
+ tmp_responses[c] = _parsings[i]->response(d);
+ } else {
+ labels[c] = 0;
+ tmp_responses[c] = 0;
+ }
+ c++;
+ }
+ }
+
+ // Sub-sample among the negative ones
+
+ int *sample_nb_occurences = new int[_nb_cells];
+ scalar_t *sample_responses = new scalar_t[_nb_cells];
+
+ loss_machine->subsample(_nb_cells, labels, tmp_responses,
+ _nb_negative_cells, sample_nb_occurences, sample_responses,
+ 1);
+ c = 0;
+ for(int i = 0; i < _nb_images; i++) {
+ for(int d = 0; d < _parsings[i]->nb_cells(); d++) {
+ if(_parsings[i]->label(d) > 0) {
+ sample_nb_occurences[c + d] = 1;
+ sample_responses[c + d] = _parsings[i]->response(d);
+ }
+ }
+
+ int d = c + _parsings[i]->nb_cells();
+
+ _parsings[i]->down_one_level(hierarchy, level, sample_nb_occurences + c, sample_responses + c);
+
+ c = d;
+ }
+
+ { ////////////////////////////////////////////////////////////////////
+ // Sanity check
+ scalar_t l = 0;
+ for(int i = 0; i < _nb_images; i++) {
+ for(int d = 0; d < _parsings[i]->nb_cells(); d++) {
+ if(_parsings[i]->label(d) != 0) {
+ l += exp( - _parsings[i]->label(d) * _parsings[i]->response(d));
+ }
+ }
+ }
+ (*global.log_stream) << "* FINAL LOSS IS " << l << endl;
+ } ////////////////////////////////////////////////////////////////////
+
+ delete[] sample_responses;
+ delete[] sample_nb_occurences;
+}
+
+void ParsingPool::update_cell_responses(PiFeatureFamily *pi_feature_family,
+ Classifier *classifier) {
+ for(int i = 0; i < _nb_images; i++) {
+ _parsings[i]->update_cell_responses(pi_feature_family, classifier);
+ }
+}
+
+void ParsingPool::weighted_sampling(LossMachine *loss_machine,
+ PiFeatureFamily *pi_feature_family,
+ SampleSet *sample_set,
+ scalar_t *responses) {
+
+ int nb_negatives_to_sample = sample_set->nb_samples() - _nb_positive_cells;
+
+ ASSERT(nb_negatives_to_sample > 0);
+
+ scalar_t *labels = new scalar_t[_nb_cells];
+ scalar_t *tmp_responses = new scalar_t[_nb_cells];
+
+ int c, s;
+
+ // Put the negative samples with their current responses, and all
+ // others to 0
+
+ c = 0;
+ for(int i = 0; i < _nb_images; i++) {
+ for(int d = 0; d < _parsings[i]->nb_cells(); d++) {
+ if(_parsings[i]->label(d) < 0) {
+ labels[c] = -1;
+ tmp_responses[c] = _parsings[i]->response(d);
+ } else {
+ labels[c] = 0;
+ tmp_responses[c] = 0;
+ }
+ c++;
+ }
+ }
+
+ // Sub-sample among the negative ones
+
+ int *sample_nb_occurences = new int[_nb_cells];
+ scalar_t *sample_responses = new scalar_t[_nb_cells];
+
+ loss_machine->subsample(_nb_cells, labels, tmp_responses,
+ nb_negatives_to_sample, sample_nb_occurences, sample_responses,
+ 0);
+
+ for(int k = 0; k < _nb_cells; k++) {
+ if(sample_nb_occurences[k] > 0) {
+ ASSERT(sample_nb_occurences[k] == 1);
+ labels[k] = -1.0;
+ tmp_responses[k] = sample_responses[k];
+ } else {
+ labels[k] = 0;
+ }
+ }
+
+ delete[] sample_responses;
+ delete[] sample_nb_occurences;
+
+ // Put the positive ones
+
+ c = 0;
+ for(int i = 0; i < _nb_images; i++) {
+ for(int d = 0; d < _parsings[i]->nb_cells(); d++) {
+ if(_parsings[i]->label(d) > 0) {
+ labels[c] = 1;
+ tmp_responses[c] = _parsings[i]->response(d);
+ }
+ c++;
+ }
+ }
+
+ // Here we have the responses for the sub-sampled in tmp_responses,
+ // and we have labels[n] set to zero for non-sampled samples
+
+ s = 0;
+ c = 0;
+
+// global.bar.init(&cout, _nb_images);
+
+ for(int i = 0; i < _nb_images; i++) {
+
+ int *to_collect = new int[_parsings[i]->nb_cells()];
+
+ for(int d = 0; d < _parsings[i]->nb_cells(); d++) {
+ to_collect[d] = (labels[c + d] != 0);
+ }
+
+ _parsings[i]->collect_samples(sample_set, pi_feature_family, s, to_collect);
+
+ for(int d = 0; d < _parsings[i]->nb_cells(); d++) {
+ if(to_collect[d]) {
+ responses[s++] = tmp_responses[c + d];
+ }
+ }
+
+ delete[] to_collect;
+
+ c += _parsings[i]->nb_cells();
+
+// global.bar.refresh(&cout, i);
+ }
+
+// global.bar.finish(&cout);
+
+ delete[] tmp_responses;
+ delete[] labels;
+}
+
+void ParsingPool::write_roc(ofstream *out) {
+ int nb_negatives = nb_negative_cells();
+ int nb_positives = nb_positive_cells();
+
+ scalar_t *pos_responses = new scalar_t[nb_positives];
+ scalar_t *neg_responses = new scalar_t[nb_negatives];
+ int np = 0, nn = 0;
+ for(int i = 0; i < _nb_images; i++) {
+ for(int c = 0; c < _parsings[i]->nb_cells(); c++) {
+ if(_parsings[i]->label(c) > 0)
+ pos_responses[np++] = _parsings[i]->response(c);
+ else if(_parsings[i]->label(c) < 0)
+ neg_responses[nn++] = _parsings[i]->response(c);
+ }
+ }
+
+ ASSERT(nn == nb_negatives && np == nb_positives);
+
+ print_roc_small_pos(out,
+ nb_positives, pos_responses,
+ nb_negatives, neg_responses,
+ 1.0);
+
+ delete[] pos_responses;
+ delete[] neg_responses;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef PARSING_POOL_H
+#define PARSING_POOL_H
+
+/*
+
+ A ParsingPool is a family of Parsing associated to all the images of
+ a LabelledImagePool.
+
+*/
+
+#include "parsing.h"
+#include "pi_feature_family.h"
+#include "classifier.h"
+#include "labelled_image_pool.h"
+#include "pose_cell_hierarchy.h"
+
+class ParsingPool {
+ int _nb_images;
+ long int _nb_cells, _nb_positive_cells, _nb_negative_cells;
+ Parsing **_parsings;
+
+public:
+
+ inline int nb_cells() {
+ return _nb_cells;
+ }
+
+ inline int nb_positive_cells() {
+ return _nb_positive_cells;
+ }
+
+ inline int nb_negative_cells() {
+ return _nb_negative_cells;
+ }
+
+ ParsingPool(LabelledImagePool *image_pool, PoseCellHierarchy *hierarchy, scalar_t proportion_negative_cells);
+
+ ~ParsingPool();
+
+ // The parameter level is the resulting level, not the starting one,
+ // hence should always be strictly positive
+ void down_one_level(LossMachine *loss_machine, PoseCellHierarchy *hierarchy, int level);
+
+ void update_cell_responses(PiFeatureFamily *pi_feature_family,
+ Classifier *classifier);
+
+ // Collect all positive and sample negative examples
+ void weighted_sampling(LossMachine *loss_machine,
+ PiFeatureFamily *pi_feature_family,
+ SampleSet *sample_set,
+ scalar_t *responses);
+
+ void write_roc(ofstream *out);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "pi_feature.h"
+
+#include "global.h"
+#include "rectangle.h"
+
+int PiFeature::random_registration_mode(int level) {
+ while(1) {
+ switch(int(6 * drand48())) {
+
+ case 0:
+ return PiReferential::RM_HEAD;
+ break;
+
+ case 1:
+ if(level >= 1)
+ return PiReferential::RM_BELLY;
+ break;
+
+ case 2:
+ if(level >= 1)
+ return PiReferential::RM_HEAD_BELLY;
+ break;
+
+ case 3:
+ if(level >= 1)
+ return PiReferential::RM_HEAD_BELLY_EDGES;
+ break;
+
+ case 4:
+ if(level >= 2)
+ return PiReferential::RM_BODY;
+ break;
+
+ case 5:
+ if(level >= 2)
+ return PiReferential::RM_BODY_EDGES;
+ break;
+
+ default:
+ abort();
+ }
+ }
+}
+
+void PiFeature::randomize_window(int registration_mode, Rectangle *window) {
+ scalar_t xc, yc, w, h;
+
+ do {
+ xc = 2 * drand48() - 1.0;
+ yc = 2 * drand48() - 1.0;
+
+ w = 2 * drand48();
+
+ // If we are in a non-rotating frame, allow rectangular window,
+ // otherwise force it to be squared
+
+ if(registration_mode == PiReferential::RM_HEAD ||
+ registration_mode == PiReferential::RM_BELLY ||
+ registration_mode == PiReferential::RM_HEAD_NO_POLARITY ||
+ registration_mode == PiReferential::RM_BELLY_NO_POLARITY) {
+ h = 2 * drand48();
+ } else {
+ h = w;
+ }
+ } while(w < global.pi_feature_window_min_size ||
+ h < global.pi_feature_window_min_size ||
+ xc - w/2 < -1.0 || xc + w/2 > 1.0 ||
+ yc - h/2 < -1.0 || yc + h/2 > 1.0);
+
+ window->xmin = xc - w/2;
+ window->ymin = yc - h/2;
+ window->xmax = xc + w/2;
+ window->ymax = yc + h/2;
+}
+
+//////////////////////////////////////////////////////////////////////
+// PF_EDGE_THRESHOLDING
+
+scalar_t PiFeature::response_edge_thresholding(RichImage *image,
+ PiReferential *referential) {
+ Rectangle registered_window_a;
+
+ referential->register_rectangle(_registration_a,
+ &_window_a,
+ ®istered_window_a);
+
+ int tag = referential->register_edge(_registration_a, _tag);
+
+ int xmin_a = int(registered_window_a.xmin) >> _edge_scale;
+ int ymin_a = int(registered_window_a.ymin) >> _edge_scale;
+ int xmax_a = int(registered_window_a.xmax) >> _edge_scale;
+ int ymax_a = int(registered_window_a.ymax) >> _edge_scale;
+
+ int scale = referential->common_scale() + _edge_scale * global.nb_scales_per_power_of_two;
+
+ scalar_t ne, nt;
+
+ ASSERT((tag >= RichImage::first_edge_tag &&
+ tag < RichImage::first_edge_tag + RichImage::nb_edge_tags) ||
+ tag == RichImage::variance_tag);
+
+ if(tag != RichImage::variance_tag) {
+ ne = scalar_t(image->nb_tags_in_window(scale, tag,
+ xmin_a, ymin_a, xmax_a, ymax_a));
+ nt = scalar_t(image->nb_tags_in_window(scale, RichImage::variance_tag,
+ xmin_a, ymin_a, xmax_a, ymax_a) + 1);
+ } else {
+ ne = scalar_t(image->nb_tags_in_window(scale, RichImage::variance_tag,
+ xmin_a, ymin_a, xmax_a, ymax_a));
+ nt = scalar_t((xmax_a - xmin_a) * (ymax_a - ymin_a)) + 1;
+ }
+
+ return ne / nt;
+}
+
+void PiFeature::draw_edge_thresholding(RGBImage *image,
+ int r, int g, int b,
+ PiReferential *referential) {
+
+ (*global.log_stream) << "draw_edge_thresholding" << endl;
+
+ Rectangle registered_window_a;
+
+ referential->register_rectangle(_registration_a,
+ &_window_a,
+ ®istered_window_a);
+
+ referential->draw_window(image, _registration_a, ®istered_window_a, 0);
+
+// if(!global.pictures_for_article) {
+// int tag = referential->register_edge(_registration_a, _tag);
+
+// referential->draw_edge_and_scale(image, _registration_a, ®istered_window_a,
+// tag, _edge_scale);
+// }
+}
+
+void PiFeature::print_edge_thresholding(ostream *os) {
+ (*os) << "_tag " << _tag << endl;
+ (*os) << "_edge_scale " << _edge_scale << endl;
+ (*os) << "_window_a.xmin " << _window_a.xmin << endl;
+ (*os) << "_window_a.ymin " << _window_a.ymin << endl;
+ (*os) << "_window_a.xmax " << _window_a.xmax << endl;
+ (*os) << "_window_a.ymax " << _window_a.ymax << endl;
+}
+
+//////////////////////////////////////////////////////////////////////
+// PF_EDGE_HISTOGRAM_COMPARISON
+
+scalar_t PiFeature::response_edge_histogram_comparison(RichImage *image,
+ PiReferential *referential) {
+
+ Rectangle registered_window_a;
+
+ referential->register_rectangle(_registration_a,
+ &_window_a,
+ ®istered_window_a);
+
+ int xmin_a = int(registered_window_a.xmin) >> _edge_scale;
+ int ymin_a = int(registered_window_a.ymin) >> _edge_scale;
+ int xmax_a = int(registered_window_a.xmax) >> _edge_scale;
+ int ymax_a = int(registered_window_a.ymax) >> _edge_scale;
+
+ Rectangle registered_window_b;
+
+ referential->register_rectangle(_registration_b,
+ &_window_b,
+ ®istered_window_b);
+
+ int xmin_b = int(registered_window_b.xmin) >> _edge_scale;
+ int ymin_b = int(registered_window_b.ymin) >> _edge_scale;
+ int xmax_b = int(registered_window_b.xmax) >> _edge_scale;
+ int ymax_b = int(registered_window_b.ymax) >> _edge_scale;
+
+ int scale = referential->common_scale() + _edge_scale * global.nb_scales_per_power_of_two;
+
+ scalar_t result = 0.0;
+
+ scalar_t ne_a = scalar_t(image->nb_tags_in_window(scale, RichImage::variance_tag,
+ xmin_a, ymin_a,
+ xmax_a, ymax_a) + 1);
+
+ scalar_t ne_b = scalar_t(image->nb_tags_in_window(scale, RichImage::variance_tag,
+ xmin_b, ymin_b,
+ xmax_b, ymax_b) + 1);
+
+ for(int t = RichImage::first_edge_tag; t < RichImage::first_edge_tag + RichImage::nb_edge_tags; t++)
+ result += sq(scalar_t(image->nb_tags_in_window(scale, t,
+ xmin_a, ymin_a,
+ xmax_a, ymax_a)) / ne_a
+ -
+ scalar_t(image->nb_tags_in_window(scale, t,
+ xmin_b, ymin_b,
+ xmax_b, ymax_b)) / ne_b);
+
+ ASSERT(!isnan(result));
+
+ return result;
+}
+
+void PiFeature::draw_edge_histogram_comparison(RGBImage *image,
+ int r, int g, int b,
+ PiReferential *referential) {
+
+ (*global.log_stream) << "draw_edge_histogram_comparison" << endl;
+
+ Rectangle registered_window;
+ {
+
+ referential->register_rectangle(_registration_a,
+ &_window_a,
+ ®istered_window);
+ referential->draw_window(image, _registration_a, ®istered_window, 0);
+ }
+
+ {
+ referential->register_rectangle(_registration_b,
+ &_window_b,
+ ®istered_window);
+
+ referential->draw_window(image, _registration_b, ®istered_window, 0);
+ }
+}
+
+void PiFeature::print_edge_histogram_comparison(ostream *os) {
+ (*os) << "_edge_scale " << _edge_scale << endl;
+ (*os) << "_window_a.xmin " << _window_a.xmin << endl;
+ (*os) << "_window_a.ymin " << _window_a.ymin << endl;
+ (*os) << "_window_a.xmax " << _window_a.xmax << endl;
+ (*os) << "_window_a.ymax " << _window_a.ymax << endl;
+ (*os) << "_window_b.xmin " << _window_a.xmin << endl;
+ (*os) << "_window_b.ymin " << _window_a.ymin << endl;
+ (*os) << "_window_b.xmax " << _window_a.xmax << endl;
+ (*os) << "_window_b.ymax " << _window_a.ymax << endl;
+}
+
+//////////////////////////////////////////////////////////////////////
+// PF_GRAYSCALE_HISTOGRAM_COMPARISON
+
+scalar_t PiFeature::response_grayscale_histogram_comparison(RichImage *image,
+ PiReferential *referential) {
+ Rectangle registered_window_a;
+
+ referential->register_rectangle(_registration_a,
+ &_window_a,
+ ®istered_window_a);
+
+ int xmin_a = int(registered_window_a.xmin);
+ int ymin_a = int(registered_window_a.ymin);
+ int xmax_a = int(registered_window_a.xmax);
+ int ymax_a = int(registered_window_a.ymax);
+
+ Rectangle registered_window_b;
+
+ referential->register_rectangle(_registration_b,
+ &_window_b,
+ ®istered_window_b);
+
+ int xmin_b = int(registered_window_b.xmin);
+ int ymin_b = int(registered_window_b.ymin);
+ int xmax_b = int(registered_window_b.xmax);
+ int ymax_b = int(registered_window_b.ymax);
+
+ int scale = referential->common_scale();
+
+ scalar_t result = 0.0;
+
+ scalar_t ne_a = scalar_t((xmax_a - xmin_a) * (ymax_a - ymin_a));
+ scalar_t ne_b = scalar_t((xmax_b - xmin_b) * (ymax_b - ymin_b));
+
+ for(int t = RichImage::first_gray_tag; t < RichImage::first_gray_tag + RichImage::nb_gray_tags; t++)
+ result += sq(scalar_t(image->nb_tags_in_window(scale, t,
+ xmin_a, ymin_a,
+ xmax_a, ymax_a))/ne_a
+ -
+ scalar_t(image->nb_tags_in_window(scale, t,
+ xmin_b, ymin_b,
+ xmax_b, ymax_b))/ne_b);
+ ASSERT(!isnan(result));
+
+ return result;
+}
+
+void PiFeature::draw_grayscale_histogram_comparison(RGBImage *image,
+ int r, int g, int b,
+ PiReferential *referential) {
+
+ (*global.log_stream) << "draw_grayscale_histogram_comparison" << endl;
+
+ Rectangle registered_window;
+ {
+
+ referential->register_rectangle(_registration_a,
+ &_window_a,
+ ®istered_window);
+
+ referential->draw_window(image, _registration_a, ®istered_window, 0);
+ }
+
+ {
+ referential->register_rectangle(_registration_b,
+ &_window_b,
+ ®istered_window);
+
+ referential->draw_window(image, _registration_b, ®istered_window, 0);
+ }
+}
+
+void PiFeature::print_grayscale_histogram_comparison(ostream *os) {
+ (*os) << "_window_a.xmin " << _window_a.xmin << endl;
+ (*os) << "_window_a.ymin " << _window_a.ymin << endl;
+ (*os) << "_window_a.xmax " << _window_a.xmax << endl;
+ (*os) << "_window_a.ymax " << _window_a.ymax << endl;
+ (*os) << "_window_b.xmin " << _window_a.xmin << endl;
+ (*os) << "_window_b.ymin " << _window_a.ymin << endl;
+ (*os) << "_window_b.xmax " << _window_a.xmax << endl;
+ (*os) << "_window_b.ymax " << _window_a.ymax << endl;
+}
+
+//////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////
+
+void PiFeature::randomize(int level) {
+
+ // We randomize all parameters, even those which will not be used
+ // due to the feature type
+
+ _tag = int(drand48() * (RichImage::nb_edge_tags + 1));
+
+ if(_tag < RichImage::nb_edge_tags)
+ _tag += RichImage::first_edge_tag;
+ else
+ _tag = RichImage::variance_tag;
+
+ _edge_scale = int(drand48() * 3);
+
+ // Windows can not be defined in different frames unless we allow
+ // head-belly registration
+
+ if(global.force_head_belly_independence) {
+ if(level == 0) {
+ _registration_a = PiReferential::RM_HEAD_NO_POLARITY;
+ _registration_b = PiReferential::RM_HEAD_NO_POLARITY;
+ } else if(level == 1) {
+ _registration_a = PiReferential::RM_BELLY_NO_POLARITY;
+ _registration_b = PiReferential::RM_BELLY_NO_POLARITY;
+ } else {
+ abort();
+ }
+ } else {
+ _registration_a = random_registration_mode(level);
+ _registration_b = random_registration_mode(level);
+ }
+
+ randomize_window(_registration_a, &_window_a);
+ randomize_window(_registration_b, &_window_b);
+
+ switch(int(drand48() * 3)) {
+
+ case 0:
+ _type = PF_EDGE_THRESHOLDING;
+ break;
+
+ case 1:
+ _type = PF_EDGE_HISTOGRAM_COMPARISON;
+ break;
+
+ case 2:
+ _type = PF_GRAYSCALE_HISTOGRAM_COMPARISON;
+ break;
+
+ default:
+ abort();
+ }
+}
+
+scalar_t PiFeature::response(RichImage *image, PiReferential *referential) {
+ scalar_t r;
+
+ switch(_type) {
+
+ case PF_EDGE_THRESHOLDING:
+ r = response_edge_thresholding(image, referential);
+ break;
+
+ case PF_EDGE_HISTOGRAM_COMPARISON:
+ r = response_edge_histogram_comparison(image, referential);
+ break;
+
+ case PF_GRAYSCALE_HISTOGRAM_COMPARISON:
+ r = response_grayscale_histogram_comparison(image, referential);
+ break;
+
+ default:
+ abort();
+ }
+
+ ASSERT(!isnan(r));
+
+ return r;
+};
+
+void PiFeature::draw(RGBImage *image,
+ int r, int g, int b, PiReferential *referential) {
+
+ switch(_type) {
+
+ case PF_EDGE_THRESHOLDING:
+ draw_edge_thresholding(image, r, g, b, referential);
+ break;
+
+ case PF_EDGE_HISTOGRAM_COMPARISON:
+ draw_edge_histogram_comparison(image, r, g, b, referential);
+ break;
+
+ case PF_GRAYSCALE_HISTOGRAM_COMPARISON:
+ draw_grayscale_histogram_comparison(image, r, g, b, referential);
+ break;
+
+ default:
+ abort();
+ }
+}
+
+void PiFeature::print(ostream *os) {
+
+ (*os) << "registration_a ";
+ PiReferential::print_registration_mode(os, _registration_a);
+ (*os) << endl;
+
+ (*os) << "registration_b ";
+ PiReferential::print_registration_mode(os, _registration_b);
+ (*os) << endl;
+
+ switch(_type) {
+
+ case PF_EDGE_THRESHOLDING:
+ print_edge_thresholding(os);
+ break;
+
+ case PF_EDGE_HISTOGRAM_COMPARISON:
+ print_edge_histogram_comparison(os);
+ break;
+
+ case PF_GRAYSCALE_HISTOGRAM_COMPARISON:
+ print_grayscale_histogram_comparison(os);
+ break;
+
+ default:
+ abort();
+ }
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+
+ This class implement the notion of pi-feature, that is a feature
+ which can be evaluated on a pair image / referential, where the
+ referential is computed from a pose cell.
+
+*/
+
+#ifndef PI_FEATURE_H
+#define PI_FEATURE_H
+
+#include "misc.h"
+#include "global.h"
+#include "rich_image.h"
+#include "pi_referential.h"
+
+class PiFeature {
+
+ enum {
+ PF_EDGE_THRESHOLDING,
+ PF_EDGE_HISTOGRAM_COMPARISON,
+ PF_GRAYSCALE_HISTOGRAM_COMPARISON,
+ } _type;
+
+ int _tag, _edge_scale;
+ Rectangle _window_a, _window_b;
+ int _registration_a, _registration_b;
+
+ int random_registration_mode(int level);
+ void randomize_window(int registration_mode, Rectangle *window);
+ void draw_window(RGBImage *image, int registration_mode, Rectangle *window);
+
+ // EDGE THRESHOLDING
+
+ scalar_t response_edge_thresholding(RichImage *image, PiReferential *referential);
+ void draw_edge_thresholding(RGBImage *image, int r, int g, int b,
+ PiReferential *referential);
+ void print_edge_thresholding(ostream *os);
+
+ // EDGE ORIENTATION HISTOGRAM COMPARISON
+
+ scalar_t response_edge_histogram_comparison(RichImage *image, PiReferential *referential);
+ void draw_edge_histogram_comparison(RGBImage *image, int r, int g, int b,
+ PiReferential *referential);
+ void print_edge_histogram_comparison(ostream *os);
+
+ // GRAYSCALE HISTOGRAM COMPARISON
+
+ scalar_t response_grayscale_histogram_comparison(RichImage *image, PiReferential *referential);
+ void draw_grayscale_histogram_comparison(RGBImage *image,
+ int r, int g, int b,
+ PiReferential *referential);
+ void print_grayscale_histogram_comparison(ostream *os);
+
+public:
+
+ void randomize(int level);
+
+ scalar_t response(RichImage *image, PiReferential *referential);
+
+ void draw(RGBImage *image,
+ int r, int g, int b,
+ PiReferential *referential);
+
+ void print(ostream *os);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "pi_feature_family.h"
+
+PiFeatureFamily::PiFeatureFamily() {
+ _nb_features = 0;
+ _pi_features = 0;
+}
+
+PiFeatureFamily::~PiFeatureFamily() {
+ delete[] _pi_features;
+}
+
+void PiFeatureFamily::read(istream *is) {
+ delete[] _pi_features;
+ read_var(is, &_nb_features);
+ _pi_features = new PiFeature[_nb_features];
+ is->read((char *) _pi_features, sizeof(PiFeature) * _nb_features);
+}
+
+void PiFeatureFamily::write(ostream *os) {
+ write_var(os, &_nb_features);
+ os->write((char *) _pi_features, sizeof(PiFeature) * _nb_features);
+}
+
+void PiFeatureFamily::resize(int nb_features) {
+ delete[] _pi_features;
+ _nb_features = nb_features;
+ _pi_features = new PiFeature[_nb_features];
+}
+
+void PiFeatureFamily::randomize(int level) {
+ for(int f = 0; f < _nb_features; f++) _pi_features[f].randomize(level);
+}
+
+void PiFeatureFamily::extract(PiFeatureFamily *pi_feature_family,
+ bool *used_features, int *new_feature_indexes) {
+ delete[] _pi_features;
+ _nb_features = 0;
+
+ for(int f = 0; f < pi_feature_family->nb_features(); f++)
+ if(used_features[f]) _nb_features++;
+
+ _pi_features = new PiFeature[_nb_features];
+
+ int g = 0;
+
+ for(int f = 0; f < pi_feature_family->nb_features(); f++)
+ if(used_features[f]) {
+ _pi_features[g] = pi_feature_family->_pi_features[f];
+ new_feature_indexes[f] = g;
+ g++;
+ } else new_feature_indexes[f] = -1;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef PI_FEATURE_FAMILY_H
+#define PI_FEATURE_FAMILY_H
+
+#include "misc.h"
+#include "pose_cell_set.h"
+#include "pi_feature.h"
+
+class PiFeatureFamily : public Storable {
+ int _nb_features;
+ PiFeature *_pi_features;
+
+public:
+ PiFeatureFamily();
+ ~PiFeatureFamily();
+
+ inline int nb_features() { return _nb_features; }
+
+ inline PiFeature *get_feature(int f) {
+ ASSERT(f >= 0 && f < _nb_features);
+ return _pi_features + f;
+ }
+
+ void read(istream *is);
+ void write(ostream *os);
+
+ void resize(int nb_features);
+ void randomize(int level);
+
+ void extract(PiFeatureFamily *pi_feature_family, bool *used_features, int *new_feature_indexes);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "pi_referential.h"
+#include "global.h"
+#include "rich_image.h"
+
+void PiReferential::draw_frame(RGBImage *image,
+ int registration_mode,
+ int x1, int y1,
+ int x2, int y2,
+ int x3, int y3,
+ int x4, int y4) {
+
+ int r, g, b;
+
+ switch(registration_mode) {
+
+ case PiReferential::RM_HEAD:
+ r = 0; g = 255; b = 0;
+ break;
+
+ case PiReferential::RM_HEAD_NO_POLARITY:
+ r = 128; g = 255; b = 128;
+ break;
+
+ case PiReferential::RM_BELLY:
+ r = 64; g = 0; b = 255;
+ break;
+
+ case PiReferential::RM_BELLY_NO_POLARITY:
+ r = 192; g = 128; b = 255;
+ break;
+
+ case PiReferential::RM_HEAD_BELLY:
+ case PiReferential::RM_HEAD_BELLY_EDGES:
+ r = 255; g = 0; b = 0;
+ break;
+
+ case PiReferential::RM_BODY:
+ case PiReferential::RM_BODY_EDGES:
+ r = 0; g = 128; b = 255;
+ break;
+
+ default:
+ cerr << "INCONSISTENCY" << endl;
+ abort();
+ }
+
+ if(global.pictures_for_article) {
+ r = 255; g = 255; b = 255;
+ image->draw_line(6, r, g, b, x1, y1, x2, y2);
+ image->draw_line(6, r, g, b, x2, y2, x3, y3);
+ image->draw_line(6, r, g, b, x3, y3, x4, y4);
+ image->draw_line(6, r, g, b, x4, y4, x1, y1);
+
+ r = 0; g = 0; b = 0;
+ image->draw_line(2, r, g, b, x1, y1, x2, y2);
+ image->draw_line(2, r, g, b, x2, y2, x3, y3);
+ image->draw_line(2, r, g, b, x3, y3, x4, y4);
+ image->draw_line(2, r, g, b, x4, y4, x1, y1);
+ } else {
+ // int xc = (x1 + x2 + x3 + x4)/4, yc = (y1 + y2 + y3 + y4)/4;
+ // image->draw_line(1, r, g, b, xc - delta, yc, xc + delta, yc);
+ // image->draw_line(1, r, g, b, xc, yc - delta, xc, yc + delta);
+ image->draw_line(2, r, g, b, x1, y1, x2, y2);
+ image->draw_line(2, r, g, b, x2, y2, x3, y3);
+ image->draw_line(2, r, g, b, x3, y3, x4, y4);
+ image->draw_line(2, r, g, b, x4, y4, x1, y1);
+ // image->draw_line(2, r, g, b,
+ // (2*xc + 5 * x1 + 5 * x2)/12, (2 * yc + 5 * y1 + 5 * y2)/12,
+ // (x1 + x2)/2, (y1 + y2)/2);
+ // image->draw_line(6, r, g, b,
+ // (2*xc + 3 * x2 + 3 * x3)/8, (2 * yc + 3 * y2 + 3 * y3)/8,
+ // (x2 + x3)/2, (y2 + y3)/2
+ // );
+ }
+}
+
+void PiReferential::draw_window(RGBImage *image,
+ int registration_mode, Rectangle *window,
+ int filled) {
+ int r, g, b;
+
+ switch(registration_mode) {
+
+ case PiReferential::RM_HEAD:
+ r = 0; g = 255; b = 0;
+ break;
+
+ case PiReferential::RM_HEAD_NO_POLARITY:
+ r = 128; g = 255; b = 128;
+ break;
+
+ case PiReferential::RM_BELLY:
+ r = 64; g = 0; b = 255;
+ break;
+
+ case PiReferential::RM_BELLY_NO_POLARITY:
+ r = 192; g = 128; b = 255;
+ break;
+
+ case PiReferential::RM_HEAD_BELLY:
+ case PiReferential::RM_HEAD_BELLY_EDGES:
+ r = 255; g = 0; b = 0;
+ break;
+
+ case PiReferential::RM_BODY:
+ case PiReferential::RM_BODY_EDGES:
+ r = 0; g = 128; b = 255;
+ break;
+
+ default:
+ cerr << "INCONSISTENCY" << endl;
+ abort();
+ }
+
+ int xmin = int(window->xmin);
+ int ymin = int(window->ymin);
+ int xmax = int(window->xmax);
+ int ymax = int(window->ymax);
+
+ if(global.pictures_for_article) {
+ r = 255; g = 255; b = 255;
+ image->draw_line(6, r, g, b, xmin, ymin, xmax, ymin);
+ image->draw_line(6, r, g, b, xmax, ymin, xmax, ymax);
+ image->draw_line(6, r, g, b, xmax, ymax, xmin, ymax);
+ image->draw_line(6, r, g, b, xmin, ymax, xmin, ymin);
+
+// if(filled) {
+// int delta = 6;
+// for(int d = ymin - ymax; d <= xmax - xmin; d += delta) {
+// int x1 = xmin + d;
+// int y1 = ymin;
+// int x2 = xmin + d + ymax - ymin;
+// int y2 = ymax;
+// if(x1 < xmin) { y1 = y1 + (xmin - x1); x1 = xmin; }
+// if(x2 > xmax) { y2 = y2 - (x2 - xmax); x2 = xmax; }
+// image->draw_line(3, r, g, b, x1, y1, x2, y2);
+// }
+// }
+
+ r = 0; g = 0; b = 0;
+ image->draw_line(2, r, g, b, xmin, ymin, xmax, ymin);
+ image->draw_line(2, r, g, b, xmax, ymin, xmax, ymax);
+ image->draw_line(2, r, g, b, xmax, ymax, xmin, ymax);
+ image->draw_line(2, r, g, b, xmin, ymax, xmin, ymin);
+
+// if(filled) {
+// int delta = 6;
+// for(int d = ymin - ymax; d <= xmax - xmin; d += delta) {
+// int x1 = xmin + d;
+// int y1 = ymin;
+// int x2 = xmin + d + ymax - ymin;
+// int y2 = ymax;
+// if(x1 < xmin) { y1 = y1 + (xmin - x1); x1 = xmin; }
+// if(x2 > xmax) { y2 = y2 - (x2 - xmax); x2 = xmax; }
+// image->draw_line(1, r, g, b, x1, y1, x2, y2);
+// }
+// }
+ } else {
+ image->draw_line(2, r, g, b, xmin, ymin, xmax, ymin);
+ image->draw_line(2, r, g, b, xmax, ymin, xmax, ymax);
+ image->draw_line(2, r, g, b, xmax, ymax, xmin, ymax);
+ image->draw_line(2, r, g, b, xmin, ymax, xmin, ymin);
+ if(filled) {
+ int delta = 4;
+ for(int d = ymin - ymax; d <= xmax - xmin; d += delta) {
+ int x1 = xmin + d;
+ int y1 = ymin;
+ int x2 = xmin + d + ymax - ymin;
+ int y2 = ymax;
+ if(x1 < xmin) { y1 = y1 + (xmin - x1); x1 = xmin; }
+ if(x2 > xmax) { y2 = y2 - (x2 - xmax); x2 = xmax; }
+ image->draw_line(1, r, g, b, x1, y1, x2, y2);
+ }
+ }
+ }
+
+}
+
+void PiReferential::draw_edge_and_scale(RGBImage *image,
+ int registration_mode, Rectangle *window,
+ int _tag, int _edge_scale) {
+ const int ref_radius = 10;
+ int r, g, b;
+ int edges = 0;
+
+ switch(registration_mode) {
+
+ case PiReferential::RM_HEAD:
+ r = 0; g = 255; b = 0;
+ break;
+
+ case PiReferential::RM_HEAD_NO_POLARITY:
+ r = 128; g = 255; b = 128;
+ break;
+
+ case PiReferential::RM_BELLY:
+ r = 64; g = 0; b = 255;
+ break;
+
+ case PiReferential::RM_BELLY_NO_POLARITY:
+ r = 192; g = 128; b = 255;
+ break;
+
+ case PiReferential::RM_HEAD_BELLY_EDGES:
+ edges = 1;
+ case PiReferential::RM_HEAD_BELLY:
+ r = 255; g = 0; b = 0;
+ break;
+
+ case PiReferential::RM_BODY_EDGES:
+ edges = 1;
+ case PiReferential::RM_BODY:
+ r = 0; g = 128; b = 255;
+ break;
+
+ default:
+ cerr << "INCONSISTENCY" << endl;
+ abort();
+ }
+
+ scalar_t xc = (window->xmin + window->xmax)/2;
+ scalar_t yc = (window->ymin + window->ymax)/2;
+ int radius = ref_radius * (1 << _edge_scale);
+
+ image->draw_ellipse(1, r, g, b, xc, yc, radius, radius, 0);
+
+ if(_tag >= RichImage::first_edge_tag && _tag < RichImage::first_edge_tag + RichImage::nb_edge_tags) {
+
+ scalar_t dx, dy;
+
+ switch(_tag - RichImage::first_edge_tag) {
+ case 0:
+ dx = 0; dy = -1;
+ break;
+
+ case 1:
+ dx = 1; dy = -1;
+ break;
+
+ case 2:
+ dx = 1; dy = 0;
+ break;
+
+ case 3:
+ dx = 1; dy = 1;
+ break;
+
+ case 4:
+ dx = 0; dy = 1;
+ break;
+
+ case 5:
+ dx = -1; dy = 1;
+ break;
+
+ case 6:
+ dx = -1; dy = 0;
+ break;
+
+ case 7:
+ dx = -1; dy = -1;
+ break;
+
+ default:
+ abort();
+ }
+
+ scalar_t l = sqrt(dx * dx + dy * dy);
+
+// dx = dx / l;
+// dy = dy / l;
+
+ if(edges) {
+ int delta = 3;
+ image->draw_ellipse(1, r, g, b, xc, yc, radius + delta, radius + delta, 0);
+ }
+
+ for(scalar_t u = 0; u <= radius; u += 0.1) {
+ scalar_t s = sqrt(radius * radius - (u * u * l * l))/l;
+ image->draw_line(2, r, g, b,
+ int(xc + u * dx - s * dy), int(yc + u * dy + s * dx),
+ int(xc + u * dx + s * dy), int(yc + u * dy - s * dx));
+ }
+
+// for(int y = yc - radius; y <= yc + radius; y++) {
+// for(int x = xc - radius; x <= xc + radius; x++) {
+// if(x >= 0 && x < image->width() && y >= 0 && y < image->height() &&
+// (x - xc) * dx + (y - yc) * dy >= 0) {
+// image->draw_point(r, g, b, x, y);
+// }
+// }
+// }
+
+ }
+
+ else if(_tag == RichImage::variance_tag) {
+ image->draw_ellipse(1, r, g, b, xc, yc, 8, 8, 0);
+ }
+
+ // else if(_tag >= RichImage::first_gray_tag && _tag < RichImage::first_gray_tag + RichImage::nb_gray_tags) {
+ // }
+}
+
+PiReferential::PiReferential(PoseCell *cell) {
+ scalar_t head_radius = sqrt(scalar_t(cell->_head_radius.min * cell->_head_radius.max));
+
+ _common_scale = global.scale_to_discrete_log_scale(head_radius / global.min_head_radius);
+
+ scalar_t discrete_scale_ratio = global.discrete_log_scale_to_scale(_common_scale);
+
+ //////////////////////////////////////////////////////////////////////
+ // Locations and scales
+
+ // Head location
+
+ _head_xc = cell->_head_xc.middle() * discrete_scale_ratio;
+ _head_yc = cell->_head_yc.middle() * discrete_scale_ratio;
+ _head_radius = cell->_head_radius.middle() * discrete_scale_ratio;
+ _head_window_scaling = _head_radius * 2.0;
+
+ // Body location
+
+ _body_xc = cell->_body_xc.middle() * discrete_scale_ratio;
+ _body_yc = cell->_body_yc.middle() * discrete_scale_ratio;
+ _body_window_scaling = sqrt(_body_radius_1 * _body_radius_2);
+
+ if((_head_xc - _body_xc) * cos(_body_tilt) + (_head_yc - _body_yc) * sin(_body_tilt) > 0) {
+ _body_tilt += M_PI;
+ }
+
+ // Belly location
+
+ const scalar_t belly_frame_factor = 2.0;
+
+ _belly_xc = _body_xc;
+ _belly_yc = _body_yc;
+ _belly_window_scaling = _head_window_scaling * belly_frame_factor;
+
+ // Head-belly location
+
+ _head_belly_xc = (_head_xc + _body_xc) * 0.5;
+ _head_belly_yc = (_head_yc + _body_yc) * 0.5;
+
+ //////////////////////////////////////////////////////////////////////
+ // Frames
+
+ if(_body_xc >= _head_xc) {
+ _horizontal_polarity = 1;
+ } else {
+ _horizontal_polarity = -1;
+ }
+
+ // Head frame
+
+ if(_horizontal_polarity < 0) {
+ _head_ux = _head_radius * 2.0;
+ _head_uy = 0;
+ } else {
+ _head_ux = - _head_radius * 2.0;
+ _head_uy = 0;
+ }
+
+ _head_vx = 0;
+ _head_vy = - _head_radius * 2.0;
+
+ _head_ux_nopolarity = _head_radius * 2.0;
+ _head_uy_nopolarity = 0;
+ _head_vx_nopolarity = 0;
+ _head_vy_nopolarity = - _head_radius * 2.0;
+
+ // Belly frame
+
+ _belly_ux = _head_ux * belly_frame_factor;
+ _belly_uy = _head_uy * belly_frame_factor;
+ _belly_vx = _head_vx * belly_frame_factor;
+ _belly_vy = _head_vy * belly_frame_factor;
+
+ _belly_ux_nopolarity = _head_ux_nopolarity * belly_frame_factor;
+ _belly_uy_nopolarity = _head_uy_nopolarity * belly_frame_factor;
+ _belly_vx_nopolarity = _head_vx_nopolarity * belly_frame_factor;
+ _belly_vy_nopolarity = _head_vy_nopolarity * belly_frame_factor;
+
+ // Head-belly frame
+
+ _head_belly_ux = 2 * (_head_xc - _head_belly_xc);
+ _head_belly_uy = 2 * (_head_yc - _head_belly_yc);
+
+ if(_horizontal_polarity < 0) {
+ _head_belly_vx = _head_belly_uy;
+ _head_belly_vy = - _head_belly_ux;
+ } else {
+ _head_belly_vx = - _head_belly_uy;
+ _head_belly_vy = _head_belly_ux;
+ }
+
+ scalar_t l = sqrt(_head_belly_vx * _head_belly_vx + _head_belly_vy * _head_belly_vy);
+
+ _head_belly_vx = _head_belly_vx/l * _head_radius * 2;
+ _head_belly_vy = _head_belly_vy/l * _head_radius * 2;
+ _head_belly_edge_shift = int(floor(- RichImage::nb_edge_tags * atan2(_head_belly_ux, _head_belly_uy) / (2 * M_PI) + 0.5));
+ _head_belly_edge_shift = (RichImage::nb_edge_tags + _head_belly_edge_shift) % RichImage::nb_edge_tags;
+
+ // Body frame
+
+ _body_ux = cos(_body_tilt) * _body_radius_1 * 2.0;
+ _body_uy = sin(_body_tilt) * _body_radius_1 * 2.0;
+ _body_vx = - sin(_body_tilt) * _body_radius_2 * 2.0;
+ _body_vy = cos(_body_tilt) * _body_radius_2 * 2.0;
+
+ _body_edge_shift = int(floor(RichImage::nb_edge_tags * _body_tilt / (2 * M_PI) + 0.5));
+ _body_edge_shift = (RichImage::nb_edge_tags + _body_edge_shift) % RichImage::nb_edge_tags;
+}
+
+int PiReferential::common_scale() {
+ return _common_scale;
+}
+
+void PiReferential::register_rectangle(int registration_mode,
+ Rectangle *original,
+ Rectangle *result) {
+ scalar_t alpha, beta , xc, yc, w, h;
+
+ alpha = (original->xmin + original->xmax) * 0.5;
+ beta = (original->ymin + original->ymax) * 0.5;
+
+ switch(registration_mode) {
+
+ case RM_HEAD:
+ {
+ xc = _head_xc + alpha * _head_ux + beta * _head_vx;
+ yc = _head_yc + alpha * _head_uy + beta * _head_vy;
+ w = (original->xmax - original->xmin) * _head_window_scaling;
+ h = (original->ymax - original->ymin) * _head_window_scaling;
+ }
+ break;
+
+ case RM_HEAD_NO_POLARITY:
+ {
+ xc = _head_xc + alpha * _head_ux_nopolarity + beta * _head_vx_nopolarity;
+ yc = _head_yc + alpha * _head_uy_nopolarity + beta * _head_vy_nopolarity;
+ w = (original->xmax - original->xmin) * _head_window_scaling;
+ h = (original->ymax - original->ymin) * _head_window_scaling;
+ }
+ break;
+
+ case RM_BELLY:
+ {
+ xc = _belly_xc + alpha * _belly_ux + beta * _belly_vx;
+ yc = _belly_yc + alpha * _belly_uy + beta * _belly_vy;
+ w = (original->xmax - original->xmin) * _belly_window_scaling;
+ h = (original->ymax - original->ymin) * _belly_window_scaling;
+ }
+ break;
+
+ case RM_BELLY_NO_POLARITY:
+ {
+ xc = _belly_xc + alpha * _belly_ux_nopolarity + beta * _belly_vx_nopolarity;
+ yc = _belly_yc + alpha * _belly_uy_nopolarity + beta * _belly_vy_nopolarity;
+ w = (original->xmax - original->xmin) * _belly_window_scaling;
+ h = (original->ymax - original->ymin) * _belly_window_scaling;
+ }
+ break;
+
+ case RM_HEAD_BELLY:
+ case RM_HEAD_BELLY_EDGES:
+ {
+ xc = _head_belly_xc + alpha * _head_belly_ux + beta * _head_belly_vx;
+ yc = _head_belly_yc + alpha * _head_belly_uy + beta * _head_belly_vy;
+ w = (original->xmax - original->xmin) * _head_window_scaling;
+ h = (original->ymax - original->ymin) * _head_window_scaling;
+ }
+ break;
+
+ case RM_BODY:
+ case RM_BODY_EDGES:
+ {
+ xc = _body_xc + alpha * _body_ux + beta * _body_vx;
+ yc = _body_yc + alpha * _body_uy + beta * _body_vy;
+ w = (original->xmax - original->xmin) * _body_window_scaling;
+ h = (original->ymax - original->ymin) * _body_window_scaling;
+ }
+ break;
+
+ default:
+ cerr << "Undefined registration mode." << endl;
+ abort();
+ }
+
+ result->xmin = xc - 0.5 * w;
+ result->ymin = yc - 0.5 * h;
+ result->xmax = xc + 0.5 * w;
+ result->ymax = yc + 0.5 * h;
+
+ ASSERT(result->xmin < result->xmax && result->ymin < result->ymax);
+}
+
+int PiReferential::register_edge(int registration_mode, int edge_type) {
+
+ if(edge_type >= RichImage::first_edge_tag &&
+ edge_type < RichImage::first_edge_tag + RichImage::nb_edge_tags) {
+
+ int e = edge_type - RichImage::first_edge_tag;
+
+ switch(registration_mode) {
+ case PiReferential::RM_HEAD_NO_POLARITY:
+ case PiReferential::RM_BELLY_NO_POLARITY:
+ break;
+
+ case PiReferential::RM_HEAD:
+ case PiReferential::RM_BELLY:
+ case PiReferential::RM_HEAD_BELLY:
+ case PiReferential::RM_BODY:
+ if(_horizontal_polarity < 0) {
+ e = (RichImage::nb_edge_tags - e) % RichImage::nb_edge_tags;
+ }
+ break;
+
+ case PiReferential::RM_HEAD_BELLY_EDGES:
+ if(_horizontal_polarity < 0) {
+ e = (RichImage::nb_edge_tags - e) % RichImage::nb_edge_tags;
+ }
+ e += _head_belly_edge_shift;
+ break;
+
+ case PiReferential::RM_BODY_EDGES:
+ if(_horizontal_polarity < 0) {
+ e = (RichImage::nb_edge_tags - e) % RichImage::nb_edge_tags;
+ }
+ e += _body_edge_shift;
+ break;
+
+ default:
+ cerr << "INCONSISTENCY" << endl;
+ abort();
+ }
+
+ e = e % RichImage::nb_edge_tags;
+
+ return RichImage::first_edge_tag + e;
+
+ }
+
+ else return edge_type;
+}
+
+void PiReferential::draw(RGBImage *image, int level) {
+ int x1, y1, x2, y2, x3, y3, x4, y4;
+
+ if(level >= 2) {
+
+ // Draw the RM_BODY reference frame
+
+ x1 = int(_body_xc + _body_ux + _body_vx);
+ y1 = int(_body_yc + _body_uy + _body_vy);
+ x2 = int(_body_xc - _body_ux + _body_vx);
+ y2 = int(_body_yc - _body_uy + _body_vy);
+ x3 = int(_body_xc - _body_ux - _body_vx);
+ y3 = int(_body_yc - _body_uy - _body_vy);
+ x4 = int(_body_xc + _body_ux - _body_vx);
+ y4 = int(_body_yc + _body_uy - _body_vy);
+
+ draw_frame(image, RM_BODY, x1, y1, x2, y2, x3, y3, x4, y4);
+ }
+
+ if(level >= 1) {
+
+ // Draw the RM_BELLY reference frame
+
+ x1 = int(_belly_xc + _belly_ux + _belly_vx);
+ y1 = int(_belly_yc + _belly_uy + _belly_vy);
+ x2 = int(_belly_xc - _belly_ux + _belly_vx);
+ y2 = int(_belly_yc - _belly_uy + _belly_vy);
+ x3 = int(_belly_xc - _belly_ux - _belly_vx);
+ y3 = int(_belly_yc - _belly_uy - _belly_vy);
+ x4 = int(_belly_xc + _belly_ux - _belly_vx);
+ y4 = int(_belly_yc + _belly_uy - _belly_vy);
+
+ draw_frame(image, RM_BELLY, x1, y1, x2, y2, x3, y3, x4, y4);
+
+ // Draw the RM_HEAD_BELLY reference frame
+
+ x1 = int(_head_belly_xc + _head_belly_ux + _head_belly_vx);
+ y1 = int(_head_belly_yc + _head_belly_uy + _head_belly_vy);
+ x2 = int(_head_belly_xc - _head_belly_ux + _head_belly_vx);
+ y2 = int(_head_belly_yc - _head_belly_uy + _head_belly_vy);
+ x3 = int(_head_belly_xc - _head_belly_ux - _head_belly_vx);
+ y3 = int(_head_belly_yc - _head_belly_uy - _head_belly_vy);
+ x4 = int(_head_belly_xc + _head_belly_ux - _head_belly_vx);
+ y4 = int(_head_belly_yc + _head_belly_uy - _head_belly_vy);
+
+ draw_frame(image, RM_HEAD_BELLY, x1, y1, x2, y2, x3, y3, x4, y4);
+ }
+
+ // Draw the RM_HEAD reference frame
+
+ x1 = int(_head_xc + _head_ux + _head_vx);
+ y1 = int(_head_yc + _head_uy + _head_vy);
+ x2 = int(_head_xc - _head_ux + _head_vx);
+ y2 = int(_head_yc - _head_uy + _head_vy);
+ x3 = int(_head_xc - _head_ux - _head_vx);
+ y3 = int(_head_yc - _head_uy - _head_vy);
+ x4 = int(_head_xc + _head_ux - _head_vx);
+ y4 = int(_head_yc + _head_uy - _head_vy);
+
+ draw_frame(image, RM_HEAD, x1, y1, x2, y2, x3, y3, x4, y4);
+}
+
+void PiReferential::print_registration_mode(ostream *out, int registration_mode) {
+ switch(registration_mode) {
+ case RM_HEAD:
+ (*out) << "RM_HEAD";
+ break;
+ case RM_HEAD_NO_POLARITY:
+ (*out) << "RM_HEAD_NO_POLARITY";
+ break;
+ case RM_BELLY:
+ (*out) << "RM_BELLY";
+ break;
+ case RM_BELLY_NO_POLARITY:
+ (*out) << "RM_BELLY_NO_POLARITY";
+ break;
+ case RM_HEAD_BELLY:
+ (*out) << "RM_HEAD_BELLY";
+ break;
+ case RM_HEAD_BELLY_EDGES:
+ (*out) << "RM_HEAD_BELLY_EDGES";
+ break;
+ case RM_BODY:
+ (*out) << "RM_BODY";
+ break;
+ case RM_BODY_EDGES:
+ (*out) << "RM_BODY_EDGES";
+ break;
+ default:
+ abort();
+ }
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+
+ This class factorizes the information from a PoseCell needed by a
+ PiFeature to be evaluated on an image. Since there can be in there
+ costly operations such as trigonometric mappings, it provides a
+ substantial cost optimization.
+
+*/
+
+#ifndef PI_REFERENTIAL_H
+#define PI_REFERENTIAL_H
+
+#include "rectangle.h"
+#include "pose_cell.h"
+#include "rgb_image.h"
+
+class PiReferential {
+ // The best scale so that the head size is
+ // ~global.reference_head_size. This is an integer scale as defined
+ // for the RichImage
+ int _common_scale;
+
+ scalar_t _horizontal_polarity;
+
+ // The head frame in _common_scale. The vectors are of length the _RADIUS_ of the head
+ scalar_t _head_xc, _head_yc, _head_radius;
+ scalar_t _head_window_scaling;
+ scalar_t _head_ux, _head_uy, _head_vx, _head_vy;
+ scalar_t _head_ux_nopolarity, _head_uy_nopolarity, _head_vx_nopolarity, _head_vy_nopolarity;
+
+ // The body frame in that _common_scale. The vectors are of length the radii of the ellipse
+ scalar_t _body_xc, _body_yc;
+ scalar_t _body_radius_1, _body_radius_2, _body_tilt;
+ scalar_t _body_ux, _body_uy, _body_vx, _body_vy;
+ scalar_t _body_window_scaling;
+ int _body_edge_shift;
+
+ // The belly frame is defined by the body location and head scale
+ scalar_t _belly_xc, _belly_yc;
+ scalar_t _belly_ux, _belly_uy, _belly_vx, _belly_vy;
+ scalar_t _belly_ux_nopolarity, _belly_uy_nopolarity, _belly_vx_nopolarity, _belly_vy_nopolarity;
+ scalar_t _belly_window_scaling;
+
+ // The head-belly frame is defined by the head location and the body
+ // center location
+ scalar_t _head_belly_xc, _head_belly_yc;
+ scalar_t _head_belly_ux, _head_belly_uy, _head_belly_vx, _head_belly_vy;
+ int _head_belly_edge_shift;
+
+ void draw_frame(RGBImage *image,
+ int registration_mode,
+ int x1, int y1,
+ int x2, int y2,
+ int x3, int y3,
+ int x4, int y4);
+
+public:
+ PiReferential(PoseCell *cell);
+
+ enum {
+ // A frame centered on the head, of size four times the head radius
+ // and, flipped verically if the body center is on the left of the
+ // head center
+ RM_HEAD,
+
+ // Same as above, without the flipping
+ RM_HEAD_NO_POLARITY,
+ // A frame centered on the body center, of size size times the
+ // head rardius, flipped vertically if the body center is on the
+ // left of the head center
+ RM_BELLY,
+
+ // Same as above, without the flipping
+ RM_BELLY_NO_POLARITY,
+
+ // A frame centered on the middle point between the head center
+ // and the body center, of size twice the distance head center -
+ // body center in the head-body direction, and of four times the
+ // head radius in the other
+ RM_HEAD_BELLY,
+
+ // Same as above with rotation of the edges
+ RM_HEAD_BELLY_EDGES,
+
+ // Not finished yet
+ RM_BODY,
+ RM_BODY_EDGES
+ };
+
+ int common_scale();
+
+ // The rectangle coordinates are in the reference frames. For the
+ // head for instance , [-1,1] x [-1,1] corresponds to the head
+ // bounding box
+
+ void register_rectangle(int registration_mode,
+ Rectangle *original,
+ Rectangle *result);
+
+ int register_edge(int registration_type, int edge_type);
+
+ void draw(RGBImage *image, int level);
+
+ void draw_window(RGBImage *image,
+ int registration_mode, Rectangle *window,
+ int filled);
+
+ void draw_edge_and_scale(RGBImage *image,
+ int registration_mode, Rectangle *window,
+ int _tag, int _edge_scale);
+
+ static void print_registration_mode(ostream *out, int registration_mode);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include <string.h>
+
+#include "pose.h"
+
+Pose::Pose() {
+ memset(this, 0, sizeof(*this));
+}
+
+void Pose::horizontal_flip(scalar_t scene_width) {
+ _head_xc = scene_width - 1 - _head_xc;
+ _body_xc = scene_width - 1 - _body_xc;
+}
+
+void Pose::translate(scalar_t dx, scalar_t dy) {
+ _bounding_box_xmin += dx;
+ _bounding_box_ymin += dy;
+ _bounding_box_xmax += dx;
+ _bounding_box_ymax += dy;
+ _head_xc += dx;
+ _head_yc += dy;
+ _body_xc += dx;
+ _body_yc += dy;
+}
+
+void Pose::scale(scalar_t factor) {
+ _bounding_box_xmin *= factor;
+ _bounding_box_ymin *= factor;
+ _bounding_box_xmax *= factor;
+ _bounding_box_ymax *= factor;
+ _head_xc *= factor;
+ _head_yc *= factor;
+ _head_radius *= factor;
+ _body_xc *= factor;
+ _body_yc *= factor;
+}
+
+const scalar_t tolerance_scale_ratio_for_hit = 1.5;
+const scalar_t tolerance_distance_factor_for_hit = 1.0;
+
+bool Pose::hit(int level, Pose *pose) {
+
+ // Head size
+
+ if(_head_radius/pose->_head_radius > tolerance_scale_ratio_for_hit ||
+ pose->_head_radius/_head_radius > tolerance_scale_ratio_for_hit)
+ return false;
+
+ scalar_t sq_delta = _head_radius * pose->_head_radius * sq(tolerance_distance_factor_for_hit);
+
+ // Head location
+
+ if(sq(_head_xc - pose->_head_xc) + sq(_head_yc - pose->_head_yc) > sq_delta)
+ return false;
+
+ if(level == 0) return true;
+
+ // Belly location
+
+ if(sq(_body_xc - pose->_body_xc) + sq(_body_yc - pose->_body_yc) > 4 * sq_delta)
+ return false;
+
+ if(level == 1) return true;
+
+ cerr << "Hit criterion is undefined for level " << level << endl;
+
+ abort();
+}
+
+const scalar_t tolerance_scale_ratio_for_collide = 1.2;
+const scalar_t tolerance_distance_factor_for_collide = 0.25;
+
+bool Pose::collide(int level, Pose *pose) {
+
+ // Head size
+
+ if(_head_radius/pose->_head_radius > tolerance_scale_ratio_for_collide ||
+ pose->_head_radius/_head_radius > tolerance_scale_ratio_for_collide)
+ return false;
+
+ scalar_t sq_delta = _head_radius * pose->_head_radius * sq(tolerance_distance_factor_for_collide);
+
+ // Head location
+
+ if(sq(_head_xc - pose->_head_xc) + sq(_head_yc - pose->_head_yc) <= sq_delta)
+ return true;
+
+ if(level == 0) return false;
+
+ // Belly location
+
+ if(sq(_body_xc - pose->_body_xc) + sq(_body_yc - pose->_body_yc) <= sq_delta)
+ return true;
+
+ if(level == 1) return false;
+
+ cerr << "Colliding criterion is undefined for level " << level << endl;
+
+ abort();
+}
+
+void Pose::draw(int thickness, int r, int g, int b, int level, RGBImage *image) {
+ // Draw the head circle
+
+ image->draw_ellipse(thickness, r, g, b,
+ _head_xc, _head_yc, _head_radius, _head_radius, 0);
+
+ // int vx = int(cos(_head_tilt) * _head_radius);
+ // int vy = int(sin(_head_tilt) * _head_radius);
+ // image->draw_line(thickness, r, g, b, _head_xc, _head_yc, _head_xc + vx, _head_yc + vy);
+
+ if(level == 1) {
+
+ // image->draw_line(thickness, r, g, b,
+ // int(_body_xc) - delta, int(_body_yc),
+ // int(_body_xc) + delta, int(_body_yc));
+
+ // image->draw_line(thickness, r, g, b,
+ // int(_body_xc), int(_body_yc) - delta,
+ // int(_body_xc), int(_body_yc) + delta);
+
+ scalar_t vx = _body_xc - _head_xc, vy = _body_yc - _head_yc;
+ scalar_t l = sqrt(vx * vx + vy * vy);
+ vx /= l;
+ vy /= l;
+
+ scalar_t body_radius = 12 + thickness / 2;
+
+ if(l > _head_radius + thickness + body_radius) {
+ image->draw_line(thickness, r, g, b,
+ _head_xc + vx * (_head_radius + thickness/2),
+ _head_yc + vy * (_head_radius + thickness/2),
+ _body_xc - vx * (body_radius - thickness/2),
+ _body_yc - vy * (body_radius - thickness/2));
+ }
+
+ // An ugly way to make a filled disc
+ for(scalar_t u = 0; u < body_radius; u += thickness / 2) {
+ image->draw_ellipse(thickness, r, g, b, _body_xc, _body_yc, u, u, 0);
+ }
+
+ }
+}
+
+void Pose::print(ostream *out) {
+ (*out) << " _head_xc " << _head_xc << endl;
+ (*out) << " _head_yc " << _head_yc << endl;
+ (*out) << " _head_radius " << _head_radius << endl;
+ (*out) << " _body_xc " << _body_xc << endl;
+ (*out) << " _body_yc " << _body_yc << endl;
+}
+
+void Pose::write(ostream *out) {
+ write_var(out, &_bounding_box_xmin);
+ write_var(out, &_bounding_box_ymin);
+ write_var(out, &_bounding_box_xmax);
+ write_var(out, &_bounding_box_ymax);
+ write_var(out, &_head_xc); write_var(out, &_head_yc);
+ write_var(out, &_head_radius);
+ write_var(out, &_body_xc);
+ write_var(out, &_body_yc);
+}
+
+void Pose::read(istream *in) {
+ read_var(in, &_bounding_box_xmin);
+ read_var(in, &_bounding_box_ymin);
+ read_var(in, &_bounding_box_xmax);
+ read_var(in, &_bounding_box_ymax);
+ read_var(in, &_head_xc); read_var(in, &_head_yc);
+ read_var(in, &_head_radius);
+ read_var(in, &_body_xc);
+ read_var(in, &_body_yc);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef POSE_H
+#define POSE_H
+
+#include "rgb_image.h"
+#include "image.h"
+
+class Pose {
+public:
+ scalar_t _bounding_box_xmin, _bounding_box_ymin;
+ scalar_t _bounding_box_xmax, _bounding_box_ymax;
+ scalar_t _head_xc, _head_yc, _head_radius;
+ scalar_t _body_xc, _body_yc;
+
+ Pose();
+
+ bool hit(int level, Pose *pose);
+ bool collide(int level, Pose *pose);
+
+ void horizontal_flip(scalar_t scene_width);
+ void translate(scalar_t dx, scalar_t dy);
+ void scale(scalar_t factor);
+
+ void draw(int thickness, int r, int g, int b, int level, RGBImage *image);
+
+ void print(ostream *out);
+
+ void write(ostream *out);
+ void read(istream *in);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "pose_cell.h"
+#include "global.h"
+#include "tools.h"
+
+bool PoseCell::contains(Pose *pose) {
+ return
+ _head_xc.contains(pose->_head_xc) &&
+ _head_yc.contains(pose->_head_yc) &&
+ _head_radius.contains(pose->_head_radius) &&
+ _body_xc.contains(pose->_body_xc) &&
+ _body_yc.contains(pose->_body_yc);
+}
+
+bool PoseCell::negative_for_train(Pose *pose) {
+ const scalar_t r_tolerance = 3;
+ scalar_t r = pose->_head_radius;
+ return
+ r <= _head_radius.min / r_tolerance ||
+ r >= _head_radius.max * r_tolerance ||
+ pose->_head_xc <= _head_xc.min - r ||
+ pose->_head_xc >= _head_xc.max + r ||
+ pose->_head_yc <= _head_yc.min - r ||
+ pose->_head_yc >= _head_yc.max + r;
+}
+
+void PoseCell::get_centroid(Pose *pose) {
+ pose->_bounding_box_xmin = -1;
+ pose->_bounding_box_ymin = -1;
+ pose->_bounding_box_xmax = -1;
+ pose->_bounding_box_ymax = -1;
+ pose->_head_radius = sqrt(_head_radius.min * _head_radius.max);
+ pose->_head_xc = _head_xc.middle();
+ pose->_head_yc = _head_yc.middle();
+ pose->_body_xc = _body_xc.middle();
+ pose->_body_yc = _body_yc.middle();
+}
+
+void PoseCell::print(ostream *out) {
+ (*out) << " _head_xc " << _head_xc << endl;
+ (*out) << " _head_yc " << _head_yc << endl;
+ (*out) << " _head_radius " << _head_radius << endl;
+ (*out) << " _head_tilt " << _head_tilt << endl;
+ (*out) << " _body_xc " << _body_xc << endl;
+ (*out) << " _body_yc " << _body_yc << endl;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef POSE_CELL_H
+#define POSE_CELL_H
+
+#include "pose.h"
+#include "interval.h"
+
+class PoseCell {
+public:
+
+ Interval _head_xc, _head_yc, _head_radius, _head_tilt;
+ Interval _body_xc, _body_yc;
+
+ // The cell contains the pose
+
+ bool contains(Pose *pose);
+
+ // The pose is far enough from the cell to accept the cell as a
+ // negative sample
+
+ bool negative_for_train(Pose *pose);
+
+ // Copies into pose the average pose of that cell
+
+ void get_centroid(Pose *pose);
+
+ void print(ostream *out);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "pose_cell_hierarchy.h"
+#include "gaussian.h"
+
+PoseCellHierarchy::PoseCellHierarchy() {
+ _body_cells = 0;
+}
+
+PoseCellHierarchy::PoseCellHierarchy(LabelledImagePool *train_pool) {
+ _nb_levels = global.nb_levels;
+ _min_head_radius = global.min_head_radius;
+ _max_head_radius = global.max_head_radius;
+ _root_cell_nb_xy_per_scale = global.root_cell_nb_xy_per_scale;
+
+ LabelledImage *image;
+ int nb_total_targets = 0;
+ for(int i = 0; i < train_pool->nb_images(); i++) {
+ image = train_pool->grab_image(i);
+ // We are going to symmetrize
+ nb_total_targets += 2 * image->nb_targets();
+ train_pool->release_image(i);
+ }
+
+ RelativeBodyPoseCell targets[nb_total_targets];
+
+ int u = 0;
+ for(int i = 0; i < train_pool->nb_images(); i++) {
+ image = train_pool->grab_image(i);
+
+ PoseCellSet cell_set;
+ add_root_cells(image, &cell_set);
+
+ for(int t = 0; t < image->nb_targets(); t++) {
+ Pose pose = *image->get_target_pose(t);
+ Pose coarse;
+
+ cell_set.get_containing_cell(&pose)->get_centroid(&coarse);
+
+ targets[u]._body_xc.set((pose._body_xc - coarse._head_xc) / coarse._head_radius);
+ targets[u]._body_yc.set((pose._body_yc - coarse._head_yc) / coarse._head_radius);
+ u++;
+
+ pose.horizontal_flip(image->width());
+
+ cell_set.get_containing_cell(&pose)->get_centroid(&coarse);
+
+ targets[u]._body_xc.set((pose._body_xc - coarse._head_xc) / coarse._head_radius);
+ targets[u]._body_yc.set((pose._body_yc - coarse._head_yc) / coarse._head_radius);
+ u++;
+ }
+
+ train_pool->release_image(i);
+ }
+
+ scalar_t fattening = 1.1;
+
+ Interval body_rxc, body_ryc;
+
+ body_rxc.set(&targets[0]._body_xc);
+ body_ryc.set(&targets[0]._body_yc);
+
+ for(int t = 0; t < nb_total_targets; t++) {
+ body_rxc.swallow(&targets[t]._body_xc);
+ body_ryc.swallow(&targets[t]._body_yc);
+ }
+
+ body_rxc.min *= fattening;
+ body_rxc.max *= fattening;
+ body_ryc.min *= fattening;
+ body_ryc.max *= fattening;
+
+ scalar_t body_rxc_min = body_resolution * floor(body_rxc.min / body_resolution);
+ int nb_body_rxc = int(ceil((body_rxc.max - body_rxc_min) / body_resolution));
+
+ scalar_t body_ryc_min = body_resolution * floor(body_ryc.min / body_resolution);
+ int nb_body_ryc = int(ceil((body_ryc.max - body_ryc_min) / body_resolution));
+
+ (*global.log_stream) << "body_rxc = " << body_rxc << endl
+ << "body_rxc_min = " << body_rxc_min << endl
+ << "body_rxc_min + nb_body_rxc * body_resolution = " << body_rxc_min + nb_body_rxc * body_resolution << endl
+ << endl
+ << "body_ryc = " << body_ryc << endl
+ << "body_ryc_min = " << body_ryc_min << endl
+ << "body_ryc_min + nb_body_ryc * body_resolution = " << body_ryc_min + nb_body_ryc * body_resolution << endl;
+
+ int used[nb_body_rxc * nb_body_rxc];
+
+ for(int k = 0; k < nb_body_rxc * nb_body_ryc; k++) {
+ used[k] = 1;
+ }
+
+ // An ugly way to compute the convexe enveloppe
+
+ for(scalar_t alpha = 0; alpha < M_PI * 2; alpha += (2 * M_PI) / 100) {
+ scalar_t vx = cos(alpha), vy = sin(alpha);
+ scalar_t rho = 0;
+
+ for(int t = 0; t < nb_total_targets; t++) {
+ rho = min(rho, vx * targets[t]._body_xc.middle() + vy * targets[t]._body_yc.middle());
+ }
+
+ rho *= fattening;
+
+ for(int j = 0; j < nb_body_ryc; j++) {
+ for(int i = 0; i < nb_body_rxc; i++) {
+ if(
+ vx * (scalar_t(i + 0) * body_resolution + body_rxc_min) +
+ vy * (scalar_t(j + 0) * body_resolution + body_ryc_min) < rho
+ &&
+ vx * (scalar_t(i + 1) * body_resolution + body_rxc_min) +
+ vy * (scalar_t(j + 0) * body_resolution + body_ryc_min) < rho
+ &&
+ vx * (scalar_t(i + 0) * body_resolution + body_rxc_min) +
+ vy * (scalar_t(j + 1) * body_resolution + body_ryc_min) < rho
+ &&
+ vx * (scalar_t(i + 1) * body_resolution + body_rxc_min) +
+ vy * (scalar_t(j + 1) * body_resolution + body_ryc_min) < rho
+ ) {
+ used[i + j * nb_body_rxc] = 0;
+ }
+ }
+ }
+ }
+
+ _nb_body_cells = 0;
+ for(int j = 0; j < nb_body_ryc; j++) {
+ for(int i = 0; i < nb_body_rxc; i++) {
+ if(used[i + nb_body_rxc * j]) {
+ _nb_body_cells++;
+ }
+ }
+ }
+
+ _body_cells = new RelativeBodyPoseCell[_nb_body_cells];
+
+ for(int j = 0; j < nb_body_ryc; j++) {
+ for(int i = 0; i < nb_body_rxc; i++) {
+ if(used[i + nb_body_rxc * j]) {
+ if(sq(scalar_t(i) * body_resolution + body_resolution/2 + body_rxc_min) +
+ sq(scalar_t(j) * body_resolution + body_resolution/2 + body_ryc_min) <= 1) {
+ (*global.log_stream) << "*";
+ } else {
+ (*global.log_stream) << "X";
+ }
+ } else {
+ (*global.log_stream) << ".";
+ }
+ }
+ (*global.log_stream) << endl;
+ }
+
+ int k = 0;
+ for(int j = 0; j < nb_body_ryc; j++) {
+ for(int i = 0; i < nb_body_rxc; i++) {
+
+ if(used[i + nb_body_rxc * j]) {
+
+ RelativeBodyPoseCell mother;
+
+ scalar_t x = scalar_t(i) * body_resolution + body_rxc_min;
+ scalar_t y = scalar_t(j) * body_resolution + body_ryc_min;
+
+ mother._body_xc.set(x, x + body_resolution);
+ mother._body_yc.set(y, y + body_resolution);
+
+ // scalar_t dist_min = body_resolution;
+ scalar_t dist_min = 1e6;
+
+ int nb_got;
+
+ Gaussian dist_body_radius_1, dist_body_radius_2, dist_body_tilt;
+
+ do {
+
+ nb_got = 0;
+
+ for(int t = 0; t < nb_total_targets; t++) {
+
+ scalar_t dist =
+ sqrt(sq(targets[t]._body_xc.middle() - x - body_resolution / 2) +
+ sq(targets[t]._body_yc.middle() - y - body_resolution / 2));
+
+ if(dist <= dist_min) {
+ dist_body_radius_1.add_sample(targets[t]._body_radius_1.middle());
+ dist_body_radius_2.add_sample(targets[t]._body_radius_2.middle());
+ dist_body_tilt.add_sample(targets[t]._body_tilt.middle());
+ nb_got++;
+ }
+
+ }
+
+ dist_min *= 2.0;
+ } while(nb_got < min(100, nb_total_targets));
+
+ scalar_t zeta = 4;
+
+ mother._body_radius_1.set(dist_body_radius_1.expectation() -
+ zeta * dist_body_radius_1.standard_deviation(),
+ dist_body_radius_1.expectation() +
+ zeta * dist_body_radius_1.standard_deviation());
+
+ mother._body_radius_2.set(dist_body_radius_2.expectation() -
+ zeta * dist_body_radius_2.standard_deviation(),
+ dist_body_radius_2.expectation() +
+ zeta * dist_body_radius_2.standard_deviation());
+
+ mother._body_tilt.set(dist_body_tilt.expectation() -
+ zeta * dist_body_tilt.standard_deviation(),
+ dist_body_tilt.expectation() +
+ zeta * dist_body_tilt.standard_deviation());
+
+ _body_cells[k++] = mother;
+ }
+ }
+ }
+
+ (*global.log_stream) << _nb_body_cells << " body cells." << endl;
+}
+
+PoseCellHierarchy::~PoseCellHierarchy() {
+ delete[] _body_cells;
+}
+
+int PoseCellHierarchy::nb_levels() {
+ return _nb_levels;
+}
+
+void PoseCellHierarchy::get_containing_cell(Image *image, int level,
+ Pose *pose, PoseCell *result_cell) {
+ PoseCellSet cell_set;
+
+ for(int l = 0; l < level + 1; l++) {
+ cell_set.erase_content();
+ if(l == 0) {
+ add_root_cells(image, &cell_set);
+ } else {
+ add_subcells(l, result_cell, &cell_set);
+ }
+
+ *result_cell = *(cell_set.get_containing_cell(pose));
+ }
+}
+
+void PoseCellHierarchy::add_root_cells(Image *image, PoseCellSet *cell_set) {
+
+ const int nb_scales = int((log(_max_head_radius) - log(_min_head_radius)) / log(2) *
+ global.nb_scales_per_power_of_two);
+
+ scalar_t alpha = log(_min_head_radius);
+ scalar_t beta = log(2) / scalar_t(global.nb_scales_per_power_of_two);
+
+ for(int s = 0; s < nb_scales; s++) {
+ scalar_t cell_xy_size = exp(alpha + scalar_t(s) * beta) / global.root_cell_nb_xy_per_scale;
+ PoseCell cell;
+ cell._head_radius.min = exp(alpha + scalar_t(s) * beta);
+ cell._head_radius.max = exp(alpha + scalar_t(s+1) * beta);
+ cell._head_tilt.min = -M_PI;
+ cell._head_tilt.max = M_PI;
+ for(scalar_t y = 0; y < image->height(); y += cell_xy_size)
+ for(scalar_t x = 0; x < image->width(); x += cell_xy_size) {
+ cell._head_xc.min = x;
+ cell._head_xc.max = x + cell_xy_size;
+ cell._head_yc.min = y;
+ cell._head_yc.max = y + cell_xy_size;
+ cell._body_xc.min = cell._head_xc.min - pseudo_infty;
+ cell._body_xc.max = cell._head_xc.max + pseudo_infty;
+ cell._body_yc.min = cell._head_yc.min - pseudo_infty;
+ cell._body_yc.max = cell._head_yc.max + pseudo_infty;
+ cell_set->add_cell(&cell);
+ }
+ }
+}
+
+void PoseCellHierarchy::add_subcells(int level, PoseCell *root,
+ PoseCellSet *cell_set) {
+
+ switch(level) {
+
+ case 1:
+ {
+ // Here we split the body-center coordinate cell part
+ PoseCell cell = *root;
+ scalar_t r = sqrt(cell._head_radius.min * cell._head_radius.max);
+ scalar_t x = (cell._head_xc.min + cell._head_xc.max) / 2.0;
+ scalar_t y = (cell._head_yc.min + cell._head_yc.max) / 2.0;
+ for(int k = 0; k < _nb_body_cells; k++) {
+ cell._body_xc.min = (_body_cells[k]._body_xc.min * r) + x;
+ cell._body_xc.max = (_body_cells[k]._body_xc.max * r) + x;
+ cell._body_yc.min = (_body_cells[k]._body_yc.min * r) + y;
+ cell._body_yc.max = (_body_cells[k]._body_yc.max * r) + y;
+ cell_set->add_cell(&cell);
+ }
+ }
+ break;
+
+ default:
+ {
+ cerr << "Inconsistent level in PoseCellHierarchy::add_subcells" << endl;
+ abort();
+ }
+ break;
+ }
+}
+
+
+int PoseCellHierarchy::nb_incompatible_poses(LabelledImagePool *pool) {
+ PoseCell target_cell;
+ PoseCellSet cell_set;
+ LabelledImage *image;
+
+ int nb_errors = 0;
+
+ for(int i = 0; i < pool->nb_images(); i++) {
+ image = pool->grab_image(i);
+
+ for(int t = 0; t < image->nb_targets(); t++) {
+ cell_set.erase_content();
+
+ int error_level = -1;
+
+ for(int l = 0; error_level < 0 && l < _nb_levels; l++) {
+ cell_set.erase_content();
+
+ if(l == 0) {
+ add_root_cells(image, &cell_set);
+ } else {
+ add_subcells(l, &target_cell, &cell_set);
+ }
+
+ int nb_compliant = 0;
+
+ for(int c = 0; c < cell_set.nb_cells(); c++) {
+ if(cell_set.get_cell(c)->contains(image->get_target_pose(t))) {
+ target_cell = *(cell_set.get_cell(c));
+ nb_compliant++;
+ }
+ }
+
+ if(nb_compliant != 1) {
+ error_level = l;
+ }
+ }
+
+ if(error_level >= 0) {
+ nb_errors++;
+ }
+ }
+
+ pool->release_image(i);
+ }
+
+ return nb_errors;
+}
+
+void PoseCellHierarchy::write(ostream *os) {
+ write_var(os, &_min_head_radius);
+ write_var(os, &_max_head_radius);
+ write_var(os, &_root_cell_nb_xy_per_scale);
+ write_var(os, &_nb_body_cells);
+ for(int k = 0; k < _nb_body_cells; k++)
+ write_var(os, &_body_cells[k]);
+}
+
+void PoseCellHierarchy::read(istream *is) {
+ delete[] _body_cells;
+ read_var(is, &_min_head_radius);
+ read_var(is, &_max_head_radius);
+ read_var(is, &_root_cell_nb_xy_per_scale);
+ read_var(is, &_nb_body_cells);
+ delete[] _body_cells;
+ _body_cells = new RelativeBodyPoseCell[_nb_body_cells];
+ for(int k = 0; k < _nb_body_cells; k++) {
+ read_var(is, &_body_cells[k]);
+ }
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef POSE_CELL_HIERARCHY_H
+#define POSE_CELL_HIERARCHY_H
+
+#include "pose_cell_set.h"
+#include "labelled_image_pool.h"
+
+struct RelativeBodyPoseCell {
+ Interval _body_xc, _body_yc, _body_radius_1, _body_radius_2, _body_tilt;
+};
+
+class PoseCellHierarchy {
+ static const scalar_t pseudo_infty = 10000;
+
+ static const scalar_t body_resolution = 0.5;
+
+ static const int nb_radius_1 = 16;
+ static const int nb_radius_2 = 16;
+ static const int nb_tilts = 64;
+
+ int _nb_levels;
+ scalar_t _min_head_radius;
+ scalar_t _max_head_radius;
+ int _root_cell_nb_xy_per_scale;
+
+ int _nb_body_cells;
+
+ RelativeBodyPoseCell *_body_cells;
+
+public:
+ PoseCellHierarchy();
+ PoseCellHierarchy(LabelledImagePool *train_pool);
+ virtual ~PoseCellHierarchy();
+
+ virtual int nb_levels();
+ virtual void get_containing_cell(Image *image, int level,
+ Pose *pose, PoseCell *result_cell);
+
+ virtual void add_root_cells(Image *image, PoseCellSet *cell_set);
+ // level is the level to build, hence should be greater than 1
+ virtual void add_subcells(int level, PoseCell *root, PoseCellSet *cell_set);
+
+ virtual int nb_incompatible_poses(LabelledImagePool *pool);
+
+ virtual void write(ostream *os);
+ virtual void read(istream *is);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "pose_cell_hierarchy_reader.h"
+
+PoseCellHierarchy *read_hierarchy(istream *is) {
+ PoseCellHierarchy *result = new PoseCellHierarchy();
+ result->read(is);
+ return result;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef POSE_CELL_HIERARCHY_READER_H
+#define POSE_CELL_HIERARCHY_READER_H
+
+#include "pose_cell_hierarchy.h"
+
+PoseCellHierarchy *read_hierarchy(istream *is);
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "pose_cell_scored_set.h"
+#include "global.h"
+#include "fusion_sort.h"
+
+PoseCellScoredSet::PoseCellScoredSet() {
+ _scores = new scalar_t[_max_nb];
+}
+
+PoseCellScoredSet::~PoseCellScoredSet() {
+ delete[] _scores;
+}
+
+void PoseCellScoredSet::add_cell_with_score(PoseCell *cell, scalar_t score) {
+ _scores[_nb_added] = score;
+ add_cell(cell);
+}
+
+void PoseCellScoredSet::decimate_hit(int level) {
+ if(_nb_added == 0) return;
+
+ Pose *poses = new Pose[_nb_added];
+ int *indexes = new int[_nb_added];
+ int *sorted_indexes = new int[_nb_added];
+
+ for(int c = 0; c < _nb_added; c++) {
+ _cells[c].get_centroid(poses + c);
+ indexes[c] = c;
+ }
+
+ indexed_fusion_dec_sort(_nb_added, indexes, sorted_indexes, _scores);
+
+ int nb_remaining = _nb_added, current = 0;
+
+ while(current < nb_remaining) {
+ int e = current + 1;
+ for(int d = current + 1; d < nb_remaining; d++)
+ if(!poses[sorted_indexes[current]].hit(level, poses + sorted_indexes[d]))
+ sorted_indexes[e++] = sorted_indexes[d];
+ nb_remaining = e;
+ current++;
+ }
+
+ PoseCell *tmp_cells = new PoseCell[max_nb_cells];
+ scalar_t *tmp_scores = new scalar_t[max_nb_cells];
+
+ for(int n = 0; n < nb_remaining; n++) {
+ tmp_cells[n] = _cells[sorted_indexes[n]];
+ tmp_scores[n] = _scores[sorted_indexes[n]];
+ }
+
+ delete[] _cells;
+ delete[] _scores;
+ _cells = tmp_cells;
+ _scores = tmp_scores;
+ _nb_added = nb_remaining;
+
+ delete[] poses;
+ delete[] indexes;
+ delete[] sorted_indexes;
+}
+
+void PoseCellScoredSet::decimate_collide(int level) {
+ if(_nb_added == 0) return;
+
+ Pose *poses = new Pose[_nb_added];
+ int *indexes = new int[_nb_added];
+ int *sorted_indexes = new int[_nb_added];
+
+ for(int c = 0; c < _nb_added; c++) {
+ _cells[c].get_centroid(poses + c);
+ indexes[c] = c;
+ }
+
+ indexed_fusion_dec_sort(_nb_added, indexes, sorted_indexes, _scores);
+
+ int nb_remaining = _nb_added, current = 0;
+
+ while(current < nb_remaining) {
+ int e = current + 1;
+ for(int d = current + 1; d < nb_remaining; d++)
+ if(!poses[sorted_indexes[current]].collide(level, poses + sorted_indexes[d]))
+ sorted_indexes[e++] = sorted_indexes[d];
+ nb_remaining = e;
+ current++;
+ }
+
+ PoseCell *tmp_cells = new PoseCell[max_nb_cells];
+ scalar_t *tmp_scores = new scalar_t[max_nb_cells];
+
+ for(int n = 0; n < nb_remaining; n++) {
+ tmp_cells[n] = _cells[sorted_indexes[n]];
+ tmp_scores[n] = _scores[sorted_indexes[n]];
+ }
+
+ delete[] _cells;
+ delete[] _scores;
+ _cells = tmp_cells;
+ _scores = tmp_scores;
+ _nb_added = nb_remaining;
+
+ delete[] poses;
+ delete[] indexes;
+ delete[] sorted_indexes;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef POSE_CELL_SCORED_SET_H
+#define POSE_CELL_SCORED_SET_H
+
+#include "pose_cell.h"
+#include "labelled_image.h"
+
+#include "pose_cell_set.h"
+
+class PoseCellScoredSet : public PoseCellSet {
+ scalar_t *_scores;
+
+public:
+
+ PoseCellScoredSet();
+ ~PoseCellScoredSet();
+
+ inline scalar_t get_score(int k) {
+ ASSERT(k >= 0 && k < _nb_added);
+ return _scores[k];
+ }
+
+ void add_cell_with_score(PoseCell *cell, scalar_t score);
+
+ void decimate_hit(int level);
+ void decimate_collide(int level);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "pose_cell_set.h"
+#include "global.h"
+
+PoseCellSet::PoseCellSet() {
+ _nb_added = 0;
+ _max_nb = max_nb_cells;
+ _cells = new PoseCell[_max_nb];
+}
+
+PoseCellSet::~PoseCellSet() {
+ delete[] _cells;
+}
+
+void PoseCellSet::erase_content() {
+ _nb_added = 0;
+}
+
+PoseCell *PoseCellSet::get_containing_cell(Pose *p) {
+ for(int c = 0; c < _nb_added; c++) {
+ if(_cells[c].contains(p)) return _cells + c;
+ }
+ return 0;
+}
+
+void PoseCellSet::add_cell(PoseCell *cell) {
+ if(_nb_added < _max_nb) {
+ _cells[_nb_added] = *cell;
+ _nb_added++;
+ } else {
+ cerr << "Too many pose cells!" << endl;
+ abort();
+ }
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef POSE_CELL_SET_H
+#define POSE_CELL_SET_H
+
+#include "pose_cell.h"
+#include "labelled_image.h"
+
+class PoseCellSet {
+protected:
+ // This is a bit ugly. Notice that all the code is written in such a
+ // way that we never have more than one such PoseCellSet in memory
+ // at any moment
+ static const int max_nb_cells = 500000;
+
+ int _max_nb;
+ int _nb_added;
+ PoseCell *_cells;
+
+public:
+
+ PoseCellSet();
+ ~PoseCellSet();
+
+ inline int nb_cells() { return _nb_added; }
+
+ inline PoseCell *get_cell(int k) {
+ ASSERT(k >= 0 && k < _nb_added);
+ return _cells + k;
+ }
+
+ PoseCell *get_containing_cell(Pose *p);
+
+ void erase_content();
+ void add_cell(PoseCell *cell);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include <time.h>
+#include "progress_bar.h"
+
+const int ProgressBar::_width = 80;
+
+ProgressBar::ProgressBar() : _visible(false), _value_max(-1) { }
+
+void ProgressBar::set_visible(bool visible) {
+ _visible = visible;
+}
+
+void ProgressBar::init(ostream *out, scalar_t value_max) {
+ _value_max = value_max;
+ _last_step = -1;
+ time(&_initial_time);
+ refresh(out, 0);
+}
+
+void ProgressBar::refresh(ostream *out, scalar_t value) {
+ if(_visible && _value_max > 0) {
+ int step = int((value * 40) / _value_max);
+
+ if(1 || step > _last_step) {
+ char buffer[_width + 1], date_buffer[buffer_size];
+ int i, j;
+ j = sprintf(buffer, "Timer: ");
+
+ for(i = 0; i < step; i++) buffer[j + i] = 'X';
+ for(; i < 40; i++) buffer[j + i] = (i%4 == 0) ? '+' : '-';
+ j += i;
+
+ time_t current_time; time(¤t_time);
+ int rt = int(((current_time - _initial_time)/scalar_t(value)) * scalar_t(_value_max - value));
+
+ if(rt > 0) {
+ if(rt > 3600 * 24) {
+ time_t current;
+ time(¤t);
+ current += rt;
+ strftime(date_buffer, buffer_size, "%a %b %e %H:%M", localtime(¤t));
+ j += snprintf(buffer + j, _width - j - 1, " (end ~ %s)", date_buffer);
+ } else {
+ int hours = rt/3600, min = (rt%3600)/60, sec = rt%60;
+ if(hours > 0)
+ j += snprintf(buffer + j, _width - j - 1, " (~%dh%dmin left)", hours, min);
+ else if(min > 0)
+ j += snprintf(buffer + j, _width - j - 1, " (~%dmin%ds left)", min, sec);
+ else
+ j += snprintf(buffer + j, _width - j - 1, " (~%ds left)", sec);
+ }
+ }
+
+ for(; j < _width; j++) buffer[j] = ' ';
+ buffer[j] = '\0';
+ (*out) << buffer << "\r";
+ out->flush();
+ _last_step = step;
+ }
+ }
+}
+
+void ProgressBar::finish(ostream *out) {
+ if(_visible) {
+ char buffer[_width + 1];
+ int j;
+ time_t current_time; time(¤t_time);
+ int rt = int(current_time - _initial_time);
+ int min = rt/60, sec = rt%60;
+ j = sprintf(buffer, "Timer: Total %dmin%ds", min, sec);
+ for(; j < _width; j++) buffer[j] = ' ';
+ buffer[j] = '\0';
+ (*out) << buffer << endl;
+ out->flush();
+ }
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+
+ This class displays a progress bar in text mode and computes a rough
+ estimate of the remaining processing time.
+
+*/
+
+#ifndef PROGRESS_BAR_H
+#define PROGRESS_BAR_H
+
+#include <iostream>
+
+using namespace std;
+
+#include "misc.h"
+
+class ProgressBar {
+ bool _visible;
+ scalar_t _value_max, _last_step;
+ time_t _initial_time;
+ const static int _width;
+public:
+ ProgressBar();
+ void set_visible(bool visible);
+ void init(ostream *out, scalar_t value_max);
+ void refresh(ostream *out, scalar_t value);
+ void finish(ostream *out);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef RECTANGLE_H
+#define RECTANGLE_H
+
+#include "misc.h"
+
+struct Rectangle {
+ scalar_t xmin, ymin, xmax, ymax;
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include <iostream>
+#include <stdio.h>
+
+#include <libpng/png.h>
+#include "jpeg_misc.h"
+
+#include "rgb_image.h"
+
+void RGBImage::allocate() {
+ _bit_plans = new unsigned char **[RGB_DEPTH];
+ _bit_lines = new unsigned char *[RGB_DEPTH * _height];
+ _bit_map = new unsigned char [_width * _height * RGB_DEPTH];
+ for(int k = 0; k < RGB_DEPTH; k++) _bit_plans[k] = _bit_lines + k * _height;
+ for(int k = 0; k < RGB_DEPTH * _height; k++) _bit_lines[k] = _bit_map + k * _width;
+}
+
+void RGBImage::deallocate() {
+ delete[] _bit_plans;
+ delete[] _bit_lines;
+ delete[] _bit_map;
+}
+
+RGBImage::RGBImage() : _bit_plans(0), _bit_lines(0), _bit_map(0) { }
+
+RGBImage::RGBImage(int width, int height) : _width(width), _height(height) {
+ allocate();
+ memset(_bit_map, 0, _width * _height * RGB_DEPTH * sizeof(unsigned char));
+}
+
+RGBImage::RGBImage(RGBImage *image, scalar_t scale) {
+ _width = int(scale * image->_width);
+ _height = int(scale * image->_height);
+
+ allocate();
+
+ for(int y = 0; y < _height; y++) {
+ for(int x = 0; x < _width; x++) {
+
+ const int delta = 10;
+ int sr = 0, sg = 0, sb = 0, t = 0;
+ int xo, yo;
+
+ for(int yy = y * delta; yy < (y + 1) * delta; yy++) {
+ for(int xx = x * delta; xx < (x + 1) * delta; xx++) {
+ xo = (image->_width * xx)/(_width * delta);
+ yo = (image->_height * yy)/(_height * delta);
+ if(xo >= 0 && xo < image->_width && yo >= 0 && yo < image->_height) {
+ sr += image->_bit_plans[RED][yo][xo];
+ sg += image->_bit_plans[GREEN][yo][xo];
+ sb += image->_bit_plans[BLUE][yo][xo];
+ t++;
+ }
+ }
+ }
+
+ if(t > 0) {
+ _bit_plans[RED][y][x] = sr / t;
+ _bit_plans[GREEN][y][x] = sg / t;
+ _bit_plans[BLUE][y][x] = sb / t;
+ } else {
+ _bit_plans[RED][y][x] = 0;
+ _bit_plans[GREEN][y][x] = 0;
+ _bit_plans[BLUE][y][x] = 0;
+ }
+
+ }
+ }
+}
+
+RGBImage::~RGBImage() {
+ deallocate();
+}
+
+void RGBImage::write_ppm(const char *filename) {
+ FILE *outfile;
+
+ if ((outfile = fopen (filename, "wb")) == 0) {
+ fprintf (stderr, "Can't open %s for reading\n", filename);
+ exit(1);
+ }
+
+ fprintf(outfile, "P6\n%d %d\n255\n", _width, _height);
+
+ char *raw = new char[_width * _height * 3];
+
+ int k = 0;
+ for(int y = 0; y < _height; y++) for(int x = 0; x < _width; x++) {
+ raw[k++] = _bit_map[x + _width * (y + _height * RED)];
+ raw[k++] = _bit_map[x + _width * (y + _height * GREEN)];
+ raw[k++] = _bit_map[x + _width * (y + _height * BLUE)];
+ }
+
+ fwrite((void *) raw, sizeof(unsigned char), _width * _height * 3, outfile);
+ fclose(outfile);
+
+ delete[] raw;
+}
+
+void RGBImage::read_ppm(const char *filename) {
+ const int buffer_size = 1024;
+ FILE *infile;
+ char buffer[buffer_size];
+ int max;
+
+ deallocate();
+
+ if((infile = fopen (filename, "r")) == 0) {
+ fprintf (stderr, "Can't open %s for reading\n", filename);
+ exit(1);
+ }
+
+ fgets(buffer, buffer_size, infile);
+
+ if(strncmp(buffer, "P6", 2) == 0) {
+
+ do {
+ fgets(buffer, buffer_size, infile);
+ } while((buffer[0] < '0') || (buffer[0] > '9'));
+ sscanf(buffer, "%d %d", &_width, &_height);
+ fgets(buffer, buffer_size, infile);
+ sscanf(buffer, "%d", &max);
+
+ allocate();
+
+ unsigned char *raw = new unsigned char[_width * _height * RGB_DEPTH];
+ fread(raw, sizeof(unsigned char), _width * _height * RGB_DEPTH, infile);
+
+ int k = 0;
+ for(int y = 0; y < _height; y++) for(int x = 0; x < _width; x++) {
+ _bit_plans[RED][y][x] = raw[k++];
+ _bit_plans[GREEN][y][x] = raw[k++];
+ _bit_plans[BLUE][y][x] = raw[k++];
+ }
+
+ delete[] raw;
+
+ } else if(strncmp(buffer, "P5", 2) == 0) {
+
+ do {
+ fgets(buffer, buffer_size, infile);
+ } while((buffer[0] < '0') || (buffer[0] > '9'));
+ sscanf(buffer, "%d %d", &_width, &_height);
+ fgets(buffer, buffer_size, infile);
+ sscanf(buffer, "%d", &max);
+
+ allocate();
+
+ unsigned char *pixbuf = new unsigned char[_width * _height];
+ fread(buffer, sizeof(unsigned char), _width * _height, infile);
+
+ int k = 0, l = 0;
+ for(int y = 0; y < _height; y++) for(int x = 0; x < _width; x++) {
+ unsigned char c = pixbuf[k++];
+ _bit_map[l++] = c;
+ _bit_map[l++] = c;
+ _bit_map[l++] = c;
+ }
+
+ delete[] pixbuf;
+
+ } else {
+ cerr << "Can not read ppm of type [" << buffer << "] from " << filename << ".\n";
+ exit(1);
+ }
+}
+
+void RGBImage::read_png(const char *name) {
+ // This is the number of bytes the read_png routine will read to
+ // decide if the file is a PNG or not. According to the png
+ // documentation, it can be 1 to 8 bytes, 8 being the max and the
+ // best.
+
+ const int header_size = 8;
+
+ png_byte header[header_size];
+ png_bytep *row_pointers;
+
+ deallocate();
+
+ // open file
+ FILE *fp = fopen(name, "rb");
+ if (!fp) {
+ cerr << "Unable to open file " << name << " for reading.\n";
+ exit(1);
+ }
+
+ // read header
+ fread(header, 1, header_size, fp);
+ if (png_sig_cmp(header, 0, header_size)) {
+ cerr << "File " << name << " does not look like PNG.\n";
+ fclose(fp);
+ exit(1);
+ }
+
+ // create png pointer
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
+ if (!png_ptr) {
+ cerr << "png_create_read_struct failed\n";
+ fclose(fp);
+ exit(1);
+ }
+
+ // create png info struct
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ png_destroy_read_struct(&png_ptr, (png_infopp) 0, (png_infopp) 0);
+ cerr << "png_create_info_struct failed\n";
+ fclose(fp);
+ exit(1);
+ }
+
+ // get image info
+ png_init_io(png_ptr, fp);
+ png_set_sig_bytes(png_ptr, header_size);
+ png_read_info(png_ptr, info_ptr);
+
+ _width = info_ptr->width;
+ _height = info_ptr->height;
+
+ png_byte bit_depth, color_type, channels;
+ color_type = info_ptr->color_type;
+ bit_depth = info_ptr->bit_depth;
+ channels = info_ptr->channels;
+
+ if(bit_depth != 8) {
+ cerr << "Can only read 8-bits PNG images." << endl;
+ exit(1);
+ }
+
+ // allocate image pointer
+ row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * _height);
+ for (int y = 0; y < _height; y++)
+ row_pointers[y] = (png_byte*) malloc(info_ptr->rowbytes);
+
+ allocate();
+
+ // read image
+ png_read_image(png_ptr, row_pointers);
+
+ // send image to red, green and blue buffers
+ switch (color_type) {
+ case PNG_COLOR_TYPE_GRAY:
+ {
+ unsigned char pixel = 0;
+ for (int y = 0; y < _height; y++) for (int x = 0; x < _width; x++) {
+ pixel = row_pointers[y][x];
+ _bit_plans[RED][y][x] = pixel;
+ _bit_plans[GREEN][y][x] = pixel;
+ _bit_plans[BLUE][y][x] = pixel;
+ }
+ }
+ break;
+
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ cerr << "PNG type GRAY_ALPHA not supported.\n";
+ exit(1);
+ break;
+
+ case PNG_COLOR_TYPE_PALETTE:
+ cerr << "PNG type PALETTE not supported.\n";
+ exit(1);
+ break;
+
+ case PNG_COLOR_TYPE_RGB:
+ {
+ if(channels != RGB_DEPTH) {
+ cerr << "Unsupported number of channels for RGB type\n";
+ break;
+ }
+ int k;
+ for (int y = 0; y < _height; y++) {
+ k = 0;
+ for (int x = 0; x < _width; x++) {
+ _bit_plans[RED][y][x] = row_pointers[y][k++];
+ _bit_plans[GREEN][y][x] = row_pointers[y][k++];
+ _bit_plans[BLUE][y][x] = row_pointers[y][k++];
+ }
+ }
+ }
+ break;
+
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ cerr << "PNG type RGB_ALPHA not supported.\n";
+ exit(1);
+ break;
+
+ default:
+ cerr << "Unknown PNG type\n";
+ exit(1);
+ }
+
+ // release memory
+ png_destroy_read_struct(&png_ptr, &info_ptr, 0);
+
+ for (int y = 0; y < _height; y++) free(row_pointers[y]);
+ free(row_pointers);
+
+ fclose(fp);
+}
+
+void RGBImage::write_png(const char *name) {
+ png_bytep *row_pointers;
+
+ // create file
+ FILE *fp = fopen(name, "wb");
+
+ if (!fp) {
+ cerr << "Unable to create image '" << name << "'\n";
+ exit(1);
+ }
+
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
+
+ if (!png_ptr) {
+ cerr << "png_create_write_struct failed\n";
+ fclose(fp);
+ exit(1);
+ }
+
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ cerr << "png_create_info_struct failed\n";
+ fclose(fp);
+ exit(1);
+ }
+
+ png_init_io(png_ptr, fp);
+
+ png_set_IHDR(png_ptr, info_ptr, _width, _height,
+ 8, 2, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+ png_write_info(png_ptr, info_ptr);
+
+ // allocate memory
+ row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * _height);
+ for (int y = 0; y < _height; y++)
+ row_pointers[y] = (png_byte*) malloc(info_ptr->rowbytes);
+
+ int k;
+ for (int y = 0; y < _height; y++) {
+ k = 0;
+ for (int x = 0; x < _width; x++) {
+ row_pointers[y][k++] = _bit_map[x + _width * (y + _height * RED)];
+ row_pointers[y][k++] = _bit_map[x + _width * (y + _height * GREEN)];
+ row_pointers[y][k++] = _bit_map[x + _width * (y + _height * BLUE)];
+ }
+ }
+
+ png_write_image(png_ptr, row_pointers);
+ png_write_end(png_ptr, 0);
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+
+ // cleanup heap allocation
+ for (int y = 0; y < _height; y++) free(row_pointers[y]);
+ free(row_pointers);
+
+ fclose(fp);
+}
+
+void RGBImage::write_jpg(const char *filename, int quality) {
+ struct jpeg_compress_struct cinfo;
+ struct my_error_mgr jerr;
+ FILE *outfile; /* target file */
+ JSAMPARRAY buffer; /* Output row buffer */
+
+ jpeg_create_compress (&cinfo);
+
+ if ((outfile = fopen (filename, "wb")) == 0) {
+ fprintf (stderr, "Can't open %s\n", filename);
+ exit(1);
+ }
+
+ cinfo.err = jpeg_std_error (&jerr.pub);
+ jerr.pub.error_exit = my_error_exit;
+
+ if (setjmp (jerr.setjmp_buffer)) {
+ jpeg_destroy_compress (&cinfo);
+ fclose (outfile);
+ exit(1);
+ }
+
+ jpeg_stdio_dest (&cinfo, outfile);
+
+ cinfo.image_width = _width;
+ cinfo.image_height = _height;
+ cinfo.input_components = RGB_DEPTH;
+
+ cinfo.in_color_space = JCS_RGB;
+
+ jpeg_set_defaults (&cinfo);
+ jpeg_set_quality (&cinfo, quality, TRUE);
+ jpeg_start_compress (&cinfo, TRUE);
+ int y = 0;
+ buffer =
+ (*cinfo.mem->alloc_sarray) ((j_common_ptr) & cinfo, JPOOL_IMAGE,
+ _width * RGB_DEPTH, 1);
+ while (int(cinfo.next_scanline) < _height) {
+ for(int d = 0; d < RGB_DEPTH; d++)
+ for(int x = 0; x < _width; x++)
+ buffer[0][x * RGB_DEPTH + d] =
+ (JSAMPLE) ((_bit_map[x + _width * (y + _height * d)] * (MAXJSAMPLE + 1)) / 255);
+ jpeg_write_scanlines (&cinfo, buffer, 1);
+ y++;
+ }
+
+ jpeg_finish_compress (&cinfo);
+ fclose (outfile);
+
+ jpeg_destroy_compress (&cinfo);
+}
+
+void RGBImage::read_jpg(const char *filename) {
+ struct jpeg_decompress_struct cinfo;
+ struct my_error_mgr jerr;
+ FILE *infile;
+ JSAMPARRAY buffer;
+
+ deallocate();
+
+ if ((infile = fopen (filename, "rb")) == 0) {
+ fprintf (stderr, "can't open %s\n", filename);
+ return;
+ }
+
+ cinfo.err = jpeg_std_error (&jerr.pub);
+ jerr.pub.error_exit = my_error_exit;
+
+ if (setjmp (jerr.setjmp_buffer)) {
+ jpeg_destroy_decompress (&cinfo);
+ fclose (infile);
+ delete[] _bit_map;
+ _width = 0;
+ _height = 0;
+ _bit_map = 0;
+ return;
+ }
+
+ jpeg_create_decompress (&cinfo);
+ jpeg_stdio_src (&cinfo, infile);
+ jpeg_read_header (&cinfo, TRUE);
+ jpeg_start_decompress (&cinfo);
+
+ _width = cinfo.output_width;
+ _height = cinfo.output_height;
+ int depth = cinfo.output_components;
+
+ allocate();
+
+ buffer =
+ (*cinfo.mem->alloc_sarray) ((j_common_ptr) & cinfo, JPOOL_IMAGE,
+ _width * depth, 1);
+
+ int y = 0;
+ while (cinfo.output_scanline < cinfo.output_height) {
+ jpeg_read_scanlines (&cinfo, buffer, 1);
+ if(depth == 1) {
+ for(int d = 0; d < RGB_DEPTH; d++)
+ for(int x = 0; x < _width; x++)
+ _bit_plans[d][y][x] =
+ (unsigned char) ((buffer[0][x * depth] * 255) / (MAXJSAMPLE + 1));
+ } else {
+ for(int d = 0; d < depth; d++)
+ for(int x = 0; x < _width; x++)
+ _bit_plans[d][y][x] =
+ (unsigned char) ((buffer[0][x * depth + d] * 255) / (MAXJSAMPLE + 1));
+ }
+ y++;
+ }
+
+ jpeg_finish_decompress (&cinfo);
+ jpeg_destroy_decompress (&cinfo);
+
+ fclose (infile);
+}
+
+void RGBImage::draw_line(int thickness,
+ unsigned char r, unsigned char g, unsigned char b,
+ scalar_t x0, scalar_t y0, scalar_t x1, scalar_t y1) {
+ int l = 0;
+ int dx, dy, h, v;
+ int ix0 = int(x0 + 0.5), iy0 = int(y0 + 0.5), ix1 = int(x1 + 0.5), iy1 = int(y1 + 0.5);
+
+ if(ix0 < ix1) { dx = 1; h = ix1 - ix0; } else { dx = -1; h = ix0 - ix1; }
+ if(iy0 < iy1) { dy = 1; v = iy1 - iy0; } else { dy = -1; v = iy0 - iy1; }
+
+ int x = ix0, y = iy0;
+
+ if(h > v) {
+ for(int i = 0; i < h + 1; i++) {
+ for(int ex = - thickness / 2 - 1; ex < (thickness + 1) / 2 + 1; ex++) {
+ for(int ey = - thickness / 2 - 1; ey < (thickness + 1) / 2 + 1; ey++) {
+ if(ex * ex + ey * ey <= thickness * thickness / 4) {
+ int xx = x + ex, yy = y + ey;
+ if(xx >= 0 && xx < _width && yy >= 0 && yy < _height)
+ set_pixel(xx, yy, r, g, b);
+ }
+ }
+ }
+
+ x += dx; l += v;
+ if(l > 0) { y += dy; l -= h; }
+ }
+
+ } else {
+
+ for(int i = 0; i < v + 1; i++) {
+ for(int ex = - thickness / 2 - 1; ex < (thickness + 1) / 2 + 1; ex++) {
+ for(int ey = - thickness / 2 - 1; ey < (thickness + 1) / 2 + 1; ey++) {
+ if(ex * ex + ey * ey <= thickness * thickness / 4) {
+ int xx = x + ex, yy = y + ey;
+ if(xx >= 0 && xx < _width && yy >= 0 && yy < _height)
+ set_pixel(xx, yy, r, g, b);
+ }
+ }
+ }
+
+ y += dy; l -= h;
+ if(l < 0) { x += dx; l += v; }
+ }
+
+ }
+
+}
+
+void RGBImage::draw_ellipse(int thickness,
+ unsigned char r, unsigned char g, unsigned char b,
+ scalar_t xc, scalar_t yc, scalar_t radius_1, scalar_t radius_2, scalar_t tilt) {
+ scalar_t ux1 = cos(tilt) * radius_1, uy1 = sin(tilt) * radius_1;
+ scalar_t ux2 = - sin(tilt) * radius_2, uy2 = cos(tilt) * radius_2;
+
+ const int nb_points_to_draw = 80;
+ scalar_t x, y, px = 0, py = 0;
+
+ for(int i = 0; i <= nb_points_to_draw; i++) {
+ scalar_t alpha = (M_PI * 2 * scalar_t(i)) / scalar_t(nb_points_to_draw);
+
+ x = xc + cos(alpha) * ux1 + sin(alpha) * ux2;
+ y = yc + cos(alpha) * uy1 + sin(alpha) * uy2;
+
+ if(i > 0) {
+ draw_line(thickness, r, g, b, px, py, x, y);
+ }
+
+ px = x; py = y;
+ }
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+// A simple color image class
+
+#ifndef RGB_IMAGE_H
+#define RGB_IMAGE_H
+
+#include "misc.h"
+
+class RGBImage {
+protected:
+ int _width, _height;
+ unsigned char ***_bit_plans, **_bit_lines, *_bit_map;
+ static const int RED = 0;
+ static const int GREEN = 1;
+ static const int BLUE = 2;
+ static const int RGB_DEPTH = 3;
+
+ void allocate();
+ void deallocate();
+
+public:
+
+ RGBImage();
+ RGBImage(int width, int height);
+ RGBImage(RGBImage *image, scalar_t scale);
+ virtual ~RGBImage();
+
+ inline int width() const { return _width; }
+ inline int height() const { return _height; }
+
+ inline void set_pixel(int x, int y, unsigned char r, unsigned char g, unsigned char b) {
+ ASSERT(x >= 0 && x < _width && y >= 0 && y < _height);
+ _bit_plans[RED][y][x] = r;
+ _bit_plans[GREEN][y][x] = g;
+ _bit_plans[BLUE][y][x] = b;
+ }
+
+ inline unsigned char pixel(int x, int y, int d) {
+ ASSERT(x >= 0 && x < _width && y >= 0 && y < _height && d >= 0 && d < RGB_DEPTH);
+ return _bit_plans[d][y][x];
+ }
+
+ virtual void read_ppm(const char *filename);
+ virtual void write_ppm(const char *filename);
+
+ virtual void read_png(const char *filename);
+ virtual void write_png(const char *filename);
+
+ virtual void read_jpg(const char *filename);
+ virtual void write_jpg(const char *filename, int quality);
+
+ virtual void draw_line(int thickness,
+ unsigned char r, unsigned char g, unsigned char b,
+ scalar_t x0, scalar_t y0, scalar_t x1, scalar_t y1);
+
+ virtual void draw_ellipse(int thickness,
+ unsigned char r, unsigned char g, unsigned char b,
+ scalar_t xc, scalar_t yc, scalar_t radius_1, scalar_t radius_2, scalar_t tilt);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "rgb_image_subpixel.h"
+
+RGBImageSubpixel::RGBImageSubpixel(int width, int height) : RGBImage(width * _scale, height* _scale) { }
+
+RGBImageSubpixel::RGBImageSubpixel(RGBImage *image) : RGBImage(image->width() * _scale, image->height() * _scale) {
+ for(int y = 0; y < _height; y++) {
+ for(int x = 0; x < _width; x++) {
+ set_pixel(x, y,
+ image->pixel(x / _scale, y / _scale, RGBImage::RED),
+ image->pixel(x / _scale, y / _scale, RGBImage::GREEN),
+ image->pixel(x / _scale, y / _scale, RGBImage::BLUE));
+ }
+ }
+}
+
+RGBImageSubpixel::~RGBImageSubpixel() { }
+
+void RGBImageSubpixel::read_ppm(const char *filename) {
+ abort();
+}
+
+void RGBImageSubpixel::write_ppm(const char *filename) {
+ abort();
+}
+
+
+void RGBImageSubpixel::read_png(const char *filename) {
+ abort();
+}
+
+void RGBImageSubpixel::write_png(const char *filename) {
+ RGBImage tmp(_width / _scale, _height / _scale);
+ for(int y = 0; y < _height / _scale; y++) {
+ for(int x = 0; x < _width / _scale; x++) {
+ int sr = 0, sg = 0, sb = 0;
+ for(int yy = y * _scale; yy < (y + 1) * _scale; yy++) {
+ for(int xx = x * _scale; xx < (x + 1) * _scale; xx++) {
+ sr += int(_bit_plans[RED][yy][xx]);
+ sg += int(_bit_plans[GREEN][yy][xx]);
+ sb += int(_bit_plans[BLUE][yy][xx]);
+ }
+ }
+
+ tmp.set_pixel(x, y,
+ sr / (_scale * _scale), sg / (_scale * _scale), sb / (_scale * _scale));
+ }
+ }
+ tmp.write_png(filename);
+}
+
+
+void RGBImageSubpixel::read_jpg(const char *filename) {
+ abort();
+}
+
+void RGBImageSubpixel::write_jpg(const char *filename, int quality) {
+ abort();
+}
+
+
+void RGBImageSubpixel::draw_line(int thickness,
+ unsigned char r, unsigned char g, unsigned char b,
+ scalar_t x0, scalar_t y0, scalar_t x1, scalar_t y1) {
+ RGBImage::draw_line(int(thickness * _scale),
+ r, g, b,
+ x0 * _scale + _scale/2, y0 * _scale + _scale/2,
+ x1 * _scale + _scale/2, y1 * _scale + _scale/2);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+// A simple color image class
+
+#ifndef RGB_IMAGE_SUBPIXEL_H
+#define RGB_IMAGE_SUBPIXEL_H
+
+#include "misc.h"
+#include "rgb_image.h"
+
+class RGBImageSubpixel : public RGBImage {
+ static const int _scale = 8;
+public:
+
+ RGBImageSubpixel(int width, int height);
+ RGBImageSubpixel(RGBImage *image);
+ virtual ~RGBImageSubpixel();
+
+ inline int width() const { return _width / _scale; }
+ inline int height() const { return _height / _scale; }
+
+ virtual void read_ppm(const char *filename);
+ virtual void write_ppm(const char *filename);
+
+ virtual void read_png(const char *filename);
+ virtual void write_png(const char *filename);
+
+ virtual void read_jpg(const char *filename);
+ virtual void write_jpg(const char *filename, int quality);
+
+ virtual void draw_line(int thickness,
+ unsigned char r, unsigned char g, unsigned char b,
+ scalar_t x0, scalar_t y0, scalar_t x1, scalar_t y1);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include <string.h>
+
+#include "rich_image.h"
+
+#define PIXEL_DELTA(a, b) ((a) >= (b) ? (a) - (b) : (b) - (a))
+
+static inline bool edge( unsigned char v0, unsigned char v1,
+ unsigned char v2, unsigned char v3, unsigned char v4, unsigned char v5,
+ unsigned char v6, unsigned char v7) {
+ unsigned char g = PIXEL_DELTA(v3, v4);
+
+ return
+ g > 8 &&
+ g > PIXEL_DELTA(v0, v3) &&
+ g > PIXEL_DELTA(v1, v4) &&
+ g > PIXEL_DELTA(v2, v3) &&
+ g > PIXEL_DELTA(v4, v5) &&
+ g > PIXEL_DELTA(v3, v6) &&
+ g > PIXEL_DELTA(v4, v7);
+}
+
+void RichImage::free() {
+ delete[] _edge_map;
+ _edge_map = 0;
+ delete[] _line_edge_map;
+ _line_edge_map = 0;
+ delete[] _tag_edge_map;
+ _tag_edge_map = 0;
+ delete[] _scale_edge_map;
+ _scale_edge_map = 0;
+ delete[] _width_at_scale;
+ _width_at_scale = 0;
+ delete[] _height_at_scale;
+ _height_at_scale = 0;
+}
+
+void RichImage::compute_one_scale_edge_maps(int width, int height,
+ unsigned int ***scale_edge_map,
+ unsigned char *pixel_map,
+ unsigned int *sum_pixel_map,
+ unsigned int *sum_sq_pixel_map) {
+
+ unsigned char d00, d01, d02, d03, d04, d05, d06, d07;
+ unsigned char d08, d09, d10, d11, d12, d13, d14, d15;
+ unsigned char *local_pixel_map;
+ unsigned int *local_sum_pixel_map, *local_sum_sq_pixel_map;
+
+ // Compute the integral images
+
+ {
+ unsigned int *sp = sum_pixel_map, *ssp = sum_sq_pixel_map;
+ unsigned char *p = pixel_map;
+ for(int x = 0; x < width; x++) {
+ *sp++ = 0;
+ *ssp++ = 0;
+ p++;
+ }
+
+ for(int y = 1; y < height; y++) {
+ *sp++ = 0;
+ *ssp++ = 0;
+ p++;
+ for(int x = 1; x < width; x++) {
+ *sp = *(sp - width) + *(sp - 1) - *(sp - width - 1) + ((unsigned int) (*p));
+ *ssp = *(ssp - width) + *(ssp - 1) - *(ssp - width - 1) + sq((unsigned int) (*p));
+ sp++;
+ ssp++;
+ p++;
+ }
+ }
+ }
+
+ const unsigned int var_square_size = 16;
+
+ int k00 = - 2 + width * (- 2);
+ int k01 = - 1 + width * (- 2);
+ int k02 = + 0 + width * (- 2);
+ int k03 = + 1 + width * (- 2);
+ int k04 = - 2 + width * (- 1);
+ int k05 = - 1 + width * (- 1);
+ int k06 = + 0 + width * (- 1);
+ int k07 = + 1 + width * (- 1);
+ int k08 = - 2 + width * (+ 0);
+ int k09 = - 1 + width * (+ 0);
+ int k10 = + 0 + width * (+ 0);
+ int k11 = + 1 + width * (+ 0);
+ int k12 = - 2 + width * (+ 1);
+ int k13 = - 1 + width * (+ 1);
+ int k14 = + 0 + width * (+ 1);
+ int k15 = + 1 + width * (+ 1);
+
+ unsigned int *edge_map0 = scale_edge_map[0][0];
+ unsigned int *edge_map1 = scale_edge_map[1][0];
+ unsigned int *edge_map2 = scale_edge_map[2][0];
+ unsigned int *edge_map3 = scale_edge_map[3][0];
+ unsigned int *edge_map4 = scale_edge_map[4][0];
+ unsigned int *edge_map5 = scale_edge_map[5][0];
+ unsigned int *edge_map6 = scale_edge_map[6][0];
+ unsigned int *edge_map7 = scale_edge_map[7][0];
+ unsigned int *variance_map = scale_edge_map[8][0];
+
+ int a = -1 - width, b = - width, c = -1, d = 0;
+
+ local_pixel_map = pixel_map;
+ local_sum_pixel_map = sum_pixel_map;
+ local_sum_sq_pixel_map = sum_sq_pixel_map;
+
+ const int gray_bin_width = 256 / nb_gray_tags;
+
+ for(int y = 0; y < height; y++) {
+
+ for(int x = 0; x < width; x++) {
+
+ if(x == 0 || y == 0) {
+ for(int e = 0; e < _nb_tags; e++)
+ scale_edge_map[e][0][d] = 0;
+ } else {
+ for(int e = 0; e < _nb_tags; e++)
+ scale_edge_map[e][0][d] =
+ scale_edge_map[e][0][b] +
+ scale_edge_map[e][0][c] -
+ scale_edge_map[e][0][a];
+ }
+
+ scale_edge_map[first_gray_tag +
+ (local_pixel_map[0] / gray_bin_width)][0][d]++;
+
+ if(x - int(var_square_size/2) >= 0 &&
+ x + int(var_square_size/2) < width &&
+ y - int(var_square_size/2) >= 0 &&
+ y + int(var_square_size/2) < height) {
+
+ unsigned int s =
+ + local_sum_pixel_map[ - var_square_size/2 + width * ( - var_square_size / 2)]
+ + local_sum_pixel_map[ + var_square_size/2 + width * ( + var_square_size / 2)]
+ - local_sum_pixel_map[ - var_square_size/2 + width * ( + var_square_size / 2)]
+ - local_sum_pixel_map[ + var_square_size/2 + width * ( - var_square_size / 2)];
+
+ unsigned int s_sq =
+ + local_sum_sq_pixel_map[ - var_square_size/2 + width * ( - var_square_size / 2)]
+ + local_sum_sq_pixel_map[ + var_square_size/2 + width * ( + var_square_size / 2)]
+ - local_sum_sq_pixel_map[ - var_square_size/2 + width * ( + var_square_size / 2)]
+ - local_sum_sq_pixel_map[ + var_square_size/2 + width * ( - var_square_size / 2)];
+
+ if(sq(var_square_size) * s_sq - sq(s) >=
+ 100 * sq(var_square_size) * (sq(var_square_size) - 1)) {
+
+ d00 = local_pixel_map[k00];
+ d01 = local_pixel_map[k01];
+ d02 = local_pixel_map[k02];
+ d03 = local_pixel_map[k03];
+
+ d04 = local_pixel_map[k04];
+ d05 = local_pixel_map[k05];
+ d06 = local_pixel_map[k06];
+ d07 = local_pixel_map[k07];
+
+ d08 = local_pixel_map[k08];
+ d09 = local_pixel_map[k09];
+ d10 = local_pixel_map[k10];
+ d11 = local_pixel_map[k11];
+
+ d12 = local_pixel_map[k12];
+ d13 = local_pixel_map[k13];
+ d14 = local_pixel_map[k14];
+ d15 = local_pixel_map[k15];
+
+ /*
+
+ #0 #1 #2 #3
+
+ XXXXXX .XXXXX ...XXX .....X
+ XXXXXX ..XXXX ...XXX ....XX
+ ...... ...XXX ...XXX ...XXX
+ ...... ....XX ...XXX ..XXXX
+
+ #4 #5 #6 #7
+
+ ...... X..... XXX... XXXXX.
+ ...... XX.... XXX... XXXX..
+ XXXXXX XXX... XXX... XXX...
+ XXXXXX XXXX.. XXX... XX....
+
+ */
+
+ if(edge(d04, d08, d01, d05, d09, d13, d06, d10)) {
+ if(d05 < d09) edge_map0[d]++;
+ else edge_map4[d]++;
+ }
+
+ if(edge(d02, d07, d00, d05, d10, d15, d08, d13)) {
+ if(d05 < d10) edge_map7[d]++;
+ else edge_map3[d]++;
+ }
+
+ if(edge(d01, d02, d04, d05, d06, d07, d09, d10)) {
+ if(d05 < d06) edge_map6[d]++;
+ else edge_map2[d]++;
+ }
+
+ if(edge(d01, d04, d03, d06, d09, d12, d11, d14)) {
+ if(d06 < d09) edge_map1[d]++;
+ else edge_map5[d]++;
+ }
+
+ variance_map[d]++;
+ }
+ }
+
+ a++; b++; c++; d++;
+ local_pixel_map++;
+ local_sum_pixel_map++;
+ local_sum_sq_pixel_map++;
+ }
+ }
+}
+
+void RichImage::compute_rich_structure() {
+ free();
+
+ unsigned char *pixel_maps;
+ unsigned char **line_pixel_maps;
+ unsigned char ***scale_pixel_maps;
+
+ _nb_scales =
+ int(global.nb_scales_per_power_of_two
+ * log(scalar_t(min(_width, _height))/scalar_t(_image_size_min)) / log(2)) + 1;
+
+ _width_at_scale = new int[_nb_scales];
+ _height_at_scale = new int[_nb_scales];
+ int total_surface = 0, total_lines = 0;
+
+ // Compute the sizes of the image at various scales
+
+ scalar_t rho = exp(log(0.5) / scalar_t(global.nb_scales_per_power_of_two));
+ scalar_t factor = 1.0;
+
+ for(int s = 0; s < _nb_scales; s++) {
+ _width_at_scale[s] = int(scalar_t(_width) * factor);
+ _height_at_scale[s] = int(scalar_t(_height) * factor);
+ total_surface += _width_at_scale[s] * _height_at_scale[s];
+ total_lines += _height_at_scale[s];
+ factor *= rho;
+ }
+
+ // Allocate the memory for the various scales and set up the pointer
+ // arrays to speed-up accesses
+
+ pixel_maps = new unsigned char[total_surface];
+ memset(pixel_maps, 0, sizeof(unsigned char) * total_surface);
+ line_pixel_maps = new unsigned char *[total_lines];
+ scale_pixel_maps = new unsigned char **[_nb_scales];
+
+ int sum_surfaces, sum_heights;
+
+ sum_surfaces = 0;
+ sum_heights = 0;
+
+ for(int s = 0; s < _nb_scales; s++) {
+ scale_pixel_maps[s] = line_pixel_maps + sum_heights;
+ for(int y = 0; y < _height_at_scale[s]; y++) {
+ scale_pixel_maps[s][y] = pixel_maps + sum_surfaces;
+ sum_surfaces += _width_at_scale[s];
+ }
+ sum_heights += _height_at_scale[s];
+ }
+
+ _edge_map = new unsigned int[total_surface * _nb_tags];
+ memset(_edge_map, 0, sizeof(unsigned int) * total_surface * _nb_tags);
+ _line_edge_map = new unsigned int *[total_lines * _nb_tags];
+ _tag_edge_map = new unsigned int **[_nb_tags * _nb_scales];
+ _scale_edge_map = new unsigned int ***[_nb_scales];
+
+ sum_surfaces = 0;
+ sum_heights = 0;
+
+ for(int s = 0; s < _nb_scales; s++) {
+ _scale_edge_map[s] = _tag_edge_map + s * _nb_tags;
+ for(int t = 0; t < _nb_tags; t++) {
+ _scale_edge_map[s][t] = _line_edge_map + sum_heights;
+ for(int y = 0; y < _height_at_scale[s]; y++) {
+ _scale_edge_map[s][t][y] = _edge_map + sum_surfaces;
+ sum_surfaces += _width_at_scale[s];
+ }
+ sum_heights += _height_at_scale[s];
+ }
+ }
+
+ // Compute the scaled images per se
+
+ for(int s = 0; s < _nb_scales; s++) {
+
+ if(s < global.nb_scales_per_power_of_two) {
+
+ if(s == 0) {
+
+ // The first image is not rescaled
+
+ for(int y = 0; y < _height_at_scale[s]; y++)
+ for(int x = 0; x < _width_at_scale[s]; x++)
+ scale_pixel_maps[s][y][x] = _content[x + _width * y];
+
+ } else {
+
+ // The nb_scales_per_power_of_two - 1 first images are
+ // generated with a 'complex' scaling, and then we just
+ // downscale by a factor of two
+
+ const int ld = 3;
+
+ int delta_column[_width_at_scale[s] << ld];
+ for(int xx = 0; xx < _width_at_scale[s] << ld; xx++)
+ delta_column[xx] = (xx * _width_at_scale[0]) / (_width_at_scale[s] << ld);
+
+ unsigned char *line[_height_at_scale[s] << ld];
+ for(int yy = 0; yy < _height_at_scale[s] << ld; yy++)
+ line[yy] = scale_pixel_maps[0][(yy * _height_at_scale[0]) / (_height_at_scale[s] << ld)];
+
+ for(int y = 0; y < _height_at_scale[s]; y++) {
+ unsigned char *dest_line = scale_pixel_maps[s][y];
+ for(int x = 0; x < _width_at_scale[s]; x++) {
+ int u = 0;
+ for(int yy = (y << ld); yy < ((y+1) << ld); yy++)
+ for(int xx = (x << ld); xx < ((x+1) << ld); xx++)
+ u += line[yy][delta_column[xx]];
+ dest_line[x] = (u >> (2 * ld));
+ }
+ }
+ }
+ } else {
+
+ // Other scales are computed by halfing one of the already
+ // computed scales
+
+ int r = s - global.nb_scales_per_power_of_two;
+ for(int y = 0; y < _height_at_scale[s]; y++)
+ for(int x = 0; x < _width_at_scale[s]; x++)
+ scale_pixel_maps[s][y][x] =
+ (int(scale_pixel_maps[r][y*2 + 0][x*2 + 0]) +
+ int(scale_pixel_maps[r][y*2 + 0][x*2 + 1]) +
+ int(scale_pixel_maps[r][y*2 + 1][x*2 + 0]) +
+ int(scale_pixel_maps[r][y*2 + 1][x*2 + 1])) >> 2;
+ }
+
+ }
+
+ // Allocate the memory for the edge maps at various scales and set
+ // up the pointer arrays to speed-up accesses
+
+ unsigned int *sum_pixel_map = new unsigned int[_width * _height];
+ unsigned int *sum_sq_pixel_map = new unsigned int[_width * _height];
+
+ for(int s = 0; s < _nb_scales; s++)
+ compute_one_scale_edge_maps(_width_at_scale[s],
+ _height_at_scale[s],
+ _scale_edge_map[s],
+ scale_pixel_maps[s][0],
+ sum_pixel_map,
+ sum_sq_pixel_map);
+
+ delete[] sum_pixel_map;
+ delete[] sum_sq_pixel_map;
+
+ delete[] pixel_maps;
+ delete[] line_pixel_maps;
+ delete[] scale_pixel_maps;
+}
+
+void RichImage::crop(int xmin, int ymin, int width, int height) {
+ free();
+ Image::crop(xmin, ymin, width, height);
+}
+
+RichImage::RichImage() : Image() {
+ _width_at_scale = 0;
+ _height_at_scale = 0;
+ _edge_map = 0;
+ _line_edge_map = 0;
+ _tag_edge_map = 0;
+ _scale_edge_map = 0;
+}
+
+RichImage::RichImage(int width, int height) : Image(width, height) {
+ _width_at_scale = 0;
+ _height_at_scale = 0;
+ _edge_map = 0;
+ _line_edge_map = 0;
+ _tag_edge_map = 0;
+ _scale_edge_map = 0;
+}
+
+RichImage::~RichImage() {
+ delete[] _edge_map;
+ delete[] _line_edge_map;
+ delete[] _tag_edge_map;
+ delete[] _scale_edge_map;
+ delete[] _width_at_scale;
+ delete[] _height_at_scale;
+}
+
+void RichImage::write(ostream *out) {
+ Image::write(out);
+}
+
+void RichImage::read(istream *in) {
+ Image::read(in);
+}
+
+void RichImage::write_tag_png(const char *filename) {
+ const int nb_cols= 4;
+ const int nb_rows = ((nb_tags() + nb_cols - 1) / nb_cols);
+
+ int height_total = 0;
+ for(int s = 0; s < _nb_scales; s++) height_total += _height_at_scale[s];
+
+ RGBImage image(_width * nb_cols, height_total * nb_rows);
+
+ for(int y = 0; y < image.height(); y++) for(int x = 0; x < image.width(); x++)
+ image.set_pixel(x, y, 0, 255, 0);
+
+ int sum_height = 0;
+
+ for(int s = 0; s < _nb_scales; s++) {
+ for(int t = 0; t < nb_tags(); t++) {
+ int x0 = (t%nb_cols) * _width, y0 = sum_height + (t/nb_cols) * _height_at_scale[s];
+ for(int y = 0; y < _height_at_scale[s]; y++) for(int x = 0; x < _width_at_scale[s]; x++) {
+ int c = 255 - min(255, max(0, int(255 * nb_tags_in_window(s, t, x, y, x+1, y+1))));
+ image.set_pixel(x0 + x, y0 + y, c, c, c);
+ }
+ }
+ sum_height += nb_rows * _height_at_scale[s];
+ }
+
+ image.write_png(filename);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+
+ This class implements the multi-scale basic edge features on the
+ images. The heavy machinery and ugly coding style is motivated by
+ performance.
+
+*/
+
+#ifndef RICH_IMAGE_H
+#define RICH_IMAGE_H
+
+#include "image.h"
+#include "rgb_image.h"
+#include "misc.h"
+#include "global.h"
+
+class RichImage : public Image {
+
+ static const int _image_size_min = 8;
+
+ int _nb_scales;
+
+ int *_width_at_scale, *_height_at_scale;
+
+ unsigned int *_edge_map;
+
+ unsigned int **_line_edge_map;
+ unsigned int ***_tag_edge_map;
+ unsigned int ****_scale_edge_map;
+
+ void free();
+
+ void compute_one_scale_edge_maps(int width, int height,
+ unsigned int ***scale_edge_map,
+ unsigned char *pixel_map,
+ unsigned int *sum_pixel_map,
+ unsigned int *sum_sq_pixel_map);
+
+public:
+
+ // We have 8 edge orientations
+ static const int nb_edge_tags = 8;
+ static const int first_edge_tag = 0;
+
+ // The variance tag is 1 if the variance of the gray levels in the
+ // 16x16 square is greater than 10
+ static const int variance_tag = 8;
+
+ // We quantify the grayscales into 8 bins
+ static const int nb_gray_tags = 8;
+ static const int first_gray_tag = 9;
+
+ static const int _nb_tags = nb_edge_tags + 1 + nb_gray_tags;
+
+ inline int nb_tags() { return _nb_tags; }
+ inline int nb_scales() { return _nb_scales; }
+
+ inline int nb_tags_in_window(int scale, int tag, int xmin, int ymin, int xmax, int ymax) {
+ if(scale < 0 || scale >= _nb_scales) return 0;
+ if(xmin < 0) xmin = 0;
+ if(xmax >= _width_at_scale[scale]) xmax = _width_at_scale[scale] - 1;
+ if(ymin < 0) ymin = 0;
+ if(ymax >= _height_at_scale[scale]) ymax = _height_at_scale[scale] - 1;
+ if(xmin < xmax && ymin < ymax) {
+ unsigned int **tmp = _scale_edge_map[scale][tag];
+ return tmp[ymax][xmax] + tmp[ymin][xmin] - tmp[ymax][xmin] - tmp[ymin][xmax];
+ } else
+ return 0;
+ }
+
+ RichImage();
+ RichImage(int width, int height);
+ virtual ~RichImage();
+
+ virtual void compute_rich_structure();
+ virtual void crop(int xmin, int ymin, int width, int height);
+
+ virtual void write(ostream *out);
+
+ // read does _NOT_ build the rich structure. One has to call
+ // compute_rich_structure() for that!
+ virtual void read(istream *in);
+
+ virtual void write_tag_png(const char *filename);
+};
+
+#endif
--- /dev/null
+#!/bin/bash
+
+#########################################################################
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the version 3 of the GNU General Public License #
+# as published by the Free Software Foundation. #
+# #
+# This program is distributed in the hope that it will be useful, but #
+# WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
+# General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+# Written by Francois Fleuret, (C) IDIAP #
+# Contact <francois.fleuret@idiap.ch> for comments & bug reports #
+#########################################################################
+
+MAIN_URL="http://www.idiap.ch/folded-ctf"
+
+# Compiling
+
+make -j -k
+
+echo
+
+# Generating the pool file
+
+DATA_PATH=./rmk-data
+POOL_NAME=${DATA_PATH}/rmk.pool
+
+if [[ -d ${DATA_PATH} ]]; then
+
+ if [[ -f ${POOL_NAME} ]]; then
+
+ echo "The pool file exists."
+
+ else
+
+ echo "Can not find the pool file, checking the data integrity."
+
+ md5sum -c ${DATA_PATH}/list.md5
+
+ if [[ $? != 0 ]]; then
+ echo "The data set is corrupted. You can download it" >&2
+ echo "from ${MAIN_URL}" >&2
+ exit 1
+ fi
+
+ echo "Generating the pool file."
+
+ ./list_to_pool ${DATA_PATH}/full.lst ${DATA_PATH} ${POOL_NAME}.wrk
+
+ if [[ $? == 0 ]]; then
+ mv ${POOL_NAME}.wrk ${POOL_NAME}
+ else
+ \rm ${POOL_NAME}.wrk 2> /dev/null
+ echo "Pool generation failed." >&2
+ exit 1
+ fi
+
+ fi
+
+else
+
+ echo "Can not find the RateMyKitten images in ${DATA_PATH}. You can" >&2
+ echo "download them from ${MAIN_URL}" >&2
+ exit 1
+
+fi
+
+# Running the computation per se
+
+RESULT_DIR=./results
+
+if [[ ! -d ${RESULT_DIR} ]]; then
+ mkdir ${RESULT_DIR}
+fi
+
+for SEED in {0..9}; do
+
+ for MODE in hb h+b; do
+
+ EXPERIMENT_RESULT_DIR="${RESULT_DIR}/${MODE}-${SEED}"
+
+ mkdir ${EXPERIMENT_RESULT_DIR} 2> /dev/null
+
+ if [[ $? == 0 ]]; then
+
+ OPTS="--random-seed=${SEED} --wanted-true-positive-rate=0.75"
+
+ if [[ $MODE == "h+b" ]]; then
+ OPTS="${OPTS} --force-head-belly-independence=yes"
+ fi
+
+ if [[ $1 == "light" ]]; then
+ OPTS="${OPTS} --nb-classifiers-per-level=1 --nb-weak-learners-per-classifier=10"
+ OPTS="${OPTS} --proportion-for-train=0.1 --proportion-for-validation=0.1 --proportion-for-test=0.1"
+ fi
+
+ ./folding \
+ --niceness=15 \
+ --pool-name=${POOL_NAME} \
+ --nb-levels=2 \
+ --nb-classifiers-per-level=25 --nb-weak-learners-per-classifier=100 \
+ --result-path=${EXPERIMENT_RESULT_DIR} \
+ --detector-name=${EXPERIMENT_RESULT_DIR}/default.det \
+ ${OPTS} \
+ open-pool \
+ train-detector \
+ compute-thresholds \
+ write-detector \
+ sequence-test-detector | tee -a ${EXPERIMENT_RESULT_DIR}/stdout
+
+ else
+
+ echo "${EXPERIMENT_RESULT_DIR} exists, aborting experiment."
+
+ fi
+
+ done
+
+done
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "sample_set.h"
+
+SampleSet::SampleSet(int nb_features, int nb_samples) {
+ _nb_features = nb_features;
+ _nb_samples = nb_samples;
+ _shared_feature_values = new SharedResponses(_nb_features, _nb_samples);
+ _shared_feature_values->grab();
+
+ _labels = new int[_nb_samples];
+ _feature_values = new scalar_t *[_nb_samples];
+ for(int s = 0; s < _nb_samples; s++)
+ _feature_values[s] = _shared_feature_values->_responses + _nb_features * s;
+
+}
+
+SampleSet::SampleSet(SampleSet *father, int nb, int *indexes) {
+ _nb_features = father->_nb_features;
+ _nb_samples = nb;
+ _shared_feature_values = father->_shared_feature_values;
+ _shared_feature_values->grab();
+
+ _labels = new int[_nb_samples];
+ _feature_values = new scalar_t *[_nb_samples];
+ for(int s = 0; s < _nb_samples; s++) {
+ _feature_values[s] = father->_feature_values[indexes[s]];
+ _labels[s] = father->_labels[indexes[s]];
+ }
+}
+
+SampleSet::~SampleSet() {
+ _shared_feature_values->release();
+ delete[] _feature_values;
+ delete[] _labels;
+}
+
+void SampleSet::set_sample(int n,
+ PiFeatureFamily *pi_feature_family,
+ RichImage *image,
+ PoseCell *cell, int label) {
+ ASSERT(n >= 0 && n < _nb_samples);
+ _labels[n] = label;
+ PiReferential referential(cell);
+ for(int f = 0; f < _nb_features; f++) {
+ _feature_values[n][f] = pi_feature_family->get_feature(f)->response(image, &referential);
+ ASSERT(!isnan(_feature_values[n][f]));
+ }
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef SAMPLE_SET_H
+#define SAMPLE_SET_H
+
+#include "pose_cell.h"
+#include "pi_feature_family.h"
+#include "shared_responses.h"
+
+class SampleSet {
+ int _nb_features;
+ int _nb_samples;
+ SharedResponses *_shared_feature_values;
+ scalar_t **_feature_values;
+ int *_labels;
+
+public:
+
+ inline int nb_samples() { return _nb_samples; }
+ inline int nb_features() { return _nb_features; }
+
+ inline int label(int n_sample) {
+ ASSERT(n_sample >= 0 && n_sample < _nb_samples);
+ return _labels[n_sample];
+ }
+
+ inline scalar_t feature_value(int n_sample, int n_feature) {
+ ASSERT(n_sample >= 0 && n_sample < _nb_samples &&
+ n_feature >= 0 && n_feature < _nb_features);
+ ASSERT(!isnan(_feature_values[n_sample][n_feature]));
+ return _feature_values[n_sample][n_feature];
+ }
+
+ SampleSet(int nb_features, int nb_samples);
+ SampleSet(SampleSet *father, int nb, int *indexes);
+
+ ~SampleSet();
+
+ void set_sample(int n,
+ PiFeatureFamily *pi_feature_family,
+ RichImage *image,
+ PoseCell *cell,
+ int label);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "shared.h"
+
+Shared::Shared() : _nb_refs(0) { }
+
+Shared::~Shared() {
+ ASSERT(_nb_refs == 0);
+}
+
+void Shared::grab() {
+ _nb_refs++;
+}
+
+void Shared::release() {
+ ASSERT(_nb_refs > 0);
+ if(--_nb_refs == 0) delete this;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+// A tiny class to implement shared objects and lazy deletion
+
+// When you create a reference to such an object, call grab(), and
+// when you destroy that reference, call release() which will delete
+// it if no reference remains. Never delete it yourself!
+
+#ifndef SHARED_H
+#define SHARED_H
+
+#include "misc.h"
+
+class Shared {
+ int _nb_refs;
+public:
+ Shared();
+ virtual ~Shared();
+ void grab();
+ void release();
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "shared_responses.h"
+
+SharedResponses::SharedResponses(int nb_features, int nb_samples) {
+ _responses = new scalar_t[nb_samples * nb_features];
+}
+
+SharedResponses::~SharedResponses() {
+ delete[] _responses;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef SHARED_RESPONSES_H
+#define SHARED_RESPONSES_H
+
+#include "shared.h"
+
+class SharedResponses : public Shared {
+public:
+ scalar_t *_responses;
+ SharedResponses(int nb_features, int nb_samples);
+ ~SharedResponses();
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "storable.h"
+
+#include <fstream>
+#include <stdlib.h>
+
+Storable::~Storable() { }
+
+void Storable::read(const char *name) {
+ ifstream in(name);
+ if(in.fail()) {
+ cerr << "Can not open " << name << " for reading." << endl;
+ exit(1);
+ }
+ read(&in);
+}
+
+void Storable::write(const char *name) {
+ ofstream out(name);
+ if(out.fail()) {
+ cerr << "Can not open " << name << " for writing." << endl;
+ exit(1);
+ }
+ write(&out);
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef STORABLE_H
+#define STORABLE_H
+
+#include <iostream>
+
+using namespace std;
+
+class Storable {
+public:
+ virtual ~Storable();
+
+ virtual void read(istream *is) = 0;
+ virtual void write(ostream *os) = 0;
+
+ virtual void read(const char *name);
+ virtual void write(const char *name);
+};
+
+#endif
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#include "misc.h"
+#include "tools.h"
+#include "fusion_sort.h"
+
+scalar_t robust_sampling(int nb, scalar_t *weights, int nb_to_sample, int *sampled) {
+ ASSERT(nb > 0);
+ if(nb == 1) {
+ for(int k = 0; k < nb_to_sample; k++) sampled[k] = 0;
+ return weights[0];
+ } else {
+ scalar_t *pair_weights = new scalar_t[(nb+1)/2];
+ for(int k = 0; k < nb/2; k++)
+ pair_weights[k] = weights[2 * k] + weights[2 * k + 1];
+ if(nb%2)
+ pair_weights[(nb+1)/2 - 1] = weights[nb-1];
+ scalar_t result = robust_sampling((nb+1)/2, pair_weights, nb_to_sample, sampled);
+ for(int k = 0; k < nb_to_sample; k++) {
+ int s = sampled[k];
+ // There is a bit of a trick for the isolated sample in the odd
+ // case. Since the corresponding pair weight is the same as the
+ // one sample alone, the test is always true and the isolated
+ // sample will be taken for sure.
+ if(drand48() * pair_weights[s] <= weights[2 * s])
+ sampled[k] = 2 * s;
+ else
+ sampled[k] = 2 * s + 1;
+ }
+ delete[] pair_weights;
+ return result;
+ }
+}
+
+void print_roc_small_pos(ostream *out,
+ int nb_pos, scalar_t *pos_responses,
+ int nb_neg, scalar_t *neg_responses,
+ scalar_t fas_factor) {
+
+ scalar_t *sorted_pos_responses = new scalar_t[nb_pos];
+
+ fusion_sort(nb_pos, pos_responses, sorted_pos_responses);
+
+ int *bins = new int[nb_pos + 1];
+ for(int k = 0; k <= nb_pos; k++) bins[k] = 0;
+
+ for(int k = 0; k < nb_neg; k++) {
+ scalar_t r = neg_responses[k];
+
+ if(r < sorted_pos_responses[0])
+ bins[0]++;
+
+ else if(r >= sorted_pos_responses[nb_pos - 1])
+ bins[nb_pos]++;
+
+ else {
+ int a = 0;
+ int b = nb_pos - 1;
+ int c = 0;
+
+ while(a < b - 1) {
+ c = (a + b) / 2;
+ if(r < sorted_pos_responses[c])
+ b = c;
+ else
+ a = c;
+ }
+
+ // Beware of identical positive responses
+ while(c < nb_pos && r >= sorted_pos_responses[c])
+ c++;
+
+ bins[c]++;
+ }
+ }
+
+ int s = nb_neg;
+ for(int k = 0; k < nb_pos; k++) {
+ s -= bins[k];
+ if(k == 0 || sorted_pos_responses[k-1] < sorted_pos_responses[k]) {
+ (*out) << (scalar_t(s) / scalar_t(nb_neg)) * fas_factor
+ << " "
+ << scalar_t(nb_pos - k)/scalar_t(nb_pos)
+ << " "
+ << sorted_pos_responses[k]
+ << endl;
+ }
+ }
+
+ delete[] bins;
+ delete[] sorted_pos_responses;
+}
--- /dev/null
+
+///////////////////////////////////////////////////////////////////////////
+// This program is free software: you can redistribute it and/or modify //
+// it under the terms of the version 3 of the GNU General Public License //
+// as published by the Free Software Foundation. //
+// //
+// This program is distributed in the hope that it will be useful, but //
+// WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU //
+// General Public License for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see <http://www.gnu.org/licenses/>. //
+// //
+// Written by Francois Fleuret, (C) IDIAP //
+// Contact <francois.fleuret@idiap.ch> for comments & bug reports //
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef TOOLS_H
+#define TOOLS_H
+
+#include <iostream>
+#include "misc.h"
+
+scalar_t robust_sampling(int nb, scalar_t *weights, int nb_to_sample, int *sampled);
+
+void print_roc_small_pos(ostream *out,
+ int nb_pos, scalar_t *pos_responses,
+ int nb_neg, scalar_t *neg_responses,
+ scalar_t fas_factor);
+
+#endif