Commit d2de26db54ebbcebb3634a3c7a10f35993538007

Authored by Scott Klum
2 parents 9b430a4f 51bb9ca5

Merge remote-tracking branch 'origin/master' into liblinear

app/br/br.cpp
@@ -163,8 +163,8 @@ public: @@ -163,8 +163,8 @@ public:
163 check((parc >= 2) && (parc <= 6), "Incorrect parameter count for 'evalDetection'."); 163 check((parc >= 2) && (parc <= 6), "Incorrect parameter count for 'evalDetection'.");
164 br_eval_detection(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 0, parc == 6 ? atoi(parv[5]) : 0); 164 br_eval_detection(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 0, parc == 6 ? atoi(parv[5]) : 0);
165 } else if (!strcmp(fun, "evalLandmarking")) { 165 } else if (!strcmp(fun, "evalLandmarking")) {
166 - check((parc >= 2) && (parc <= 5), "Incorrect parameter count for 'evalLandmarking'.");  
167 - br_eval_landmarking(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 1); 166 + check((parc >= 2) && (parc <= 7), "Incorrect parameter count for 'evalLandmarking'.");
  167 + br_eval_landmarking(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? atoi(parv[3]) : 0, parc >= 5 ? atoi(parv[4]) : 1, parc >= 6 ? atoi(parv[5]) : 0, parc >= 7 ? atoi(parv[6]) : 5);
168 } else if (!strcmp(fun, "evalRegression")) { 168 } else if (!strcmp(fun, "evalRegression")) {
169 check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'."); 169 check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'.");
170 br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); 170 br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : "");
@@ -264,7 +264,7 @@ private: @@ -264,7 +264,7 @@ private:
264 "-evalClassification <predicted_gallery> <truth_gallery> <predicted property name> <ground truth proprty name>\n" 264 "-evalClassification <predicted_gallery> <truth_gallery> <predicted property name> <ground truth proprty name>\n"
265 "-evalClustering <clusters> <gallery>\n" 265 "-evalClustering <clusters> <gallery>\n"
266 "-evalDetection <predicted_gallery> <truth_gallery> [{csv}] [{normalize}] [{minSize}]\n" 266 "-evalDetection <predicted_gallery> <truth_gallery> [{csv}] [{normalize}] [{minSize}]\n"
267 - "-evalLandmarking <predicted_gallery> <truth_gallery> [{csv} [<normalization_index_a> <normalization_index_b>]]\n" 267 + "-evalLandmarking <predicted_gallery> <truth_gallery> [{csv} [<normalization_index_a> <normalization_index_b>] [sample_index] [total_examples]]\n"
268 "-evalRegression <predicted_gallery> <truth_gallery> <predicted property name> <ground truth property name>\n" 268 "-evalRegression <predicted_gallery> <truth_gallery> <predicted property name> <ground truth property name>\n"
269 "-assertEval <simmat> <mask> <accuracy>\n" 269 "-assertEval <simmat> <mask> <accuracy>\n"
270 "-plotDetection <file> ... <file> {destination}\n" 270 "-plotDetection <file> ... <file> {destination}\n"
openbr/core/common.cpp
@@ -16,21 +16,32 @@ @@ -16,21 +16,32 @@
16 16
17 #include "common.h" 17 #include "common.h"
18 #include <QMutex> 18 #include <QMutex>
  19 +#include <RandomLib/Random.hpp>
19 20
20 using namespace std; 21 using namespace std;
21 22
  23 +static RandomLib::Random g_rand;
  24 +static QMutex rngLock;
  25 +
22 /**** GLOBAL ****/ 26 /**** GLOBAL ****/
23 void Common::seedRNG() { 27 void Common::seedRNG() {
24 - static QMutex seedControl;  
25 - QMutexLocker lock(&seedControl); 28 + QMutexLocker lock(&rngLock);
26 29
27 static bool seeded = false; 30 static bool seeded = false;
28 if (!seeded) { 31 if (!seeded) {
29 srand(0); // We seed with 0 instead of time(NULL) to have reproducible randomness 32 srand(0); // We seed with 0 instead of time(NULL) to have reproducible randomness
30 seeded = true; 33 seeded = true;
  34 + g_rand.Reseed(0);
31 } 35 }
32 } 36 }
33 37
  38 +double Common::randN()
  39 +{
  40 + QMutexLocker lock(&rngLock);
  41 +
  42 + return g_rand.FloatN();
  43 +}
  44 +
34 QList<int> Common::RandSample(int n, int max, int min, bool unique) 45 QList<int> Common::RandSample(int n, int max, int min, bool unique)
35 { 46 {
36 QList<int> samples; samples.reserve(n); 47 QList<int> samples; samples.reserve(n);
openbr/core/common.h
@@ -220,6 +220,9 @@ double KernelDensityEstimation(const V&lt;T&gt; &amp;vals, double x, double h) @@ -220,6 +220,9 @@ double KernelDensityEstimation(const V&lt;T&gt; &amp;vals, double x, double h)
220 return y / (vals.size() * h); 220 return y / (vals.size() * h);
221 } 221 }
222 222
  223 +// Return a random number, uniformly distributed over 0,1
  224 +double randN();
  225 +
