automatic commit
authorFrancois Fleuret <fleuret@moose.fleuret.org>
Thu, 9 Oct 2008 08:00:45 +0000 (10:00 +0200)
committerFrancois Fleuret <fleuret@moose.fleuret.org>
Thu, 9 Oct 2008 08:00:45 +0000 (10:00 +0200)
88 files changed:
Makefile [new file with mode: 0644]
boosted_classifier.cc [new file with mode: 0644]
boosted_classifier.h [new file with mode: 0644]
chrono.cc [new file with mode: 0644]
chrono.h [new file with mode: 0644]
classifier.cc [new file with mode: 0644]
classifier.h [new file with mode: 0644]
classifier_reader.cc [new file with mode: 0644]
classifier_reader.h [new file with mode: 0644]
decision_tree.cc [new file with mode: 0644]
decision_tree.h [new file with mode: 0644]
detector.cc [new file with mode: 0644]
detector.h [new file with mode: 0644]
error_rates.cc [new file with mode: 0644]
error_rates.h [new file with mode: 0644]
folding.cc [new file with mode: 0644]
fusion_sort.cc [new file with mode: 0644]
fusion_sort.h [new file with mode: 0644]
gaussian.cc [new file with mode: 0644]
gaussian.h [new file with mode: 0644]
global.cc [new file with mode: 0644]
global.h [new file with mode: 0644]
graph.sh [new file with mode: 0755]
image.cc [new file with mode: 0644]
image.h [new file with mode: 0644]
interval.cc [new file with mode: 0644]
interval.h [new file with mode: 0644]
jpeg_misc.cc [new file with mode: 0644]
jpeg_misc.h [new file with mode: 0644]
labelled_image.cc [new file with mode: 0644]
labelled_image.h [new file with mode: 0644]
labelled_image_pool.cc [new file with mode: 0644]
labelled_image_pool.h [new file with mode: 0644]
labelled_image_pool_file.cc [new file with mode: 0644]
labelled_image_pool_file.h [new file with mode: 0644]
labelled_image_pool_subset.cc [new file with mode: 0644]
labelled_image_pool_subset.h [new file with mode: 0644]
list_to_pool.cc [new file with mode: 0644]
loss_machine.cc [new file with mode: 0644]
loss_machine.h [new file with mode: 0644]
materials.cc [new file with mode: 0644]
materials.h [new file with mode: 0644]
misc.cc [new file with mode: 0644]
misc.h [new file with mode: 0644]
param_parser.cc [new file with mode: 0644]
param_parser.h [new file with mode: 0644]
parsing.cc [new file with mode: 0644]
parsing.h [new file with mode: 0644]
parsing_pool.cc [new file with mode: 0644]
parsing_pool.h [new file with mode: 0644]
pi_feature.cc [new file with mode: 0644]
pi_feature.h [new file with mode: 0644]
pi_feature_family.cc [new file with mode: 0644]
pi_feature_family.h [new file with mode: 0644]
pi_referential.cc [new file with mode: 0644]
pi_referential.h [new file with mode: 0644]
pose.cc [new file with mode: 0644]
pose.h [new file with mode: 0644]
pose_cell.cc [new file with mode: 0644]
pose_cell.h [new file with mode: 0644]
pose_cell_hierarchy.cc [new file with mode: 0644]
pose_cell_hierarchy.h [new file with mode: 0644]
pose_cell_hierarchy_reader.cc [new file with mode: 0644]
pose_cell_hierarchy_reader.h [new file with mode: 0644]
pose_cell_scored_set.cc [new file with mode: 0644]
pose_cell_scored_set.h [new file with mode: 0644]
pose_cell_set.cc [new file with mode: 0644]
pose_cell_set.h [new file with mode: 0644]
progress_bar.cc [new file with mode: 0644]
progress_bar.h [new file with mode: 0644]
rectangle.h [new file with mode: 0644]
rgb_image.cc [new file with mode: 0644]
rgb_image.h [new file with mode: 0644]
rgb_image_subpixel.cc [new file with mode: 0644]
rgb_image_subpixel.h [new file with mode: 0644]
rich_image.cc [new file with mode: 0644]
rich_image.h [new file with mode: 0644]
run.sh [new file with mode: 0755]
sample_set.cc [new file with mode: 0644]
sample_set.h [new file with mode: 0644]
shared.cc [new file with mode: 0644]
shared.h [new file with mode: 0644]
shared_responses.cc [new file with mode: 0644]
shared_responses.h [new file with mode: 0644]
storable.cc [new file with mode: 0644]
storable.h [new file with mode: 0644]
tools.cc [new file with mode: 0644]
tools.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..7fc3339
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,89 @@
+
+#########################################################################
+# 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
diff --git a/boosted_classifier.cc b/boosted_classifier.cc
new file mode 100644 (file)
index 0000000..8080ad2
--- /dev/null
@@ -0,0 +1,130 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/boosted_classifier.h b/boosted_classifier.h
new file mode 100644 (file)
index 0000000..8f52e5a
--- /dev/null
@@ -0,0 +1,58 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/chrono.cc b/chrono.cc
new file mode 100644 (file)
index 0000000..c4b7267
--- /dev/null
+++ b/chrono.cc
@@ -0,0 +1,62 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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); }
diff --git a/chrono.h b/chrono.h
new file mode 100644 (file)
index 0000000..8b26bcf
--- /dev/null
+++ b/chrono.h
@@ -0,0 +1,39 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/classifier.cc b/classifier.cc
new file mode 100644 (file)
index 0000000..6cf61e8
--- /dev/null
@@ -0,0 +1,42 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/classifier.h b/classifier.h
new file mode 100644 (file)
index 0000000..b5dc2a7
--- /dev/null
@@ -0,0 +1,61 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/classifier_reader.cc b/classifier_reader.cc
new file mode 100644 (file)
index 0000000..dcd1e5b
--- /dev/null
@@ -0,0 +1,43 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/classifier_reader.h b/classifier_reader.h
new file mode 100644 (file)
index 0000000..5d68cee
--- /dev/null
@@ -0,0 +1,28 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/decision_tree.cc b/decision_tree.cc
new file mode 100644 (file)
index 0000000..e2b3daa
--- /dev/null
@@ -0,0 +1,303 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+  }
+}
diff --git a/decision_tree.h b/decision_tree.h
new file mode 100644 (file)
index 0000000..59dba57
--- /dev/null
@@ -0,0 +1,67 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/detector.cc b/detector.cc
new file mode 100644 (file)
index 0000000..1c3ef23
--- /dev/null
@@ -0,0 +1,474 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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, &current_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, &current_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);
+}
diff --git a/detector.h b/detector.h
new file mode 100644 (file)
index 0000000..72248af
--- /dev/null
@@ -0,0 +1,76 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/error_rates.cc b/error_rates.cc
new file mode 100644 (file)
index 0000000..14d4d8c
--- /dev/null
@@ -0,0 +1,137 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/error_rates.h b/error_rates.h
new file mode 100644 (file)
index 0000000..71906d6
--- /dev/null
@@ -0,0 +1,30 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/folding.cc b/folding.cc
new file mode 100644 (file)
index 0000000..34c785c
--- /dev/null
@@ -0,0 +1,339 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+
+}
diff --git a/fusion_sort.cc b/fusion_sort.cc
new file mode 100644 (file)
index 0000000..3774588
--- /dev/null
@@ -0,0 +1,85 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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];
+}
diff --git a/fusion_sort.h b/fusion_sort.h
new file mode 100644 (file)
index 0000000..d2c1cd1
--- /dev/null
@@ -0,0 +1,28 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/gaussian.cc b/gaussian.cc
new file mode 100644 (file)
index 0000000..3278377
--- /dev/null
@@ -0,0 +1,45 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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());
+}
+
diff --git a/gaussian.h b/gaussian.h
new file mode 100644 (file)
index 0000000..da02d30
--- /dev/null
@@ -0,0 +1,35 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/global.cc b/global.cc
new file mode 100644 (file)
index 0000000..253733c
--- /dev/null
+++ b/global.cc
@@ -0,0 +1,172 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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"));
+}
diff --git a/global.h b/global.h
new file mode 100644 (file)
index 0000000..a053d3d
--- /dev/null
+++ b/global.h
@@ -0,0 +1,101 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/graph.sh b/graph.sh
new file mode 100755 (executable)
index 0000000..b54fcf4
--- /dev/null
+++ b/graph.sh
@@ -0,0 +1,62 @@
+#!/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}"
diff --git a/image.cc b/image.cc
new file mode 100644 (file)
index 0000000..e9eb83f
--- /dev/null
+++ b/image.cc
@@ -0,0 +1,74 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/image.h b/image.h
new file mode 100644 (file)
index 0000000..5ba20a8
--- /dev/null
+++ b/image.h
@@ -0,0 +1,60 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/interval.cc b/interval.cc
new file mode 100644 (file)
index 0000000..1d7bde9
--- /dev/null
@@ -0,0 +1,48 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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 << "]";
+}
diff --git a/interval.h b/interval.h
new file mode 100644 (file)
index 0000000..57810f6
--- /dev/null
@@ -0,0 +1,58 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/jpeg_misc.cc b/jpeg_misc.cc
new file mode 100644 (file)
index 0000000..3d653aa
--- /dev/null
@@ -0,0 +1,25 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/jpeg_misc.h b/jpeg_misc.h
new file mode 100644 (file)
index 0000000..b6d95ea
--- /dev/null
@@ -0,0 +1,36 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/labelled_image.cc b/labelled_image.cc
new file mode 100644 (file)
index 0000000..d02313d
--- /dev/null
@@ -0,0 +1,101 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/labelled_image.h b/labelled_image.h
new file mode 100644 (file)
index 0000000..2a26d73
--- /dev/null
@@ -0,0 +1,53 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/labelled_image_pool.cc b/labelled_image_pool.cc
new file mode 100644 (file)
index 0000000..bbfe943
--- /dev/null
@@ -0,0 +1,21 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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() { }
diff --git a/labelled_image_pool.h b/labelled_image_pool.h
new file mode 100644 (file)
index 0000000..3186dc7
--- /dev/null
@@ -0,0 +1,33 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/labelled_image_pool_file.cc b/labelled_image_pool_file.cc
new file mode 100644 (file)
index 0000000..cb099ee
--- /dev/null
@@ -0,0 +1,101 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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];
+}
diff --git a/labelled_image_pool_file.h b/labelled_image_pool_file.h
new file mode 100644 (file)
index 0000000..a46d781
--- /dev/null
@@ -0,0 +1,46 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/labelled_image_pool_subset.cc b/labelled_image_pool_subset.cc
new file mode 100644 (file)
index 0000000..3b46d48
--- /dev/null
@@ -0,0 +1,47 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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]);
+}
diff --git a/labelled_image_pool_subset.h b/labelled_image_pool_subset.h
new file mode 100644 (file)
index 0000000..cc86696
--- /dev/null
@@ -0,0 +1,36 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/list_to_pool.cc b/list_to_pool.cc
new file mode 100644 (file)
index 0000000..c142534
--- /dev/null
@@ -0,0 +1,280 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+
+}
diff --git a/loss_machine.cc b/loss_machine.cc
new file mode 100644 (file)
index 0000000..6ff78d5
--- /dev/null
@@ -0,0 +1,421 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+  }
+
+
+}
diff --git a/loss_machine.h b/loss_machine.h
new file mode 100644 (file)
index 0000000..a293e8b
--- /dev/null
@@ -0,0 +1,55 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/materials.cc b/materials.cc
new file mode 100644 (file)
index 0000000..bbb6cbf
--- /dev/null
@@ -0,0 +1,250 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/materials.h b/materials.h
new file mode 100644 (file)
index 0000000..4d6b337
--- /dev/null
@@ -0,0 +1,47 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/misc.cc b/misc.cc
new file mode 100644 (file)
index 0000000..2d2258d
--- /dev/null
+++ b/misc.cc
@@ -0,0 +1,125 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+  }
+}
diff --git a/misc.h b/misc.h
new file mode 100644 (file)
index 0000000..d2d5b22
--- /dev/null
+++ b/misc.h
@@ -0,0 +1,116 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/param_parser.cc b/param_parser.cc
new file mode 100644 (file)
index 0000000..5ae8a41
--- /dev/null
@@ -0,0 +1,146 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+  }
+}
+
diff --git a/param_parser.h b/param_parser.h
new file mode 100644 (file)
index 0000000..57d0082
--- /dev/null
@@ -0,0 +1,44 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/parsing.cc b/parsing.cc
new file mode 100644 (file)
index 0000000..9d0060d
--- /dev/null
@@ -0,0 +1,186 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/parsing.h b/parsing.h
new file mode 100644 (file)
index 0000000..4f1c9b5
--- /dev/null
+++ b/parsing.h
@@ -0,0 +1,87 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/parsing_pool.cc b/parsing_pool.cc
new file mode 100644 (file)
index 0000000..696477a
--- /dev/null
@@ -0,0 +1,259 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/parsing_pool.h b/parsing_pool.h
new file mode 100644 (file)
index 0000000..f7b67be
--- /dev/null
@@ -0,0 +1,74 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/pi_feature.cc b/pi_feature.cc
new file mode 100644 (file)
index 0000000..300ccee
--- /dev/null
@@ -0,0 +1,471 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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,
+                                  &registered_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,
+                                  &registered_window_a);
+
+  referential->draw_window(image, _registration_a, &registered_window_a, 0);
+
+//   if(!global.pictures_for_article) {
+//     int tag = referential->register_edge(_registration_a, _tag);
+
+//     referential->draw_edge_and_scale(image, _registration_a, &registered_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,
+                                  &registered_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,
+                                  &registered_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,
+                                    &registered_window);
+    referential->draw_window(image, _registration_a, &registered_window, 0);
+  }
+
+  {
+    referential->register_rectangle(_registration_b,
+                                    &_window_b,
+                                    &registered_window);
+
+    referential->draw_window(image, _registration_b, &registered_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,
+                                  &registered_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,
+                                  &registered_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,
+                                    &registered_window);
+
+    referential->draw_window(image, _registration_a, &registered_window, 0);
+  }
+
+  {
+    referential->register_rectangle(_registration_b,
+                                    &_window_b,
+                                    &registered_window);
+
+    referential->draw_window(image, _registration_b, &registered_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();
+  }
+}
diff --git a/pi_feature.h b/pi_feature.h
new file mode 100644 (file)
index 0000000..b03a922
--- /dev/null
@@ -0,0 +1,86 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/pi_feature_family.cc b/pi_feature_family.cc
new file mode 100644 (file)
index 0000000..ba3a35e
--- /dev/null
@@ -0,0 +1,70 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/pi_feature_family.h b/pi_feature_family.h
new file mode 100644 (file)
index 0000000..84ac2f0
--- /dev/null
@@ -0,0 +1,50 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/pi_referential.cc b/pi_referential.cc
new file mode 100644 (file)
index 0000000..6e7eb3e
--- /dev/null
@@ -0,0 +1,655 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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();
+  }
+}
diff --git a/pi_referential.h b/pi_referential.h
new file mode 100644 (file)
index 0000000..1dcb007
--- /dev/null
@@ -0,0 +1,133 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/pose.cc b/pose.cc
new file mode 100644 (file)
index 0000000..ed6c705
--- /dev/null
+++ b/pose.cc
@@ -0,0 +1,190 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/pose.h b/pose.h
new file mode 100644 (file)
index 0000000..acd7884
--- /dev/null
+++ b/pose.h
@@ -0,0 +1,49 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/pose_cell.cc b/pose_cell.cc
new file mode 100644 (file)
index 0000000..4e5e7e2
--- /dev/null
@@ -0,0 +1,63 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/pose_cell.h b/pose_cell.h
new file mode 100644 (file)
index 0000000..b970756
--- /dev/null
@@ -0,0 +1,47 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/pose_cell_hierarchy.cc b/pose_cell_hierarchy.cc
new file mode 100644 (file)
index 0000000..0816f3a
--- /dev/null
@@ -0,0 +1,392 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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]);
+  }
+}
diff --git a/pose_cell_hierarchy.h b/pose_cell_hierarchy.h
new file mode 100644 (file)
index 0000000..51f4e6e
--- /dev/null
@@ -0,0 +1,66 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/pose_cell_hierarchy_reader.cc b/pose_cell_hierarchy_reader.cc
new file mode 100644 (file)
index 0000000..1d3b5b5
--- /dev/null
@@ -0,0 +1,25 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/pose_cell_hierarchy_reader.h b/pose_cell_hierarchy_reader.h
new file mode 100644 (file)
index 0000000..77ae99d
--- /dev/null
@@ -0,0 +1,26 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/pose_cell_scored_set.cc b/pose_cell_scored_set.cc
new file mode 100644 (file)
index 0000000..253c3ee
--- /dev/null
@@ -0,0 +1,122 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/pose_cell_scored_set.h b/pose_cell_scored_set.h
new file mode 100644 (file)
index 0000000..e3bc877
--- /dev/null
@@ -0,0 +1,46 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/pose_cell_set.cc b/pose_cell_set.cc
new file mode 100644 (file)
index 0000000..62d04dd
--- /dev/null
@@ -0,0 +1,51 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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();
+  }
+}
diff --git a/pose_cell_set.h b/pose_cell_set.h
new file mode 100644 (file)
index 0000000..b0a5fac
--- /dev/null
@@ -0,0 +1,54 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/progress_bar.cc b/progress_bar.cc
new file mode 100644 (file)
index 0000000..b7800eb
--- /dev/null
@@ -0,0 +1,93 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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(&current_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(&current);
+          current += rt;
+          strftime(date_buffer, buffer_size, "%a %b %e %H:%M", localtime(&current));
+          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(&current_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();
+  }
+}
diff --git a/progress_bar.h b/progress_bar.h
new file mode 100644 (file)
index 0000000..50aa7c4
--- /dev/null
@@ -0,0 +1,48 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/rectangle.h b/rectangle.h
new file mode 100644 (file)
index 0000000..b8fd645
--- /dev/null
@@ -0,0 +1,28 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/rgb_image.cc b/rgb_image.cc
new file mode 100644 (file)
index 0000000..1fd38cb
--- /dev/null
@@ -0,0 +1,566 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+  }
+}
diff --git a/rgb_image.h b/rgb_image.h
new file mode 100644 (file)
index 0000000..9105534
--- /dev/null
@@ -0,0 +1,78 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/rgb_image_subpixel.cc b/rgb_image_subpixel.cc
new file mode 100644 (file)
index 0000000..3338f50
--- /dev/null
@@ -0,0 +1,86 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/rgb_image_subpixel.h b/rgb_image_subpixel.h
new file mode 100644 (file)
index 0000000..393068b
--- /dev/null
@@ -0,0 +1,52 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/rich_image.cc b/rich_image.cc
new file mode 100644 (file)
index 0000000..f3b4b54
--- /dev/null
@@ -0,0 +1,453 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/rich_image.h b/rich_image.h
new file mode 100644 (file)
index 0000000..c8f7a74
--- /dev/null
@@ -0,0 +1,105 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/run.sh b/run.sh
new file mode 100755 (executable)
index 0000000..3a90f56
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,124 @@
+#!/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
diff --git a/sample_set.cc b/sample_set.cc
new file mode 100644 (file)
index 0000000..bf323e6
--- /dev/null
@@ -0,0 +1,65 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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]));
+  }
+}
diff --git a/sample_set.h b/sample_set.h
new file mode 100644 (file)
index 0000000..ed870e8
--- /dev/null
@@ -0,0 +1,62 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/shared.cc b/shared.cc
new file mode 100644 (file)
index 0000000..6986ad6
--- /dev/null
+++ b/shared.cc
@@ -0,0 +1,34 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/shared.h b/shared.h
new file mode 100644 (file)
index 0000000..b17e6f0
--- /dev/null
+++ b/shared.h
@@ -0,0 +1,39 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/shared_responses.cc b/shared_responses.cc
new file mode 100644 (file)
index 0000000..5d96ece
--- /dev/null
@@ -0,0 +1,27 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/shared_responses.h b/shared_responses.h
new file mode 100644 (file)
index 0000000..28b9a62
--- /dev/null
@@ -0,0 +1,31 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/storable.cc b/storable.cc
new file mode 100644 (file)
index 0000000..4e57ae1
--- /dev/null
@@ -0,0 +1,42 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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);
+}
diff --git a/storable.h b/storable.h
new file mode 100644 (file)
index 0000000..d9fca51
--- /dev/null
@@ -0,0 +1,37 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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
diff --git a/tools.cc b/tools.cc
new file mode 100644 (file)
index 0000000..1834cd8
--- /dev/null
+++ b/tools.cc
@@ -0,0 +1,108 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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;
+}
diff --git a/tools.h b/tools.h
new file mode 100644 (file)
index 0000000..ec35551
--- /dev/null
+++ b/tools.h
@@ -0,0 +1,32 @@
+
+///////////////////////////////////////////////////////////////////////////
+// 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