223 /*! 226 /*!
224 * \brief Returns a vector of n integers sampled in the range <min, max]. 227 * \brief Returns a vector of n integers sampled in the range <min, max].
225 * 228 *
@@ -236,19 +239,14 @@ QList&lt;int&gt; RandSample(int n, const QSet&lt;int&gt; &amp;values, bool unique = false); @@ -236,19 +239,14 @@ QList&lt;int&gt; RandSample(int n, const QSet&lt;int&gt; &amp;values, bool unique = false);
236 template <typename T> 239 template <typename T>
237 QList<int> RandSample(int n, const QList<T> &weights, bool unique = false) 240 QList<int> RandSample(int n, const QList<T> &weights, bool unique = false)
238 { 241 {
239 - static bool seeded = false;  
240 - if (!seeded) {  
241 - srand(time(NULL));  
242 - seeded = true;  
243 - }  
244 -  
245 QList<T> cdf = CumSum(weights); 242 QList<T> cdf = CumSum(weights);
246 for (int i=0; i<cdf.size(); i++) // Normalize cdf 243 for (int i=0; i<cdf.size(); i++) // Normalize cdf
247 cdf[i] = cdf[i] / cdf.last(); 244 cdf[i] = cdf[i] / cdf.last();
248 245
249 QList<int> samples; samples.reserve(n); 246 QList<int> samples; samples.reserve(n);
250 while (samples.size() < n) { 247 while (samples.size() < n) {
251 - T r = (T)rand() / (T)RAND_MAX; 248 + T r = randN();
  249 +
252 for (int j=0; j<weights.size(); j++) { 250 for (int j=0; j<weights.size(); j++) {
253 if ((r >= cdf[j]) && (r <= cdf[j+1])) { 251 if ((r >= cdf[j]) && (r <= cdf[j+1])) {
254 if (!unique || !samples.contains(j)) 252 if (!unique || !samples.contains(j))
openbr/core/core.cpp
@@ -110,9 +110,11 @@ struct AlgorithmCore @@ -110,9 +110,11 @@ struct AlgorithmCore
110 110
111 void store(const QString &model) const 111 void store(const QString &model) const
112 { 112 {
113 - // Create stream  
114 - QByteArray data;  
115 - QDataStream out(&data, QFile::WriteOnly); 113 + QtUtils::BlockCompression compressedWrite;
  114 + QFile outFile(model);
  115 + compressedWrite.setBasis(&outFile);
  116 + QDataStream out(&compressedWrite);
  117 + compressedWrite.open(QFile::WriteOnly);
116 118
117 // Serialize algorithm to stream 119 // Serialize algorithm to stream
118 transform->serialize(out); 120 transform->serialize(out);
@@ -131,18 +133,16 @@ struct AlgorithmCore @@ -131,18 +133,16 @@ struct AlgorithmCore
131 if (mode == TransformCompare) 133 if (mode == TransformCompare)
132 comparison->serialize(out); 134 comparison->serialize(out);
133 135
134 - // Compress and save to file  
135 - QtUtils::writeFile(model, data, -1); 136 + compressedWrite.close();
136 } 137 }
137 138
138 void load(const QString &model) 139 void load(const QString &model)
139 { 140 {
140 - // Load from file and decompress  
141 - QByteArray data;  
142 - QtUtils::readFile(model, data, true);  
143 -  
144 - // Create stream  
145 - QDataStream in(&data, QFile::ReadOnly); 141 + QtUtils::BlockCompression compressedRead;
  142 + QFile inFile(model);
  143 + compressedRead.setBasis(&inFile);
  144 + QDataStream in(&compressedRead);
  145 + compressedRead.open(QFile::ReadOnly);
146 146
147 // Load algorithm 147 // Load algorithm
148 transform = QSharedPointer<Transform>(Transform::deserialize(in)); 148 transform = QSharedPointer<Transform>(Transform::deserialize(in));
openbr/core/eigenutils.cpp
@@ -4,80 +4,15 @@ @@ -4,80 +4,15 @@
4 using namespace Eigen; 4 using namespace Eigen;
5 using namespace cv; 5 using namespace cv;
6 6
7 -//Helper function to quickly write eigen matrix to disk. Not efficient.  
8 -void writeEigen(MatrixXf X, QString filename) {  
9 - Mat m(X.rows(),X.cols(),CV_32FC1);  
10 - for (int i = 0; i < X.rows(); i++) {  
11 - for (int j = 0; j < X.cols(); j++) {  
12 - m.at<float>(i,j) = X(i,j);  
13 - }  
14 - }  
15 - QScopedPointer<br::Format> format(br::Factory<br::Format>::make(filename));  
16 - format->write(br::Template(m));  
17 -}  
18 -  
19 -void writeEigen(MatrixXd X, QString filename) {  
20 - Mat m(X.rows(),X.cols(),CV_32FC1);  
21 - for (int i = 0; i < X.rows(); i++) {  
22 - for (int j = 0; j < X.cols(); j++) {  
23 - m.at<float>(i,j) = (float)X(i,j);  
24 - }  
25 - }  
26 - QScopedPointer<br::Format> format(br::Factory<br::Format>::make(filename));  
27 - format->write(br::Template(m));  
28 -}  
29 -  
30 -void writeEigen(VectorXd X, QString filename) {  
31 - Mat m(X.size(),1,CV_32FC1);  
32 - for (int i = 0; i < X.rows(); i++) {  
33 - m.at<float>(i,0) = (float)X(i);  
34 - }  
35 - QScopedPointer<br::Format> format(br::Factory<br::Format>::make(filename));  
36 - format->write(br::Template(m));  
37 -}  
38 -  
39 -void writeEigen(VectorXf X, QString filename) {  
40 - Mat m(X.size(),1,CV_32FC1);  
41 - for (int i = 0; i < X.rows(); i++) {  
42 - m.at<float>(i,0) = X(i);  
43 - }  
44 - QScopedPointer<br::Format> format(br::Factory<br::Format>::make(filename));  
45 - format->write(br::Template(m));  
46 -}  
47 -  
48 -void printEigen(Eigen::MatrixXd X) {  
49 - for (int i = 0; i < X.rows(); i++) {  
50 - QString str;  
51 - for (int j = 0; j < X.cols(); j++) {  
52 - str.append(QString::number(X(i,j)) + " ");  
53 - }  
54 - qDebug() << str;  
55 - }  
56 -}  
57 -void printEigen(Eigen::MatrixXf X) {  
58 - for (int i = 0; i < X.rows(); i++) {  
59 - QString str;  
60 - for (int j = 0; j < X.cols(); j++) {  
61 - str.append(QString::number(X(i,j)) + " ");  
62 - }  
63 - qDebug() << str;  
64 - }  
65 -}  
66 -  
67 -void printSize(Eigen::MatrixXf X) { 7 +void EigenUtils::printSize(Eigen::MatrixXf X) {
68 qDebug() << "Rows=" << X.rows() << "\tCols=" << X.cols(); 8 qDebug() << "Rows=" << X.rows() << "\tCols=" << X.cols();
69 } 9 }
70 10
71 -float eigMean(const Eigen::MatrixXf& x) {  
72 - return x.array().sum() / (x.rows() * x.cols()); 11 +float EigenUtils::stddev(const Eigen::MatrixXf& x) {
  12 + return sqrt((x.array() - x.mean()).pow(2).sum() / (x.cols() * x.rows()));
73 } 13 }
74 14
75 -float eigStd(const Eigen::MatrixXf& x) {  
76 - float mean = eigMean(x);  
77 - return sqrt((x.array() - mean).pow(2).sum() / (x.cols() * x.rows()));  
78 -}  
79 -  
80 -MatrixXf removeRowCol(const MatrixXf X, int row, int col) { 15 +MatrixXf EigenUtils::removeRowCol(const MatrixXf X, int row, int col) {
81 MatrixXf Y(X.rows() - 1,X.cols() - 1); 16 MatrixXf Y(X.rows() - 1,X.cols() - 1);
82 17
83 for (int i1 = 0, i2 = 0; i1 < X.rows(); i1++) { 18 for (int i1 = 0, i2 = 0; i1 < X.rows(); i1++) {
@@ -96,7 +31,7 @@ MatrixXf removeRowCol(const MatrixXf X, int row, int col) { @@ -96,7 +31,7 @@ MatrixXf removeRowCol(const MatrixXf X, int row, int col) {
96 return Y; 31 return Y;
97 } 32 }
98 33
99 -MatrixXf pointsToMatrix(const QList<QPointF> points, bool isAffine) { 34 +MatrixXf EigenUtils::pointsToMatrix(const QList<QPointF> points, bool isAffine) {
100 MatrixXf P(points.size(), isAffine ? 3 : 2); 35 MatrixXf P(points.size(), isAffine ? 3 : 2);
101 for (int i = 0; i < points.size(); i++) { 36 for (int i = 0; i < points.size(); i++) {
102 P(i, 0) = points[i].x(); 37 P(i, 0) = points[i].x();
@@ -107,7 +42,7 @@ MatrixXf pointsToMatrix(const QList&lt;QPointF&gt; points, bool isAffine) { @@ -107,7 +42,7 @@ MatrixXf pointsToMatrix(const QList&lt;QPointF&gt; points, bool isAffine) {
107 return P; 42 return P;
108 } 43 }
109 44
110 -QList<QPointF> matrixToPoints(const Eigen::MatrixXf P) { 45 +QList<QPointF> EigenUtils::matrixToPoints(const Eigen::MatrixXf P) {
111 QList<QPointF> points; 46 QList<QPointF> points;
112 for (int i = 0; i < P.rows(); i++) 47 for (int i = 0; i < P.rows(); i++)
113 points.append(QPointF(P(i, 0), P(i, 1))); 48 points.append(QPointF(P(i, 0), P(i, 1)));
@@ -115,7 +50,7 @@ QList&lt;QPointF&gt; matrixToPoints(const Eigen::MatrixXf P) { @@ -115,7 +50,7 @@ QList&lt;QPointF&gt; matrixToPoints(const Eigen::MatrixXf P) {
115 } 50 }
116 51
117 //Converts x y points in a single vector to two column matrix 52 //Converts x y points in a single vector to two column matrix
118 -Eigen::MatrixXf vectorToMatrix(const Eigen::MatrixXf vector) { 53 +Eigen::MatrixXf EigenUtils::vectorToMatrix(const Eigen::MatrixXf vector) {
119 int n = vector.rows(); 54 int n = vector.rows();
120 Eigen::MatrixXf matrix(n / 2, 2); 55 Eigen::MatrixXf matrix(n / 2, 2);
121 for (int i = 0; i < n / 2; i++) { 56 for (int i = 0; i < n / 2; i++) {
@@ -126,7 +61,7 @@ Eigen::MatrixXf vectorToMatrix(const Eigen::MatrixXf vector) { @@ -126,7 +61,7 @@ Eigen::MatrixXf vectorToMatrix(const Eigen::MatrixXf vector) {
126 return matrix; 61 return matrix;
127 } 62 }
128 63
129 -Eigen::MatrixXf matrixToVector(const Eigen::MatrixXf matrix) { 64 +Eigen::MatrixXf EigenUtils::matrixToVector(const Eigen::MatrixXf matrix) {
130 int n2 = matrix.rows(); 65 int n2 = matrix.rows();
131 Eigen::MatrixXf vector(n2 * 2, 1); 66 Eigen::MatrixXf vector(n2 * 2, 1);
132 for (int i = 0; i < n2; i++) { 67 for (int i = 0; i < n2; i++) {
@@ -136,12 +71,3 @@ Eigen::MatrixXf matrixToVector(const Eigen::MatrixXf matrix) { @@ -136,12 +71,3 @@ Eigen::MatrixXf matrixToVector(const Eigen::MatrixXf matrix) {
136 } 71 }
137 return vector; 72 return vector;
138 } 73 }
139 -  
140 -Eigen::MatrixXf toEigen(const Mat m) {  
141 - if (m.type() != CV_32F)  
142 - qFatal("Mat to Eigen Converstation only supports CV_32F");  
143 -  
144 - Eigen::MatrixXf data(m.rows, m.cols);  
145 - return Eigen::Map<const Eigen::MatrixXf>(m.ptr<float>(), m.rows, m.cols);  
146 -}  
147 -  
openbr/core/eigenutils.h
@@ -18,32 +18,74 @@ @@ -18,32 +18,74 @@
18 #define EIGENUTILS_H 18 #define EIGENUTILS_H
19 19
20 #include <QDataStream> 20 #include <QDataStream>
  21 +#include <QDebug>
21 #include <Eigen/Core> 22 #include <Eigen/Core>
22 #include <assert.h> 23 #include <assert.h>
23 24
  25 +#include <opencv2/core/eigen.hpp>
24 #include <opencv2/core/core.hpp> 26 #include <opencv2/core/core.hpp>
25 27
26 -void writeEigen(Eigen::MatrixXf X, QString filename);  
27 -void writeEigen(Eigen::MatrixXd X, QString filename);  
28 -void writeEigen(Eigen::VectorXd X, QString filename);  
29 -void writeEigen(Eigen::VectorXf X, QString filename);  
30 -void printEigen(Eigen::MatrixXd X);  
31 -void printEigen(Eigen::MatrixXf X);  
32 -void printSize(Eigen::MatrixXf X); 28 +#include "openbr/core/qtutils.h"
33 29
34 -//Converts x y points in a single vector to two column matrix  
35 -Eigen::MatrixXf vectorToMatrix(const Eigen::MatrixXf vector);  
36 -Eigen::MatrixXf matrixToVector(const Eigen::MatrixXf matrix); 30 +namespace EigenUtils
  31 +{
  32 + template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
  33 + QString matrixToString(const Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat)
  34 + {
  35 + QString result;
  36 + if (mat.rows() > 1) result += "{ ";
  37 + for (int r=0; r<mat.rows(); r++) {
  38 + if ((mat.rows() > 1) && (r > 0)) result += " ";
  39 + if (mat.cols() > 1) result += "[";
  40 + for (int c=0; c<mat.cols(); c++) {
  41 + result += QString::number(mat(r, c));
  42 + if (c < mat.cols() - 1) result += ", ";
  43 + }
  44 + if (mat.cols() > 1) result += "]";
  45 + if (r < mat.rows()-1) result += "\n";
  46 + }
  47 + if (mat.rows() > 1) result += " }";
  48 + return result;
  49 + }
  50 +
  51 + template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
  52 + void writeMatrix(const Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat, const QString &filename)
  53 + {
  54 + int r = mat.rows();
  55 + int c = mat.cols();
  56 +
  57 + _Scalar *data = new _Scalar[r*c];
  58 + for (int i=0; i<r; i++)
  59 + for (int j=0; j<c; j++)
  60 + data[i*c+j] = mat(i, j);
  61 + int bytes = r*c*sizeof(_Scalar);
  62 + QByteArray byteArray((const char*)data,bytes);
  63 + QtUtils::writeFile(filename,byteArray);
  64 + }
  65 +
  66 + void printSize(Eigen::MatrixXf X);
37 67
38 -//Remove row and column from the matrix:  
39 -Eigen::MatrixXf removeRowCol(const Eigen::MatrixXf X, int row, int col); 68 + // Converts x y points in a single vector to two column matrix
  69 + Eigen::MatrixXf vectorToMatrix(const Eigen::MatrixXf vector);
  70 + Eigen::MatrixXf matrixToVector(const Eigen::MatrixXf matrix);
40 71
41 -//Convert a point list into a matrix:  
42 -Eigen::MatrixXf pointsToMatrix(const QList<QPointF> points, bool isAffine=false);  
43 -QList<QPointF> matrixToPoints(const Eigen::MatrixXf P); 72 + // Remove row and column from the matrix:
  73 + Eigen::MatrixXf removeRowCol(const Eigen::MatrixXf X, int row, int col);
44 74
45 -//Convert cv::Mat to Eigen  
46 -Eigen::MatrixXf toEigen(const cv::Mat m); 75 + // Convert a point list into a matrix:
  76 + Eigen::MatrixXf pointsToMatrix(const QList<QPointF> points, bool isAffine=false);
  77 + QList<QPointF> matrixToPoints(const Eigen::MatrixXf P);
  78 +
  79 + // Compute the element-wise standard deviation
  80 + float stddev(const Eigen::MatrixXf& x);
  81 +}
  82 +
  83 +template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
  84 +inline QDebug operator<<(QDebug dbg, const Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat)
  85 +{
  86 + dbg.nospace() << EigenUtils::matrixToString(mat);
  87 + return dbg.space();
  88 +}
47 89
48 template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols> 90 template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
49 inline QDataStream &operator<<(QDataStream &stream, const Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat) 91 inline QDataStream &operator<<(QDataStream &stream, const Eigen::Matrix< _Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols > &mat)
@@ -83,56 +125,4 @@ inline QDataStream &amp;operator&gt;&gt;(QDataStream &amp;stream, Eigen::Matrix&lt; _Scalar, _Row @@ -83,56 +125,4 @@ inline QDataStream &amp;operator&gt;&gt;(QDataStream &amp;stream, Eigen::Matrix&lt; _Scalar, _Row
83 return stream; 125 return stream;
84 } 126 }
85 127
86 -/*Compute the mean of the each column (dim == 1) or row (dim == 2)  
87 - of the matrix*/  
88 -template<typename T>  
89 -Eigen::MatrixBase<T> eigMean(const Eigen::MatrixBase<T>& x,int dim)  
90 -{  
91 - if (dim == 1) {  
92 - Eigen::MatrixBase<T> y(1,x.cols());  
93 - for (int i = 0; i < x.cols(); i++)  
94 - y(i) = x.col(i).sum() / x.rows();  
95 - return y;  
96 - } else if (dim == 2) {  
97 - Eigen::MatrixBase<T> y(x.rows(),1);  
98 - for (int i = 0; i < x.rows(); i++)  
99 - y(i) = x.row(i).sum() / x.cols();  
100 - return y;  
101 - }  
102 - qFatal("A matrix can only have two dimensions");  
103 -}  
104 -  
105 -/*Compute the element-wise mean*/  
106 -float eigMean(const Eigen::MatrixXf& x);  
107 -/*Compute the element-wise mean*/  
108 -float eigStd(const Eigen::MatrixXf& x);  
109 -  
110 -/*Compute the std dev of the each column (dim == 1) or row (dim == 2)  
111 - of the matrix*/  
112 -template<typename T>  
113 -Eigen::MatrixBase<T> eigStd(const Eigen::MatrixBase<T>& x,int dim)  
114 -{  
115 - Eigen::MatrixBase<T> mean = eigMean(x, dim);  
116 - if (dim == 1) {  
117 - Eigen::MatrixBase<T> y(1,x.cols());  
118 - for (int i = 0; i < x.cols(); i++) {  
119 - T value = 0;  
120 - for (int j = 0; j < x.rows(); j++)  
121 - value += pow(y(j, i) - mean(i), 2);  
122 - y(i) = sqrt(value / (x.rows() - 1));  
123 - }  
124 - return y;  
125 - } else if (dim == 2) {  
126 - Eigen::MatrixBase<T> y(x.rows(),1);  
127 - for (int i = 0; i < x.rows(); i++) {  
128 - T value = 0;  
129 - for (int j = 0; j < x.cols(); j++)  
130 - value += pow(y(i, j) - mean(j), 2);  
131 - y(i) = sqrt(value / (x.cols() - 1));  
132 - }  
133 - return y;  
134 - }  
135 - qFatal("A matrix can only have two dimensions");  
136 -}  
137 -  
138 #endif // EIGENUTILS_H 128 #endif // EIGENUTILS_H
openbr/core/eval.cpp
@@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
18 #include "eval.h" 18 #include "eval.h"
19 #include "openbr/core/common.h" 19 #include "openbr/core/common.h"
20 #include "openbr/core/qtutils.h" 20 #include "openbr/core/qtutils.h"
  21 +#include "openbr/core/opencvutils.h"
21 #include <QMapIterator> 22 #include <QMapIterator>
22 23
23 using namespace cv; 24 using namespace cv;
@@ -1068,16 +1069,25 @@ float EvalDetection(const QString &amp;predictedGallery, const QString &amp;truthGallery @@ -1068,16 +1069,25 @@ float EvalDetection(const QString &amp;predictedGallery, const QString &amp;truthGallery
1068 return averageOverlap; 1069 return averageOverlap;
1069 } 1070 }
1070 1071
1071 -float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv, int normalizationIndexA, int normalizationIndexB) 1072 +static void projectAndWrite(Transform *t, const Template &src, const QString &filePath)
  1073 +{
  1074 + Template dst;
  1075 + t->project(src,dst);
  1076 + OpenCVUtils::saveImage(dst.m(),filePath);
  1077 +}
  1078 +
  1079 +float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv, int normalizationIndexA, int normalizationIndexB, int sampleIndex, int totalExamples)
1072 { 1080 {
1073 qDebug("Evaluating landmarking of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery)); 1081 qDebug("Evaluating landmarking of %s against %s", qPrintable(predictedGallery), qPrintable(truthGallery));
1074 - const TemplateList predicted(TemplateList::fromGallery(predictedGallery));  
1075 - const TemplateList truth(TemplateList::fromGallery(truthGallery));  
1076 - const QStringList predictedNames = File::get<QString>(predicted, "name");  
1077 - const QStringList truthNames = File::get<QString>(truth, "name"); 1082 + TemplateList predicted(TemplateList::fromGallery(predictedGallery));
  1083 + TemplateList truth(TemplateList::fromGallery(truthGallery));
  1084 + QStringList predictedNames = File::get<QString>(predicted, "name");
  1085 + QStringList truthNames = File::get<QString>(truth, "name");
1078 1086
1079 int skipped = 0; 1087 int skipped = 0;
1080 QList< QList<float> > pointErrors; 1088 QList< QList<float> > pointErrors;
  1089 + QList<float> imageErrors;
  1090 + QList<float> normalizedLengths;
1081 for (int i=0; i<predicted.size(); i++) { 1091 for (int i=0; i<predicted.size(); i++) {
1082 const QString &predictedName = predictedNames[i]; 1092 const QString &predictedName = predictedNames[i];
1083 const int truthIndex = truthNames.indexOf(predictedName); 1093 const int truthIndex = truthNames.indexOf(predictedName);
@@ -1085,39 +1095,94 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle @@ -1085,39 +1095,94 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle
1085 const QList<QPointF> predictedPoints = predicted[i].file.points(); 1095 const QList<QPointF> predictedPoints = predicted[i].file.points();
1086 const QList<QPointF> truthPoints = truth[truthIndex].file.points(); 1096 const QList<QPointF> truthPoints = truth[truthIndex].file.points();
1087 if (predictedPoints.size() != truthPoints.size() || truthPoints.contains(QPointF(-1,-1))) { 1097 if (predictedPoints.size() != truthPoints.size() || truthPoints.contains(QPointF(-1,-1))) {
1088 - skipped++; 1098 + predicted.removeAt(i);
  1099 + predictedNames.removeAt(i);
  1100 + truth.removeAt(i);
  1101 + truthNames.removeAt(i);
  1102 + i--; skipped++;
1089 continue; 1103 continue;
1090 } 1104 }
  1105 +
1091 while (pointErrors.size() < predictedPoints.size()) 1106 while (pointErrors.size() < predictedPoints.size())
1092 pointErrors.append(QList<float>()); 1107 pointErrors.append(QList<float>());
  1108 +
  1109 + // Want to know error for every image.
  1110 +
1093 if (normalizationIndexA >= truthPoints.size()) qFatal("Normalization index A is out of range."); 1111 if (normalizationIndexA >= truthPoints.size()) qFatal("Normalization index A is out of range.");
1094 if (normalizationIndexB >= truthPoints.size()) qFatal("Normalization index B is out of range."); 1112 if (normalizationIndexB >= truthPoints.size()) qFatal("Normalization index B is out of range.");
1095 const float normalizedLength = QtUtils::euclideanLength(truthPoints[normalizationIndexB] - truthPoints[normalizationIndexA]); 1113 const float normalizedLength = QtUtils::euclideanLength(truthPoints[normalizationIndexB] - truthPoints[normalizationIndexA]);
1096 - for (int j=0; j<predictedPoints.size(); j++)  
1097 - pointErrors[j].append(QtUtils::euclideanLength(predictedPoints[j] - truthPoints[j])/normalizedLength); 1114 + normalizedLengths.append(normalizedLength);
  1115 + float totalError = 0;
  1116 + for (int j=0; j<predictedPoints.size(); j++) {
  1117 + float error = QtUtils::euclideanLength(predictedPoints[j] - truthPoints[j])/normalizedLength;
  1118 + totalError += error;
  1119 + pointErrors[j].append(error);
  1120 + }
  1121 + imageErrors.append(totalError/predictedPoints.size());
1098 } 1122 }
1099 - qDebug() << "Skipped " << skipped << " files due to point size mismatch."; 1123 +
  1124 + qDebug() << "Skipped" << skipped << "files due to point size mismatch.";
1100 1125
1101 QList<float> averagePointErrors; averagePointErrors.reserve(pointErrors.size()); 1126 QList<float> averagePointErrors; averagePointErrors.reserve(pointErrors.size());
1102 - for (int i=0; i<pointErrors.size(); i++) {  
1103 - std::sort(pointErrors[i].begin(), pointErrors[i].end());  
1104 - averagePointErrors.append(Common::Mean(pointErrors[i]));  
1105 - }  
1106 - const float averagePointError = Common::Mean(averagePointErrors);  
1107 1127
1108 QStringList lines; 1128 QStringList lines;
1109 lines.append("Plot,X,Y"); 1129 lines.append("Plot,X,Y");
  1130 +
  1131 + QtUtils::touchDir(QDir("landmarking_examples_truth"));
  1132 + QtUtils::touchDir(QDir("landmarking_examples_predicted"));
  1133 +
  1134 + // Example
  1135 + {
  1136 + QScopedPointer<Transform> t(Transform::make("Open+Draw(verbose,rects=false,location=false)",NULL));
  1137 +
  1138 + QString filePath = "landmarking_examples_truth/"+truth[sampleIndex].file.fileName();
  1139 + projectAndWrite(t.data(), truth[sampleIndex],filePath);
  1140 + lines.append("Sample,"+filePath+","+QString::number(truth[sampleIndex].file.points().size()));
  1141 + }
  1142 +
  1143 + // Get best and worst performing examples
  1144 + QList< QPair<float,int> > exampleIndices = Common::Sort(imageErrors,true);
  1145 +
  1146 + QScopedPointer<Transform> t(Transform::make("Open+Draw(rects=false)",NULL));
  1147 +
  1148 + for (int i=0; i<totalExamples; i++) {
  1149 + QString filePath = "landmarking_examples_truth/"+truth[exampleIndices[i].second].file.fileName();
  1150 + projectAndWrite(t.data(), truth[exampleIndices[i].second],filePath);
  1151 + lines.append("EXT,"+filePath+","+QString::number(exampleIndices[i].first));
  1152 +
  1153 + filePath = "landmarking_examples_predicted/"+predicted[exampleIndices[i].second].file.fileName();
  1154 + projectAndWrite(t.data(), predicted[exampleIndices[i].second],filePath);
  1155 + lines.append("EXP,"+filePath+","+QString::number(exampleIndices[i].first));
  1156 + }
  1157 +
  1158 + for (int i=exampleIndices.size()-1; i>exampleIndices.size()-totalExamples-1; i--) {
  1159 + QString filePath = "landmarking_examples_truth/"+truth[exampleIndices[i].second].file.fileName();
  1160 + projectAndWrite(t.data(), truth[exampleIndices[i].second],filePath);
  1161 + lines.append("EXT,"+filePath+","+QString::number(exampleIndices[i].first));
  1162 +
  1163 + filePath = "landmarking_examples_predicted/"+predicted[exampleIndices[i].second].file.fileName();
  1164 + projectAndWrite(t.data(), predicted[exampleIndices[i].second],filePath);
  1165 + lines.append("EXP,"+filePath+","+QString::number(exampleIndices[i].first));
  1166 + }
  1167 +
1110 for (int i=0; i<pointErrors.size(); i++) { 1168 for (int i=0; i<pointErrors.size(); i++) {
  1169 + std::sort(pointErrors[i].begin(), pointErrors[i].end());
  1170 + averagePointErrors.append(Common::Mean(pointErrors[i]));
1111 const QList<float> &pointError = pointErrors[i]; 1171 const QList<float> &pointError = pointErrors[i];
1112 const int keep = qMin(Max_Points, pointError.size()); 1172 const int keep = qMin(Max_Points, pointError.size());
1113 for (int j=0; j<keep; j++) 1173 for (int j=0; j<keep; j++)
1114 lines.append(QString("Box,%1,%2").arg(QString::number(i), QString::number(pointError[j*(pointError.size()-1)/(keep-1)]))); 1174 lines.append(QString("Box,%1,%2").arg(QString::number(i), QString::number(pointError[j*(pointError.size()-1)/(keep-1)])));
1115 } 1175 }
1116 1176
  1177 + const float averagePointError = Common::Mean(averagePointErrors);
  1178 +
1117 lines.append(QString("AvgError,0,%1").arg(averagePointError)); 1179 lines.append(QString("AvgError,0,%1").arg(averagePointError));
  1180 + lines.append(QString("NormLength,0,%1").arg(Common::Mean(normalizedLengths)));
1118 1181
1119 QtUtils::writeFile(csv, lines); 1182 QtUtils::writeFile(csv, lines);
1120 - qDebug("Average Error: %.3f", averagePointError); 1183 +
  1184 + qDebug("Average Error for all Points: %.3f", averagePointError);
  1185 +
1121 return averagePointError; 1186 return averagePointError;
1122 } 1187 }
1123 1188
openbr/core/eval.h
@@ -31,7 +31,7 @@ namespace br @@ -31,7 +31,7 @@ namespace br
31 31
32 void EvalClassification(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); 32 void EvalClassification(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = "");
33 float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", bool normalize = false, int minSize = 0, int maxSize = 0); // Return average overlap 33 float EvalDetection(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", bool normalize = false, int minSize = 0, int maxSize = 0); // Return average overlap
34 - float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", int normalizationIndexA = 0, int normalizationIndexB = 1); // Return average error 34 + float EvalLandmarking(const QString &predictedGallery, const QString &truthGallery, const QString &csv = "", int normalizationIndexA = 0, int normalizationIndexB = 1, int sampleIndex = 0, int totalExamples = 5); // Return average error
35 void EvalRegression(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = ""); 35 void EvalRegression(const QString &predictedGallery, const QString &truthGallery, QString predictedProperty = "", QString truthProperty = "");
36 } 36 }
37 37
openbr/core/plot.cpp
@@ -482,16 +482,115 @@ bool PlotLandmarking(const QStringList &amp;files, const File &amp;destination, bool sho @@ -482,16 +482,115 @@ bool PlotLandmarking(const QStringList &amp;files, const File &amp;destination, bool sho
482 qDebug("Plotting %d landmarking file(s) to %s", files.size(), qPrintable(destination)); 482 qDebug("Plotting %d landmarking file(s) to %s", files.size(), qPrintable(destination));
483 RPlot p(files, destination, false); 483 RPlot p(files, destination, false);
484 484
485 - p.file.write("# Split data into individual plots\n"  
486 - "plot_index = which(names(data)==\"Plot\")\n"  
487 - "Box <- data[grep(\"Box\",data$Plot),-c(1)]\n"  
488 - "rm(data)\n"  
489 - "\n");  
490 -  
491 - p.file.write(qPrintable(QString("ggplot(Box, aes(Y,%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + 485 + p.file.write(qPrintable(QString("# Split data into individual plots\n"
  486 + "plot_index = which(names(data)==\"Plot\")\n"
  487 + "Box <- data[grep(\"Box\",data$Plot),-c(1)]\n"
  488 + "Box$X <- factor(Box$X, levels = Box$X, ordered = TRUE)\n"
  489 + "Sample <- data[grep(\"Sample\",data$Plot),-c(1)]\n"
  490 + "Sample$X <- as.character(Sample$X)\n"
  491 + "EXT <- data[grep(\"EXT\",data$Plot),-c(1)]\n"
  492 + "EXT$X <- as.character(EXT$X)\n"
  493 + "EXP <- data[grep(\"EXP\",data$Plot),-c(1)]\n"
  494 + "EXP$X <- as.character(EXP$X)\n"
  495 + "NormLength <- data[grep(\"NormLength\",data$Plot),-c(1)]\n"
  496 + "rm(data)\n"
  497 + "\n")));
  498 +
  499 + p.file.write(qPrintable(QString("summarySE <- function(data=NULL, measurevar, groupvars=NULL, na.rm=FALSE, conf.interval=.95, .drop=TRUE) {\n\t"
  500 + "require(plyr)\n\n\tlength2 <- function (x, na.rm=FALSE) {\n\t\tif (na.rm) sum(!is.na(x))\n\t\telse length(x)"
  501 + "\n\t}\n\n\tdatac <- ddply(data, groupvars, .drop=.drop, .fun = function(xx, col) {\n\t\t"
  502 + "c(N=length2(xx[[col]], na.rm=na.rm), mean=mean(xx[[col]], na.rm=na.rm), sd=sd(xx[[col]], na.rm=na.rm))\n\t\t},"
  503 + "\n\t\tmeasurevar\n\t)\n\n\tdatac <- rename(datac, c(\"mean\" = measurevar))\n\tdatac$se <- datac$sd / sqrt(datac$N)"
  504 + "\n\tciMult <- qt(conf.interval/2 + .5, datac$N-1)\n\tdatac$ci <- datac$se * ciMult\n\n\treturn(datac)\n}\n")));
  505 +
  506 +
  507 + p.file.write(qPrintable(QString("\nreadData <- function(data) {\n\texamples <- list()\n"
  508 + "\tfor (i in 1:nrow(data)) {\n"
  509 + "\t\tpath <- data[i,1]\n"
  510 + "\t\tvalue <- data[i,2]\n"
  511 + "\t\tfile <- unlist(strsplit(path, \"[.]\"))[1]\n"
  512 + "\t\text <- unlist(strsplit(path, \"[.]\"))[2]\n"
  513 + "\t\tif (ext == \"jpg\" || ext == \"JPEG\" || ext == \"jpeg\" || ext == \"JPG\") {\n"
  514 + "\t\t\timg <- readJPEG(path)\n"
  515 + "\t\t} else if (ext == \"PNG\" || ext == \"png\") {\n"
  516 + "\t\t\timg <- readPNG(path)\n"
  517 + "\t\t} else if (ext == \"TIFF\" || ext == \"tiff\" || ext == \"TIF\" || ext == \"tif\") { \n"
  518 + "\t\t\timg <- readTIFF(path)\n"
  519 + "}else {\n"
  520 + "\t\t\tnext\n"
  521 + "\t\t}\n"
  522 + "\t\texample <- list(file = file, value = value, image = img)\n"
  523 + "\t\texamples[[i]] <- example\n"
  524 + "\t}\n"
  525 + "\treturn(examples)\n"
  526 + "}\n")));
  527 +
  528 + p.file.write(qPrintable(QString("\nlibrary(jpeg)\n"
  529 + "library(png)\n"
  530 + "library(grid)\n"
  531 + "multiplot <- function(..., plotlist=NULL, cols) {\n"
  532 + "\trequire(grid)\n"
  533 + "\t# Make a list from the ... arguments and plotlist\n"
  534 + "\tplots <- c(list(...), plotlist)\n"
  535 + "\tnumPlots = length(plots)\n"
  536 + "\t# Make the panel\n"
  537 + "\tplotCols = cols\n"
  538 + "\tplotRows = ceiling(numPlots/plotCols)\n"
  539 + "\t# Set up the page\n"
  540 + "\tgrid.newpage()\n"
  541 + "\tpushViewport(viewport(layout = grid.layout(plotRows, plotCols)))\n"
  542 + "\tvplayout <- function(x, y)\n"
  543 + "\tviewport(layout.pos.row = x, layout.pos.col = y)\n"
  544 + "\t# Make each plot, in the correct location\n"
  545 + "\tfor (i in 1:numPlots) {\n"
  546 + "\t\tcurRow = ceiling(i/plotCols)\n"
  547 + "\t\tcurCol = (i-1) %% plotCols + 1\n"
  548 + "\t\tprint(plots[[i]], vp = vplayout(curRow, curCol))\n"
  549 + "\t}\n"
  550 + "}\n")));
  551 +
  552 + p.file.write(qPrintable(QString("\nplotImage <- function(image, title=NULL, label=NULL) { \n"
  553 + "\tp <- qplot(1:10, 1:10, geom=\"blank\") + annotation_custom(rasterGrob(image$image), xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + theme(axis.line=element_blank(), axis.title.y=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), line=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) + labs(title=title) + xlab(label)\n"
  554 + "\treturn(p)"
  555 + "}\n")));
  556 +
  557 + p.file.write(qPrintable(QString("\nsample <- readData(Sample) \n"
  558 + "rows <- sample[[1]]$value\n"
  559 + "algs <- unique(Box$%1)\n"
  560 + "algs <- algs[!duplicated(algs)]\n"
  561 + "print(plotImage(sample[[1]],\"Sample Landmarks\",sprintf(\"Total Landmarks: %s\",sample[[1]]$value))) \n"
  562 + "if (nrow(EXT) != 0 && nrow(EXP)) {\n"
  563 + "\tfor (j in 1:length(algs)) {\n"
  564 + "\ttruthSample <- readData(EXT[EXT$. == algs[[j]],])\n"
  565 + "\tpredictedSample <- readData(EXP[EXP$. == algs[[j]],])\n"
  566 + "\t\tfor (i in 1:length(predictedSample)) {\n"
  567 + "\t\t\tmultiplot(plotImage(predictedSample[[i]],sprintf(\"%s\\nPredicted Landmarks\",algs[[j]]),sprintf(\"Average Landmark Error: %.3f\",predictedSample[[i]]$value)),plotImage(truthSample[[i]],\"Ground Truth\\nLandmarks\",\"\"),cols=2)\n"
  568 + "\t\t}\n"
  569 + "\t}\n"
  570 + "}\n").arg(p.major.size > 1 ? p.major.header : (p.minor.header.isEmpty() ? p.major.header : p.minor.header))));
  571 +
  572 + p.file.write(qPrintable(QString("\n"
  573 + "# Code to format error table\n"
  574 + "StatBox <- summarySE(Box, measurevar=\"Y\", groupvars=c(\"%1\",\"X\"))\n"
  575 + "OverallStatBox <- summarySE(Box, measurevar=\"Y\", groupvars=c(\"%1\"))\n"
  576 + "mat <- matrix(paste(as.character(round(StatBox$Y, 3)), round(StatBox$ci, 3), sep=\" \\u00b1 \"),nrow=rows,ncol=length(algs),byrow=FALSE)\n"
  577 + "mat <- rbind(mat, paste(as.character(round(OverallStatBox$Y, 3)), round(OverallStatBox$ci, 3), sep=\" \\u00b1 \"))\n"
  578 + "mat <- rbind(mat, as.character(round(NormLength$Y, 3)))\n"
  579 + "colnames(mat) <- algs\n"
  580 + "rownames(mat) <- c(seq(0,rows-1),\"Aggregate\",\"Average IPD\")\n"
  581 + "ETable <- as.table(mat)\n").arg(p.major.size > 1 ? p.major.header : (p.minor.header.isEmpty() ? p.major.header : p.minor.header))));
  582 +
  583 + p.file.write(qPrintable(QString("\n"
  584 + "print(textplot(ETable))\n"
  585 + "print(title(\"Landmarking Error Rates\"))\n")));
  586 +
  587 + p.file.write(qPrintable(QString("ggplot(Box, aes(Y,%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(),
  588 + p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) +
492 QString(" + annotation_logticks(sides=\"b\") + stat_ecdf() + scale_x_log10(\"Normalized Error\", breaks=c(0.001,0.01,0.1,1,10)) + scale_y_continuous(\"Cumulative Density\", label=percent) + theme_minimal()\n\n"))); 589 QString(" + annotation_logticks(sides=\"b\") + stat_ecdf() + scale_x_log10(\"Normalized Error\", breaks=c(0.001,0.01,0.1,1,10)) + scale_y_continuous(\"Cumulative Density\", label=percent) + theme_minimal()\n\n")));
  590 +
493 p.file.write(qPrintable(QString("ggplot(Box, aes(factor(X), Y%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + 591 p.file.write(qPrintable(QString("ggplot(Box, aes(factor(X), Y%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) +
494 - QString("+ annotation_logticks(sides=\"l\") + geom_boxplot(alpha=0.5) + geom_jitter(size=1, alpha=0.5) + scale_x_discrete(\"Landmark\") + scale_y_log10(\"Normalized Error\", breaks=c(0.01,0.1,1,10)) + theme_minimal()\n\n"))); 592 + QString("+ annotation_logticks(sides=\"l\") + geom_boxplot(alpha=0.5) + geom_jitter(size=1, alpha=0.5) + scale_x_discrete(\"Landmark\") + scale_y_log10(\"Normalized Error\", breaks=c(0.001,0.01,0.1,1,10)) + theme_minimal()\n\n")));
  593 +
495 p.file.write(qPrintable(QString("ggplot(Box, aes(factor(X), Y%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + 594 p.file.write(qPrintable(QString("ggplot(Box, aes(factor(X), Y%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) +
496 QString("+ annotation_logticks(sides=\"l\") + geom_violin(alpha=0.5) + scale_x_discrete(\"Landmark\") + scale_y_log10(\"Normalized Error\", breaks=c(0.001,0.01,0.1,1,10))\n\n"))); 595 QString("+ annotation_logticks(sides=\"l\") + geom_violin(alpha=0.5) + scale_x_discrete(\"Landmark\") + scale_y_log10(\"Normalized Error\", breaks=c(0.001,0.01,0.1,1,10))\n\n")));
497 596
openbr/core/qtutils.cpp
@@ -500,6 +500,131 @@ QString getAbsolutePath(const QString &amp;filename) @@ -500,6 +500,131 @@ QString getAbsolutePath(const QString &amp;filename)
500 return QFileInfo(filename).absoluteFilePath(); 500 return QFileInfo(filename).absoluteFilePath();
501 } 501 }
502 502
  503 +BlockCompression::BlockCompression(QIODevice *_basis)
  504 +{
  505 + blockSize = 100000000;
  506 + setBasis(_basis);
  507 +}
  508 +
  509 +BlockCompression::BlockCompression() { blockSize = 100000000; };
  510 +
  511 +
  512 +bool BlockCompression::open(QIODevice::OpenMode mode)
  513 +{
  514 + this->setOpenMode(mode);
  515 + bool res = basis->open(mode);
  516 +
  517 + if (!res)
  518 + return false;
  519 +
  520 + blockReader.setDevice(basis);
  521 + blockWriter.setDevice(basis);
  522 +
  523 + if (mode & QIODevice::WriteOnly) {
  524 + precompressedBlockWriter = new QBuffer;
  525 + precompressedBlockWriter->open(QIODevice::ReadWrite);
  526 + }
  527 + else if (mode & QIODevice::ReadOnly) {
  528 + QByteArray compressedBlock;
  529 + blockReader >> compressedBlock;
  530 +
  531 + decompressedBlock = qUncompress(compressedBlock);
  532 + decompressedBlockReader.setBuffer(&decompressedBlock);
  533 + decompressedBlockReader.open(QIODevice::ReadOnly);
  534 + }
  535 +
  536 + return true;
  537 +}
  538 +
  539 +void BlockCompression::close()
  540 +{
  541 + // flush output buffer
  542 + if ((openMode() & QIODevice::WriteOnly) && precompressedBlockWriter) {
  543 + QByteArray compressedBlock = qCompress(precompressedBlockWriter->buffer(), -1);
  544 + blockWriter << compressedBlock;
  545 + }
  546 + basis->close();
  547 +}
  548 +
  549 +void BlockCompression::setBasis(QIODevice *_basis)
  550 +{
  551 + basis = _basis;
  552 + blockReader.setDevice(basis);
  553 + blockWriter.setDevice(basis);
  554 +}
  555 +
  556 +// read from current decompressed block, if out of space, read and decompress another
  557 +// block from basis
  558 +qint64 BlockCompression::readData(char *data, qint64 remaining)
  559 +{
  560 + qint64 read = 0;
  561 + while (remaining > 0) {
  562 + qint64 single_read = decompressedBlockReader.read(data, remaining);
  563 + if (single_read == -1)
  564 + qFatal("miss read");
  565 +
  566 + remaining -= single_read;
  567 + read += single_read;
  568 + data += single_read;
  569 +
  570 + // need a new block
  571 + if (remaining > 0) {
  572 + QByteArray compressedBlock;
  573 + blockReader >> compressedBlock;
  574 + if (compressedBlock.size() == 0) {
  575 + return read;
  576 + }
  577 + decompressedBlock = qUncompress(compressedBlock);
  578 +
  579 + decompressedBlockReader.close();
  580 + decompressedBlockReader.setBuffer(&decompressedBlock);
  581 + decompressedBlockReader.open(QIODevice::ReadOnly);
  582 + }
  583 + }
  584 + return blockReader.atEnd() && !basis->isReadable() ? -1 : read;
  585 +}
  586 +
  587 +bool BlockCompression::isSequential() const
  588 +{
  589 + return true;
  590 +}
  591 +
  592 +qint64 BlockCompression::writeData(const char *data, qint64 remaining)
  593 +{
  594 + qint64 written = 0;
  595 +
  596 + while (remaining > 0) {
  597 + // how much more can be put in this buffer?
  598 + qint64 capacity = blockSize - precompressedBlockWriter->pos();
  599 +
  600 + // don't try to write beyond capacity
  601 + qint64 write_size = qMin(capacity, remaining);
  602 +
  603 + qint64 singleWrite = precompressedBlockWriter->write(data, write_size);
  604 + // ignore the error case here, we consdier basis's failure mode the real
  605 + // end case
  606 + if (singleWrite == -1)
  607 + singleWrite = 0;
  608 +
  609 + remaining -= singleWrite;
  610 + data += singleWrite;
  611 + written += singleWrite;
  612 +
  613 + if (remaining > 0) {
  614 + QByteArray compressedBlock = qCompress(precompressedBlockWriter->buffer(), -1);
  615 +
  616 + if (compressedBlock.size() != 0)
  617 + blockWriter << compressedBlock;
  618 +
  619 + delete precompressedBlockWriter;
  620 + precompressedBlockWriter = new QBuffer;
  621 + precompressedBlockWriter->open(QIODevice::ReadWrite);
  622 + }
  623 + }
  624 + return basis->isWritable() ? written : -1;
  625 +}
  626 +
  627 +
503 628
504 } // namespace QtUtils 629 } // namespace QtUtils
505 630
openbr/core/qtutils.h
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 #ifndef QTUTILS_QTUTILS_H 17 #ifndef QTUTILS_QTUTILS_H
18 #define QTUTILS_QTUTILS_H 18 #define QTUTILS_QTUTILS_H
19 19
  20 +#include <QBuffer>
20 #include <QByteArray> 21 #include <QByteArray>
21 #include <QDir> 22 #include <QDir>
22 #include <QFile> 23 #include <QFile>
@@ -93,6 +94,38 @@ namespace QtUtils @@ -93,6 +94,38 @@ namespace QtUtils
93 94
94 /**** Rect Utilities ****/ 95 /**** Rect Utilities ****/
95 float overlap(const QRectF &r, const QRectF &s); 96 float overlap(const QRectF &r, const QRectF &s);
  97 +
  98 +
  99 + class BlockCompression : public QIODevice
  100 + {
  101 + public:
  102 + BlockCompression(QIODevice *_basis);
  103 + BlockCompression();
  104 + int blockSize;
  105 + QIODevice *basis;
  106 +
  107 + bool open(QIODevice::OpenMode mode);
  108 +
  109 + void close();
  110 +
  111 + void setBasis(QIODevice *_basis);
  112 +
  113 + QDataStream blockReader;
  114 + QByteArray decompressedBlock;
  115 + QBuffer decompressedBlockReader;
  116 +
  117 + // read from current decompressed block, if out of space, read and decompress another
  118 + // block from basis
  119 + qint64 readData(char *data, qint64 remaining);
  120 +
  121 + bool isSequential() const;
  122 +
  123 + // write to a QByteArray, when max block sized is reached, compress and write
  124 + // it to basis
  125 + QBuffer * precompressedBlockWriter;
  126 + QDataStream blockWriter;
  127 + qint64 writeData(const char *data, qint64 remaining);
  128 + };
96 } 129 }
97 130
98 #endif // QTUTILS_QTUTILS_H 131 #endif // QTUTILS_QTUTILS_H
openbr/openbr.cpp
@@ -134,9 +134,9 @@ float br_eval_detection(const char *predicted_gallery, const char *truth_gallery @@ -134,9 +134,9 @@ float br_eval_detection(const char *predicted_gallery, const char *truth_gallery
134 return EvalDetection(predicted_gallery, truth_gallery, csv, normalize, minSize, maxSize); 134 return EvalDetection(predicted_gallery, truth_gallery, csv, normalize, minSize, maxSize);
135 } 135 }
136 136
137 -float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv, int normalization_index_a, int normalization_index_b) 137 +float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv, int normalization_index_a, int normalization_index_b, int sample_index, int total_examples)
138 { 138 {
139 - return EvalLandmarking(predicted_gallery, truth_gallery, csv, normalization_index_a, normalization_index_b); 139 + return EvalLandmarking(predicted_gallery, truth_gallery, csv, normalization_index_a, normalization_index_b, sample_index, total_examples);
140 } 140 }
141 141
142 void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char *predicted_property, const char *truth_property) 142 void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char *predicted_property, const char *truth_property)
openbr/openbr.h
@@ -214,8 +214,10 @@ BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *tru @@ -214,8 +214,10 @@ BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *tru
214 * \param csv Optional \c .csv file to contain performance metrics. 214 * \param csv Optional \c .csv file to contain performance metrics.
215 * \param normalization_index_a Optional first index in the list of points to use for normalization. 215 * \param normalization_index_a Optional first index in the list of points to use for normalization.
216 * \param normalization_index_b Optional second index in the list of points to use for normalization. 216 * \param normalization_index_b Optional second index in the list of points to use for normalization.
  217 + * \param sample_index Optional index for sample landmark image in ground truth gallery.
  218 + * \param total_examples Optional number of accurate and inaccurate examples to display.
217 */ 219 */
218 -BR_EXPORT float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv = "", int normalization_index_a = 0, int normalization_index_b = 1); 220 +BR_EXPORT float br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv = "", int normalization_index_a = 0, int normalization_index_b = 1, int sample_index = 0, int total_examples = 5);
219 221
220 /*! 222 /*!
221 * \brief Evaluates regression accuracy to disk. 223 * \brief Evaluates regression accuracy to disk.
openbr/plugins/algorithms.cpp
@@ -31,15 +31,15 @@ class AlgorithmsInitializer : public Initializer @@ -31,15 +31,15 @@ class AlgorithmsInitializer : public Initializer
31 void initialize() const 31 void initialize() const
32 { 32 {
33 // Face 33 // Face
34 - Globals->abbreviations.insert("FaceRecognition", "FaceDetection+Expand+<FaceRecognitionRegistration>+Expand+<FaceRecognitionExtraction>+<FaceRecognitionEmbedding>+<FaceRecognitionQuantization>+SetMetadata(AlgorithmID,-1):MatchProbability(ByteL1)");  
35 - Globals->abbreviations.insert("GenderClassification", "FaceDetection+Expand+<FaceClassificationRegistration>+Expand+<FaceClassificationExtraction>+<GenderClassifier>+Discard");  
36 - Globals->abbreviations.insert("AgeRegression", "FaceDetection+Expand+<FaceClassificationRegistration>+Expand+<FaceClassificationExtraction>+<AgeRegressor>+Discard"); 34 + Globals->abbreviations.insert("FaceRecognition", "FaceDetection+FaceRecognitionRegistration+<FaceRecognitionExtraction>+<FaceRecognitionEmbedding>+<FaceRecognitionQuantization>+SetMetadata(AlgorithmID,-1):Unit(ByteL1)");
  35 + Globals->abbreviations.insert("GenderClassification", "FaceDetection+Expand+FaceClassificationRegistration+Expand+<FaceClassificationExtraction>+<GenderClassifier>+Discard");
  36 + Globals->abbreviations.insert("AgeRegression", "FaceDetection+Expand+FaceClassificationRegistration+Expand+<FaceClassificationExtraction>+<AgeRegressor>+Discard");
37 Globals->abbreviations.insert("FaceQuality", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(64,64,0.25,0.35)+ImageQuality+Cvt(Gray)+DFFS+Discard"); 37 Globals->abbreviations.insert("FaceQuality", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(64,64,0.25,0.35)+ImageQuality+Cvt(Gray)+DFFS+Discard");
38 Globals->abbreviations.insert("MedianFace", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(256,256,0.37,0.45)+Center(Median)"); 38 Globals->abbreviations.insert("MedianFace", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(256,256,0.37,0.45)+Center(Median)");
39 Globals->abbreviations.insert("BlurredFaceDetection", "Open+LimitSize(1024)+SkinMask/(Cvt(Gray)+GradientMask)+And+Morph(Erode,16)+LargestConvexArea"); 39 Globals->abbreviations.insert("BlurredFaceDetection", "Open+LimitSize(1024)+SkinMask/(Cvt(Gray)+GradientMask)+And+Morph(Erode,16)+LargestConvexArea");
40 Globals->abbreviations.insert("DrawFaceDetection", "Open+Cascade(FrontalFace)+Expand+ASEFEyes+Draw(inPlace=true)"); 40 Globals->abbreviations.insert("DrawFaceDetection", "Open+Cascade(FrontalFace)+Expand+ASEFEyes+Draw(inPlace=true)");
41 Globals->abbreviations.insert("ShowFaceDetection", "DrawFaceDetection+Contract+First+Show+Discard"); 41 Globals->abbreviations.insert("ShowFaceDetection", "DrawFaceDetection+Contract+First+Show+Discard");
42 - Globals->abbreviations.insert("DownloadFaceRecognition", "Download+Open+ROI+Expand+Cvt(Gray)+Cascade(FrontalFace)+Expand+<FaceRecognitionRegistration>+Expand+<FaceRecognitionExtraction>+<FaceRecognitionEmbedding>+<FaceRecognitionQuantization>+SetMetadata(AlgorithmID,-1):MatchProbability(ByteL1)"); 42 + Globals->abbreviations.insert("DownloadFaceRecognition", "Download+Open+ROI+Cvt(Gray)+Cascade(FrontalFace)+FaceRecognitionRegistration+<FaceRecognitionExtraction>+<FaceRecognitionEmbedding>+<FaceRecognitionQuantization>+SetMetadata(AlgorithmID,-1):Unit(ByteL1)");
43 Globals->abbreviations.insert("OpenBR", "FaceRecognition"); 43 Globals->abbreviations.insert("OpenBR", "FaceRecognition");
44 Globals->abbreviations.insert("GenderEstimation", "GenderClassification"); 44 Globals->abbreviations.insert("GenderEstimation", "GenderClassification");
45 Globals->abbreviations.insert("AgeEstimation", "AgeRegression"); 45 Globals->abbreviations.insert("AgeEstimation", "AgeRegression");
@@ -50,7 +50,7 @@ class AlgorithmsInitializer : public Initializer @@ -50,7 +50,7 @@ class AlgorithmsInitializer : public Initializer
50 // Video 50 // Video
51 Globals->abbreviations.insert("DisplayVideo", "FPSLimit(30)+Show(false,[FrameNumber])+Discard"); 51 Globals->abbreviations.insert("DisplayVideo", "FPSLimit(30)+Show(false,[FrameNumber])+Discard");
52 Globals->abbreviations.insert("PerFrameDetection", "SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+RestoreMat(original)+Draw(inPlace=true)+Show(false,[FrameNumber])+Discard"); 52 Globals->abbreviations.insert("PerFrameDetection", "SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+RestoreMat(original)+Draw(inPlace=true)+Show(false,[FrameNumber])+Discard");
53 - Globals->abbreviations.insert("AgeGenderDemo", "SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+<FaceClassificationRegistration>+<FaceClassificationExtraction>+<AgeRegressor>/<GenderClassifier>+Discard+RestoreMat(original)+Draw(inPlace=true)+DrawPropertiesPoint([Age,Gender],Affine_0,inPlace=true)+SaveMat(original)+Discard+Contract+RestoreMat(original)+FPSCalc+Show(false,[AvgFPS,Age,Gender])+Discard"); 53 + Globals->abbreviations.insert("AgeGenderDemo", "SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+FaceClassificationRegistration+<FaceClassificationExtraction>+<AgeRegressor>/<GenderClassifier>+Discard+RestoreMat(original)+Draw(inPlace=true)+DrawPropertiesPoint([Age,Gender],Affine_0,inPlace=true)+SaveMat(original)+Discard+Contract+RestoreMat(original)+FPSCalc+Show(false,[AvgFPS,Age,Gender])+Discard");
54 Globals->abbreviations.insert("ShowOpticalFlowField", "SaveMat(original)+AggregateFrames(2)+OpticalFlow(useMagnitude=false)+Grid(100,100)+DrawOpticalFlow+FPSLimit(30)+Show(false)+Discard"); 54 Globals->abbreviations.insert("ShowOpticalFlowField", "SaveMat(original)+AggregateFrames(2)+OpticalFlow(useMagnitude=false)+Grid(100,100)+DrawOpticalFlow+FPSLimit(30)+Show(false)+Discard");
55 Globals->abbreviations.insert("ShowOpticalFlowMagnitude", "AggregateFrames(2)+OpticalFlow+Normalize(Range,false,0,255)+Cvt(Color)+Draw+FPSLimit(30)+Show(false)+Discard"); 55 Globals->abbreviations.insert("ShowOpticalFlowMagnitude", "AggregateFrames(2)+OpticalFlow+Normalize(Range,false,0,255)+Cvt(Color)+Draw+FPSLimit(30)+Show(false)+Discard");
56 Globals->abbreviations.insert("ShowMotionSegmentation", "DropFrames(5)+AggregateFrames(2)+OpticalFlow+CvtUChar+WatershedSegmentation+DrawSegmentation+Draw+FPSLimit(30)+Show(false)+Discard"); 56 Globals->abbreviations.insert("ShowMotionSegmentation", "DropFrames(5)+AggregateFrames(2)+OpticalFlow+CvtUChar+WatershedSegmentation+DrawSegmentation+Draw+FPSLimit(30)+Show(false)+Discard");
@@ -92,11 +92,11 @@ class AlgorithmsInitializer : public Initializer @@ -92,11 +92,11 @@ class AlgorithmsInitializer : public Initializer
92 Globals->abbreviations.insert("DenseHOG", "Gradient+RectRegions(8,8,6,6)+Bin(0,360,8)+Hist(8)"); 92 Globals->abbreviations.insert("DenseHOG", "Gradient+RectRegions(8,8,6,6)+Bin(0,360,8)+Hist(8)");
93 Globals->abbreviations.insert("DenseSIFT", "(Grid(10,10)+SIFTDescriptor(12)+ByRow)"); 93 Globals->abbreviations.insert("DenseSIFT", "(Grid(10,10)+SIFTDescriptor(12)+ByRow)");
94 Globals->abbreviations.insert("DenseSIFT2", "(Grid(5,5)+SIFTDescriptor(12)+ByRow)"); 94 Globals->abbreviations.insert("DenseSIFT2", "(Grid(5,5)+SIFTDescriptor(12)+ByRow)");
95 - Globals->abbreviations.insert("FaceRecognitionRegistration", "(ASEFEyes+Affine(88,88,0.25,0.35)+DownsampleTraining(FTE(DFFS),instances=1))"); 95 + Globals->abbreviations.insert("FaceRecognitionRegistration", "ASEFEyes+Affine(88,88,0.25,0.35)");
96 Globals->abbreviations.insert("FaceRecognitionExtraction", "(Mask+DenseSIFT/DenseLBP+DownsampleTraining(PCA(0.95),instances=1)+Normalize(L2)+Cat)"); 96 Globals->abbreviations.insert("FaceRecognitionExtraction", "(Mask+DenseSIFT/DenseLBP+DownsampleTraining(PCA(0.95),instances=1)+Normalize(L2)+Cat)");
97 Globals->abbreviations.insert("FaceRecognitionEmbedding", "(Dup(12)+RndSubspace(0.05,1)+DownsampleTraining(LDA(0.98),instances=-2)+Cat+DownsampleTraining(PCA(768),instances=1))"); 97 Globals->abbreviations.insert("FaceRecognitionEmbedding", "(Dup(12)+RndSubspace(0.05,1)+DownsampleTraining(LDA(0.98),instances=-2)+Cat+DownsampleTraining(PCA(768),instances=1))");
98 Globals->abbreviations.insert("FaceRecognitionQuantization", "(Normalize(L1)+Quantize)"); 98 Globals->abbreviations.insert("FaceRecognitionQuantization", "(Normalize(L1)+Quantize)");
99 - Globals->abbreviations.insert("FaceClassificationRegistration", "(ASEFEyes+Affine(56,72,0.33,0.45)+FTE(DFFS))"); 99 + Globals->abbreviations.insert("FaceClassificationRegistration", "ASEFEyes+Affine(56,72,0.33,0.45)");
100 Globals->abbreviations.insert("FaceClassificationExtraction", "((Grid(7,7)+SIFTDescriptor(8)+ByRow)/DenseLBP+DownsampleTraining(PCA(0.95),instances=-1, inputVariable=Gender)+Cat)"); 100 Globals->abbreviations.insert("FaceClassificationExtraction", "((Grid(7,7)+SIFTDescriptor(8)+ByRow)/DenseLBP+DownsampleTraining(PCA(0.95),instances=-1, inputVariable=Gender)+Cat)");
101 Globals->abbreviations.insert("AgeRegressor", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Age)+DownsampleTraining(SVM(RBF,EPS_SVR,inputVariable=Age),instances=100, inputVariable=Age)"); 101 Globals->abbreviations.insert("AgeRegressor", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Age)+DownsampleTraining(SVM(RBF,EPS_SVR,inputVariable=Age),instances=100, inputVariable=Age)");
102 Globals->abbreviations.insert("GenderClassifier", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Gender)+DownsampleTraining(SVM(RBF,C_SVC,inputVariable=Gender),instances=4000, inputVariable=Gender)"); 102 Globals->abbreviations.insert("GenderClassifier", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Gender)+DownsampleTraining(SVM(RBF,C_SVC,inputVariable=Gender),instances=4000, inputVariable=Gender)");
openbr/plugins/cascade.cpp
@@ -252,6 +252,8 @@ class CascadeTransform : public MetaTransform @@ -252,6 +252,8 @@ class CascadeTransform : public MetaTransform
252 void init() 252 void init()
253 { 253 {
254 cascadeResource.setResourceMaker(new CascadeResourceMaker(model)); 254 cascadeResource.setResourceMaker(new CascadeResourceMaker(model));
  255 + if (model == "Ear" || model == "Eye" || model == "FrontalFace" || model == "ProfileFace")
  256 + this->trainable = false;
255 } 257 }
256 258
257 // Train transform 259 // Train transform
openbr/plugins/draw.cpp
@@ -42,11 +42,15 @@ class DrawTransform : public UntrainableTransform @@ -42,11 +42,15 @@ class DrawTransform : public UntrainableTransform
42 Q_PROPERTY(bool rects READ get_rects WRITE set_rects RESET reset_rects STORED false) 42 Q_PROPERTY(bool rects READ get_rects WRITE set_rects RESET reset_rects STORED false)
43 Q_PROPERTY(bool inPlace READ get_inPlace WRITE set_inPlace RESET reset_inPlace STORED false) 43 Q_PROPERTY(bool inPlace READ get_inPlace WRITE set_inPlace RESET reset_inPlace STORED false)
44 Q_PROPERTY(int lineThickness READ get_lineThickness WRITE set_lineThickness RESET reset_lineThickness STORED false) 44 Q_PROPERTY(int lineThickness READ get_lineThickness WRITE set_lineThickness RESET reset_lineThickness STORED false)
  45 + Q_PROPERTY(bool named READ get_named WRITE set_named RESET reset_named STORED false)
  46 + Q_PROPERTY(bool location READ get_location WRITE set_location RESET reset_location STORED false)
45 BR_PROPERTY(bool, verbose, false) 47 BR_PROPERTY(bool, verbose, false)
46 BR_PROPERTY(bool, points, true) 48 BR_PROPERTY(bool, points, true)
47 BR_PROPERTY(bool, rects, true) 49 BR_PROPERTY(bool, rects, true)
48 BR_PROPERTY(bool, inPlace, false) 50 BR_PROPERTY(bool, inPlace, false)
49 BR_PROPERTY(int, lineThickness, 1) 51 BR_PROPERTY(int, lineThickness, 1)
  52 + BR_PROPERTY(bool, named, true)
  53 + BR_PROPERTY(bool, location, true)
50 54
51 void project(const Template &src, Template &dst) const 55 void project(const Template &src, Template &dst) const
52 { 56 {
@@ -55,11 +59,12 @@ class DrawTransform : public UntrainableTransform @@ -55,11 +59,12 @@ class DrawTransform : public UntrainableTransform
55 dst.m() = inPlace ? src.m() : src.m().clone(); 59 dst.m() = inPlace ? src.m() : src.m().clone();
56 60
57 if (points) { 61 if (points) {
58 - const QList<Point2f> pointsList = OpenCVUtils::toPoints(src.file.namedPoints() + src.file.points()); 62 + const QList<Point2f> pointsList = (named) ? OpenCVUtils::toPoints(src.file.points()+src.file.namedPoints()) : OpenCVUtils::toPoints(src.file.points());
59 for (int i=0; i<pointsList.size(); i++) { 63 for (int i=0; i<pointsList.size(); i++) {
60 const Point2f &point = pointsList[i]; 64 const Point2f &point = pointsList[i];
61 circle(dst, point, 3, color, -1); 65 circle(dst, point, 3, color, -1);
62 - if (verbose) putText(dst, QString("%1,(%2,%3)").arg(QString::number(i),QString::number(point.x),QString::number(point.y)).toStdString(), point, FONT_HERSHEY_SIMPLEX, 0.5, verboseColor, 1); 66 + QString label = (location) ? QString("%1,(%2,%3)").arg(QString::number(i),QString::number(point.x),QString::number(point.y)) : QString("%1").arg(QString::number(i));
  67 + if (verbose) putText(dst, label.toStdString(), point, FONT_HERSHEY_SIMPLEX, 0.5, verboseColor, 1);
63 } 68 }
64 } 69 }
65 if (rects) { 70 if (rects) {
openbr/plugins/eigen3.cpp
@@ -600,7 +600,7 @@ class SparseLDATransform : public Transform @@ -600,7 +600,7 @@ class SparseLDATransform : public Transform
600 600
601 //Only works on binary class problems for now 601 //Only works on binary class problems for now
602 assert(ldaOrig.projection.cols() == 1); 602 assert(ldaOrig.projection.cols() == 1);
603 - float ldaStd = eigStd(ldaOrig.projection); 603 + float ldaStd = EigenUtils::stddev(ldaOrig.projection);
604 for (int i = 0; i < ldaOrig.projection.rows(); i++) 604 for (int i = 0; i < ldaOrig.projection.rows(); i++)
605 if (abs(ldaOrig.projection(i)) > varThreshold * ldaStd) 605 if (abs(ldaOrig.projection(i)) > varThreshold * ldaStd)
606 selections.append(i); 606 selections.append(i);
openbr/plugins/filter.cpp
@@ -61,11 +61,23 @@ class BlurTransform : public UntrainableTransform @@ -61,11 +61,23 @@ class BlurTransform : public UntrainableTransform
61 { 61 {
62 Q_OBJECT 62 Q_OBJECT
63 Q_PROPERTY(float sigma READ get_sigma WRITE set_sigma RESET reset_sigma STORED false) 63 Q_PROPERTY(float sigma READ get_sigma WRITE set_sigma RESET reset_sigma STORED false)
  64 + Q_PROPERTY(bool ROI READ get_ROI WRITE set_ROI RESET reset_ROI STORED false)
64 BR_PROPERTY(float, sigma, 1) 65 BR_PROPERTY(float, sigma, 1)
  66 + BR_PROPERTY(bool, ROI, false)
65 67
66 void project(const Template &src, Template &dst) const 68 void project(const Template &src, Template &dst) const
67 { 69 {
68 - GaussianBlur(src, dst, Size(0,0), sigma); 70 + if (!ROI) GaussianBlur(src, dst, Size(0,0), sigma);
  71 + else {
  72 + dst.m() = src.m();
  73 + foreach (const QRectF &rect, src.file.rects()) {
  74 + Rect region(rect.x(), rect.y(), rect.width(), rect.height());
  75 + Mat input = dst.m();
  76 + Mat output = input.clone();
  77 + GaussianBlur(input(region), output(region), Size(0,0), sigma);
  78 + dst.m() = output;
  79 + }
  80 + }
69 } 81 }
70 }; 82 };
71 83
openbr/plugins/gui.cpp
@@ -764,7 +764,8 @@ public: @@ -764,7 +764,8 @@ public:
764 } 764 }
765 else 765 else
766 { 766 {
767 - dst[i].file.set(labelSet[idx], rectSet[idx]); 767 + if (labels.isEmpty()) dst[i].file.appendRect(rectSet[idx]);
  768 + else dst[i].file.set(labelSet[idx], rectSet[idx]);
768 } 769 }
769 } 770 }
770 } 771 }
openbr/plugins/meta.cpp
@@ -17,6 +17,8 @@ @@ -17,6 +17,8 @@
17 #include <QFutureSynchronizer> 17 #include <QFutureSynchronizer>
18 #include <QRegularExpression> 18 #include <QRegularExpression>
19 #include <QtConcurrentRun> 19 #include <QtConcurrentRun>
  20 +#include <qbuffer.h>
  21 +
20 #include "openbr_internal.h" 22 #include "openbr_internal.h"
21 #include "openbr/core/common.h" 23 #include "openbr/core/common.h"
22 #include "openbr/core/opencvutils.h" 24 #include "openbr/core/opencvutils.h"
@@ -94,17 +96,15 @@ class PipeTransform : public CompositeTransform @@ -94,17 +96,15 @@ class PipeTransform : public CompositeTransform
94 96
95 int i = 0; 97 int i = 0;
96 while (i < transforms.size()) { 98 while (i < transforms.size()) {
97 - fprintf(stderr, "\n%s", qPrintable(transforms[i]->objectName()));  
98 -  
99 // Conditional statement covers likely case that first transform is untrainable 99 // Conditional statement covers likely case that first transform is untrainable
100 if (transforms[i]->trainable) { 100 if (transforms[i]->trainable) {
101 - fprintf(stderr, " training..."); 101 + qDebug() << "Training " << transforms[i]->description() << "\n...";
102 transforms[i]->train(dataLines); 102 transforms[i]->train(dataLines);
103 } 103 }
104 104
105 // if the transform is time varying, we can't project it in parallel 105 // if the transform is time varying, we can't project it in parallel
106 if (transforms[i]->timeVarying()) { 106 if (transforms[i]->timeVarying()) {
107 - fprintf(stderr, "\n%s projecting...", qPrintable(transforms[i]->objectName())); 107 + qDebug() << "Projecting " << transforms[i]->description() << "\n...";
108 for (int j=0; j < dataLines.size();j++) { 108 for (int j=0; j < dataLines.size();j++) {
109 TemplateList junk; 109 TemplateList junk;
110 splitFTEs(dataLines[j], junk); 110 splitFTEs(dataLines[j], junk);
@@ -130,7 +130,16 @@ class PipeTransform : public CompositeTransform @@ -130,7 +130,16 @@ class PipeTransform : public CompositeTransform
130 !transforms[nextTrainableTransform]->timeVarying()) 130 !transforms[nextTrainableTransform]->timeVarying())
131 nextTrainableTransform++; 131 nextTrainableTransform++;
132 132
133 - fprintf(stderr, " projecting..."); 133 + // No more trainable transforms? Don't need any more projects then
  134 + if (nextTrainableTransform == transforms.size())
  135 + break;
  136 +
  137 + fprintf(stderr, "Projecting %s", qPrintable(transforms[i]->description()));
  138 + for (int j=i+1; j < nextTrainableTransform; j++)
  139 + fprintf(stderr,"+%s", qPrintable(transforms[j]->description()));
  140 + fprintf(stderr, "\n...\n");
  141 + fflush(stderr);
  142 +
134 QFutureSynchronizer<void> futures; 143 QFutureSynchronizer<void> futures;
135 for (int j=0; j < dataLines.size(); j++) 144 for (int j=0; j < dataLines.size(); j++)
136 futures.addFuture(QtConcurrent::run(this, &PipeTransform::_projectPartial, &dataLines[j], i, nextTrainableTransform)); 145 futures.addFuture(QtConcurrent::run(this, &PipeTransform::_projectPartial, &dataLines[j], i, nextTrainableTransform));
@@ -510,7 +519,6 @@ class LoadStoreTransform : public MetaTransform @@ -510,7 +519,6 @@ class LoadStoreTransform : public MetaTransform
510 519
511 public: 520 public:
512 Transform *transform; 521 Transform *transform;
513 - QString baseName;  
514 522
515 LoadStoreTransform() : transform(NULL) {} 523 LoadStoreTransform() : transform(NULL) {}
516 524
@@ -540,8 +548,8 @@ private: @@ -540,8 +548,8 @@ private:
540 void init() 548 void init()
541 { 549 {
542 if (transform != NULL) return; 550 if (transform != NULL) return;
543 - if (fileName.isEmpty()) baseName = QRegExp("^[_a-zA-Z0-9]+$").exactMatch(transformString) ? transformString : QtUtils::shortTextHash(transformString);  
544 - else baseName = fileName; 551 + if (fileName.isEmpty()) fileName = QRegExp("^[_a-zA-Z0-9]+$").exactMatch(transformString) ? transformString : QtUtils::shortTextHash(transformString);
  552 +
545 if (!tryLoad()) 553 if (!tryLoad())
546 transform = make(transformString); 554 transform = make(transformString);
547 else 555 else
@@ -553,19 +561,28 @@ private: @@ -553,19 +561,28 @@ private:
553 return transform->timeVarying(); 561 return transform->timeVarying();
554 } 562 }
555 563
556 - void train(const TemplateList &data) 564 + void train(const QList<TemplateList> &data)
557 { 565 {
558 if (QFileInfo(getFileName()).exists()) 566 if (QFileInfo(getFileName()).exists())
559 return; 567 return;
560 568
561 transform->train(data); 569 transform->train(data);
562 570
563 - qDebug("Storing %s", qPrintable(baseName));  
564 - QByteArray byteArray;  
565 - QDataStream stream(&byteArray, QFile::WriteOnly);  
566 - stream << transform->description(); 571 + qDebug("Storing %s", qPrintable(fileName));
  572 + QtUtils::BlockCompression compressedOut;
  573 + QFile fout(fileName);
  574 + QtUtils::touchDir(fout);
  575 + compressedOut.setBasis(&fout);
  576 +
  577 + QDataStream stream(&compressedOut);
  578 + QString desc = transform->description();
  579 +
  580 + if (!compressedOut.open(QFile::WriteOnly))
  581 + qFatal("Failed to open %s for writing.", qPrintable(file));
  582 +
  583 + stream << desc;
567 transform->store(stream); 584 transform->store(stream);
568 - QtUtils::writeFile(baseName, byteArray, -1); 585 + compressedOut.close();
569 } 586 }
570 587
571 void project(const Template &src, Template &dst) const 588 void project(const Template &src, Template &dst) const
@@ -595,8 +612,8 @@ private: @@ -595,8 +612,8 @@ private:
595 612
596 QString getFileName() const 613 QString getFileName() const
597 { 614 {
598 - if (QFileInfo(baseName).exists()) return baseName;  
599 - const QString file = Globals->sdkPath + "/share/openbr/models/transforms/" + baseName; 615 + if (QFileInfo(fileName).exists()) return fileName;
  616 + const QString file = Globals->sdkPath + "/share/openbr/models/transforms/" + fileName;
600 return QFileInfo(file).exists() ? file : QString(); 617 return QFileInfo(file).exists() ? file : QString();
601 } 618 }
602 619
@@ -606,12 +623,19 @@ private: @@ -606,12 +623,19 @@ private:
606 if (file.isEmpty()) return false; 623 if (file.isEmpty()) return false;
607 624
608 qDebug("Loading %s", qPrintable(file)); 625 qDebug("Loading %s", qPrintable(file));
609 - QByteArray data;  
610 - QtUtils::readFile(file, data, true);  
611 - QDataStream stream(&data, QFile::ReadOnly); 626 + QFile fin(file);
  627 + QtUtils::BlockCompression reader(&fin);
  628 + if (!reader.open(QIODevice::ReadOnly)) {
  629 + if (QFileInfo(file).exists()) qFatal("Unable to open %s for reading. Check file permissions.", qPrintable(file));
  630 + else qFatal("Unable to open %s for reading. File does not exist.", qPrintable(file));
  631 + }
  632 +
  633 + QDataStream stream(&reader);
612 stream >> transformString; 634 stream >> transformString;
  635 +
613 transform = Transform::make(transformString); 636 transform = Transform::make(transformString);
614 transform->load(stream); 637 transform->load(stream);
  638 +
615 return true; 639 return true;
616 } 640 }
617 }; 641 };
openbr/plugins/misc.cpp
@@ -115,6 +115,9 @@ private: @@ -115,6 +115,9 @@ private:
115 void project(const Template &src, Template &dst) const 115 void project(const Template &src, Template &dst) const
116 { 116 {
117 dst.file = src.file; 117 dst.file = src.file;
  118 + if (Globals->verbose)
  119 + qDebug("Opening %s", qPrintable(src.file.flat()));
  120 +
118 if (src.empty()) { 121 if (src.empty()) {
119 const Mat img = imread(src.file.resolved().toStdString(), mode); 122 const Mat img = imread(src.file.resolved().toStdString(), mode);
120 if (img.data) dst.append(img); 123 if (img.data) dst.append(img);
@@ -126,6 +129,8 @@ private: @@ -126,6 +129,8 @@ private:
126 else dst.file.fte = true; 129 else dst.file.fte = true;
127 } 130 }
128 } 131 }
  132 + if (dst.file.fte)
  133 + qWarning("Error opening %s", qPrintable(src.file.flat()));
129 } 134 }
130 }; 135 };
131 BR_REGISTER(Transform, ReadTransform) 136 BR_REGISTER(Transform, ReadTransform)
openbr/plugins/process.cpp
@@ -349,6 +349,7 @@ public: @@ -349,6 +349,7 @@ public:
349 QByteArray data = readArray; 349 QByteArray data = readArray;
350 QDataStream deserializer(data); 350 QDataStream deserializer(data);
351 Transform *res = Transform::deserialize(deserializer); 351 Transform *res = Transform::deserialize(deserializer);
  352 + sendSignal(CommunicationManager::OUTPUT_AVAILABLE);
352 return res; 353 return res;
353 } 354 }
354 355
@@ -534,6 +535,8 @@ struct ProcessData @@ -534,6 +535,8 @@ struct ProcessData
534 class ProcessWrapperTransform : public WrapperTransform 535 class ProcessWrapperTransform : public WrapperTransform
535 { 536 {
536 Q_OBJECT 537 Q_OBJECT
  538 + Q_PROPERTY(int concurrentCount READ get_concurrentCount WRITE set_concurrentCount RESET reset_concurrentCount STORED false)
  539 + BR_PROPERTY(int, concurrentCount, 2)
537 540
538 QString baseKey; 541 QString baseKey;
539 542
@@ -579,17 +582,33 @@ class ProcessWrapperTransform : public WrapperTransform @@ -579,17 +582,33 @@ class ProcessWrapperTransform : public WrapperTransform
579 if (transform) { 582 if (transform) {
580 QDataStream out(&serialized, QFile::WriteOnly); 583 QDataStream out(&serialized, QFile::WriteOnly);
581 transform->serialize(out); 584 transform->serialize(out);
  585 + tcount = Globals->parallelism;
  586 + counter.acquire(counter.available());
  587 + counter.release(this->concurrentCount);
582 } 588 }
583 } 589 }
584 590
585 - QByteArray serialized; 591 + static QSemaphore counter;
  592 + mutable int tcount;
  593 + mutable QByteArray serialized;
586 void transmitTForm(CommunicationManager *localComm) const 594 void transmitTForm(CommunicationManager *localComm) const
587 { 595 {
588 if (serialized.isEmpty() ) 596 if (serialized.isEmpty() )
589 qFatal("Trying to transmit empty transform!"); 597 qFatal("Trying to transmit empty transform!");
590 598
  599 + counter.acquire(1);
  600 + static QMutex transmission;
  601 + QMutexLocker lock(&transmission);
  602 + tcount--;
  603 +
591 localComm->writeArray = serialized; 604 localComm->writeArray = serialized;
  605 + if (tcount == 0)
  606 + serialized.clear();
  607 + lock.unlock();
  608 +
592 emit localComm->pulseSendSerialized(); 609 emit localComm->pulseSendSerialized();
  610 + localComm->getSignal();
  611 + counter.release(1);
593 } 612 }
594 613
595 void activateProcess(ProcessData *data) const 614 void activateProcess(ProcessData *data) const
@@ -633,6 +652,7 @@ public: @@ -633,6 +652,7 @@ public:
633 bool processActive; 652 bool processActive;
634 ProcessWrapperTransform() : WrapperTransform(false) { processActive = false; } 653 ProcessWrapperTransform() : WrapperTransform(false) { processActive = false; }
635 }; 654 };
  655 +QSemaphore ProcessWrapperTransform::counter;
636 656
637 BR_REGISTER(Transform, ProcessWrapperTransform) 657 BR_REGISTER(Transform, ProcessWrapperTransform)
638 658
openbr/plugins/quality.cpp
@@ -77,6 +77,12 @@ class ImpostorUniquenessMeasureTransform : public Transform @@ -77,6 +77,12 @@ class ImpostorUniquenessMeasureTransform : public Transform
77 77
78 BR_REGISTER(Transform, ImpostorUniquenessMeasureTransform) 78 BR_REGISTER(Transform, ImpostorUniquenessMeasureTransform)
79 79
  80 +
  81 +float KDEPointer(const QList<float> *scores, double x, double h)
  82 +{
  83 + return Common::KernelDensityEstimation(*scores, x, h);
  84 +}
  85 +
80 /* Kernel Density Estimator */ 86 /* Kernel Density Estimator */
81 struct KDE 87 struct KDE
82 { 88 {
@@ -85,20 +91,35 @@ struct KDE @@ -85,20 +91,35 @@ struct KDE
85 QList<float> bins; 91 QList<float> bins;
86 92
87 KDE() : min(0), max(1), mean(0), stddev(1) {} 93 KDE() : min(0), max(1), mean(0), stddev(1) {}
88 - KDE(const QList<float> &scores) 94 +
  95 + KDE(const QList<float> &scores, bool trainKDE)
89 { 96 {
90 Common::MinMax(scores, &min, &max); 97 Common::MinMax(scores, &min, &max);
91 Common::MeanStdDev(scores, &mean, &stddev); 98 Common::MeanStdDev(scores, &mean, &stddev);
  99 +
  100 + if (!trainKDE)
  101 + return;
  102 +
92 double h = Common::KernelDensityBandwidth(scores); 103 double h = Common::KernelDensityBandwidth(scores);
93 const int size = 255; 104 const int size = 255;
94 bins.reserve(size); 105 bins.reserve(size);
95 - for (int i=0; i<size; i++)  
96 - bins.append(Common::KernelDensityEstimation(scores, min + (max-min)*i/(size-1), h)); 106 +
  107 + QFutureSynchronizer<float> futures;
  108 +
  109 + for (int i=0; i < size; i++)
  110 + futures.addFuture(QtConcurrent::run(KDEPointer, &scores, min + (max-min)*i/(size-1), h));
  111 + futures.waitForFinished();
  112 +
  113 + foreach(const QFuture<float> & future, futures.futures())
  114 + bins.append(future.result());
97 } 115 }
98 116
99 float operator()(float score, bool gaussian = true) const 117 float operator()(float score, bool gaussian = true) const
100 { 118 {
101 if (gaussian) return 1/(stddev*sqrt(2*CV_PI))*exp(-0.5*pow((score-mean)/stddev, 2)); 119 if (gaussian) return 1/(stddev*sqrt(2*CV_PI))*exp(-0.5*pow((score-mean)/stddev, 2));
  120 + if (bins.empty())
  121 + return -std::numeric_limits<float>::max();
  122 +
102 if (score <= min) return bins.first(); 123 if (score <= min) return bins.first();
103 if (score >= max) return bins.last(); 124 if (score >= max) return bins.last();
104 const float x = (score-min)/(max-min)*bins.size(); 125 const float x = (score-min)/(max-min)*bins.size();
@@ -123,8 +144,8 @@ struct MP @@ -123,8 +144,8 @@ struct MP
123 { 144 {
124 KDE genuine, impostor; 145 KDE genuine, impostor;
125 MP() {} 146 MP() {}
126 - MP(const QList<float> &genuineScores, const QList<float> &impostorScores)  
127 - : genuine(genuineScores), impostor(impostorScores) {} 147 + MP(const QList<float> &genuineScores, const QList<float> &impostorScores, bool trainKDE)
  148 + : genuine(genuineScores, trainKDE), impostor(impostorScores, trainKDE) {}
128 float operator()(float score, bool gaussian = true) const 149 float operator()(float score, bool gaussian = true) const
129 { 150 {
130 const float g = genuine(score, gaussian); 151 const float g = genuine(score, gaussian);
@@ -165,7 +186,7 @@ class MatchProbabilityDistance : public Distance @@ -165,7 +186,7 @@ class MatchProbabilityDistance : public Distance
165 const QList<int> labels = src.indexProperty(inputVariable); 186 const QList<int> labels = src.indexProperty(inputVariable);
166 QScopedPointer<MatrixOutput> matrixOutput(MatrixOutput::make(FileList(src.size()), FileList(src.size()))); 187 QScopedPointer<MatrixOutput> matrixOutput(MatrixOutput::make(FileList(src.size()), FileList(src.size())));
167 distance->compare(src, src, matrixOutput.data()); 188 distance->compare(src, src, matrixOutput.data());
168 - 189 +
169 QList<float> genuineScores, impostorScores; 190 QList<float> genuineScores, impostorScores;
170 genuineScores.reserve(labels.size()); 191 genuineScores.reserve(labels.size());
171 impostorScores.reserve(labels.size()*labels.size()); 192 impostorScores.reserve(labels.size()*labels.size());
@@ -178,8 +199,8 @@ class MatchProbabilityDistance : public Distance @@ -178,8 +199,8 @@ class MatchProbabilityDistance : public Distance
178 else impostorScores.append(score); 199 else impostorScores.append(score);
179 } 200 }
180 } 201 }
181 -  
182 - mp = MP(genuineScores, impostorScores); 202 +
  203 + mp = MP(genuineScores, impostorScores, !gaussian);
183 } 204 }
184 205
185 float compare(const Template &target, const Template &query) const 206 float compare(const Template &target, const Template &query) const
openbr/plugins/stasm4.cpp
@@ -180,7 +180,7 @@ private: @@ -180,7 +180,7 @@ private:
180 void project(const Template &src, Template &dst) const 180 void project(const Template &src, Template &dst) const
181 { 181 {
182 QList<float> paramList = src.file.getList<float>("affineParameters"); 182 QList<float> paramList = src.file.getList<float>("affineParameters");
183 - Eigen::MatrixXf points = pointsToMatrix(src.file.points(), true); 183 + Eigen::MatrixXf points = EigenUtils::pointsToMatrix(src.file.points(), true);
184 Eigen::MatrixXf affine = Eigen::MatrixXf::Zero(3, 3); 184 Eigen::MatrixXf affine = Eigen::MatrixXf::Zero(3, 3);
185 for (int i = 0, cnt = 0; i < 2; i++) 185 for (int i = 0, cnt = 0; i < 2; i++)
186 for (int j = 0; j < 3; j++, cnt++) 186 for (int j = 0; j < 3; j++, cnt++)
@@ -192,7 +192,7 @@ private: @@ -192,7 +192,7 @@ private:
192 points = affineInv * pointsT; 192 points = affineInv * pointsT;
193 dst = src; 193 dst = src;
194 dst.file.clearPoints(); 194 dst.file.clearPoints();
195 - dst.file.setPoints(matrixToPoints(points.transpose())); 195 + dst.file.setPoints(EigenUtils::matrixToPoints(points.transpose()));
196 } 196 }
197 }; 197 };
198 198
1 -Subproject commit 79938fe401faafead086b4711dd0b8f898a7a21e 1 +Subproject commit 85842e6da7738e317b9d40d5a395b92cd7b996e1