Commit 3808a6fe13088d7caf3584a0517f4f9dc4cb5f75

Authored by Josh Klontz
2 parents 4070e564 24aec81e

Merge branch 'master' into attributes

LICENSE.txt
1   -Copyright 2012-2014 The MITRE Corporation
  1 +Copyright 2012 The MITRE Corporation
2 2  
3 3 Licensed under the Apache License, Version 2.0 (the "License");
4 4 you may not use this file except in compliance with the License.
... ...
app/br/br.cpp
... ... @@ -138,8 +138,8 @@ public:
138 138 check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalClassification'.");
139 139 br_eval_classification(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : "");
140 140 } else if (!strcmp(fun, "evalClustering")) {
141   - check(parc == 2, "Incorrect parameter count for 'evalClustering'.");
142   - br_eval_clustering(parv[0], parv[1]);
  141 + check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalClustering'.");
  142 + br_eval_clustering(parv[0], parv[1], parc == 3 ? parv[2] : "");
143 143 } else if (!strcmp(fun, "evalDetection")) {
144 144 check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalDetection'.");
145 145 br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : "");
... ...
openbr/core/cluster.cpp
... ... @@ -100,7 +100,9 @@ Neighborhood getNeighborhood(const QStringList &amp;simmats)
100 100 int currentRows = -1;
101 101 int columnOffset = 0;
102 102 for (int j=0; j<numGalleries; j++) {
103   - cv::Mat m = BEE::readMat(simmats[i*numGalleries+j]);
  103 + QScopedPointer<br::Format> format(br::Factory<br::Format>::make(simmats[i*numGalleries+j]));
  104 + br::Template t = format->read();
  105 + cv::Mat m = t.m();
104 106 if (j==0) {
105 107 currentRows = m.rows;
106 108 allNeighbors.resize(currentRows);
... ... @@ -115,8 +117,9 @@ Neighborhood getNeighborhood(const QStringList &amp;simmats)
115 117 float val = m.at<float>(k,l);
116 118 if ((i==j) && (k==l)) continue; // Skips self-similarity scores
117 119  
118   - if ((val != -std::numeric_limits<float>::infinity()) &&
119   - (val != std::numeric_limits<float>::infinity())) {
  120 + if (val != -std::numeric_limits<float>::max()
  121 + && val != -std::numeric_limits<float>::infinity()
  122 + && val != std::numeric_limits<float>::infinity()) {
120 123 globalMax = std::max(globalMax, val);
121 124 globalMin = std::min(globalMin, val);
122 125 }
... ... @@ -157,7 +160,7 @@ Neighborhood getNeighborhood(const QStringList &amp;simmats)
157 160 // Zhu et al. "A Rank-Order Distance based Clustering Algorithm for Face Tagging", CVPR 2011
158 161 br::Clusters br::ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv)
159 162 {
160   - qDebug("Clustering %d simmat(s)", simmats.size());
  163 + qDebug("Clustering %d simmat(s), aggressiveness %f", simmats.size(), aggressiveness);
161 164  
162 165 // Read in gallery parts, keeping top neighbors of each template
163 166 Neighborhood neighborhood = getNeighborhood(simmats);
... ... @@ -275,13 +278,14 @@ float jaccardIndex(const QVector&lt;int&gt; &amp;indicesA, const QVector&lt;int&gt; &amp;indicesB)
275 278  
276 279 // Evaluates clustering algorithms based on metrics described in
277 280 // Santo Fortunato "Community detection in graphs", Physics Reports 486 (2010)
278   -void br::EvalClustering(const QString &csv, const QString &input)
  281 +void br::EvalClustering(const QString &csv, const QString &input, QString truth_property)
279 282 {
  283 + if (truth_property.isEmpty())
  284 + truth_property = "Label";
280 285 qDebug("Evaluating %s against %s", qPrintable(csv), qPrintable(input));
281 286  
282   - // We assume clustering algorithms store assigned cluster labels as integers (since the clusters are
283   - // not named). Direct use of ClusterID is not general -cao
284   - QList<int> labels = File::get<int>(TemplateList::fromGallery(input), "ClusterID");
  287 + TemplateList tList = TemplateList::fromGallery(input);
  288 + QList<int> labels = tList.indexProperty(truth_property);
285 289  
286 290 QHash<int, int> labelToIndex;
287 291 int nClusters = 0;
... ...
openbr/core/cluster.h
... ... @@ -28,7 +28,7 @@ namespace br
28 28 typedef QVector<Cluster> Clusters;
29 29  
30 30 Clusters ClusterGallery(const QStringList &simmats, float aggressiveness, const QString &csv);
31   - void EvalClustering(const QString &csv, const QString &input);
  31 + void EvalClustering(const QString &csv, const QString &input, QString truth_property);
32 32  
33 33 Clusters ReadClusters(const QString &csv);
34 34 void WriteClusters(const Clusters &clusters, const QString &csv);
... ...
openbr/core/common.cpp
... ... @@ -76,3 +76,11 @@ QList&lt;float&gt; Common::linspace(float start, float stop, int n) {
76 76 spaced.append(stop);
77 77 return spaced;
78 78 }
  79 +
  80 +QList<int> Common::ind2sub(int dims, int nPerDim, int idx) {
  81 + QList<int> subIndices;
  82 + for (int j = 0; j < dims; j++) {
  83 + subIndices.append(((int)floor( idx / pow((float)nPerDim, j))) % nPerDim);
  84 + }
  85 + return subIndices;
  86 +}
... ...
openbr/core/common.h
... ... @@ -335,6 +335,10 @@ V&lt;T&gt; Downsample(V&lt;T&gt; vals, int k)
335 335 return newVals;
336 336 }
337 337  
  338 +/*! \brief Converts index into subdimensions.
  339 +*/
  340 +QList<int> ind2sub(int dims, int nPerDim, int idx);
  341 +
338 342 }
339 343  
340 344 #endif // COMMON_COMMON_H
... ...
openbr/core/eigenutils.cpp
... ... @@ -67,3 +67,12 @@ void printEigen(Eigen::MatrixXf X) {
67 67 void printSize(Eigen::MatrixXf X) {
68 68 qDebug() << "Rows=" << X.rows() << "\tCols=" << X.cols();
69 69 }
  70 +
  71 +float eigMean(const Eigen::MatrixXf& x) {
  72 + return x.array().sum() / (x.rows() * x.cols());
  73 +}
  74 +
  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 +}
... ...
openbr/core/eigenutils.h
... ... @@ -67,4 +67,55 @@ inline QDataStream &amp;operator&gt;&gt;(QDataStream &amp;stream, Eigen::Matrix&lt; _Scalar, _Row
67 67 return stream;
68 68 }
69 69  
  70 +/*Compute the mean of the each column (dim == 1) or row (dim == 2)
  71 + of the matrix*/
  72 +template<typename T>
  73 +Eigen::MatrixBase<T> eigMean(const Eigen::MatrixBase<T>& x,int dim)
  74 +{
  75 + if (dim == 1) {
  76 + Eigen::MatrixBase<T> y(1,x.cols());
  77 + for (int i = 0; i < x.cols(); i++)
  78 + y(i) = x.col(i).sum() / x.rows();
  79 + return y;
  80 + } else if (dim == 2) {
  81 + Eigen::MatrixBase<T> y(x.rows(),1);
  82 + for (int i = 0; i < x.rows(); i++)
  83 + y(i) = x.row(i).sum() / x.cols();
  84 + return y;
  85 + }
  86 + qFatal("A matrix can only have two dimensions");
  87 +}
  88 +
  89 +/*Compute the element-wise mean*/
  90 +float eigMean(const Eigen::MatrixXf& x);
  91 +/*Compute the element-wise mean*/
  92 +float eigStd(const Eigen::MatrixXf& x);
  93 +
  94 +/*Compute the std dev of the each column (dim == 1) or row (dim == 2)
  95 + of the matrix*/
  96 +template<typename T>
  97 +Eigen::MatrixBase<T> eigStd(const Eigen::MatrixBase<T>& x,int dim)
  98 +{
  99 + Eigen::MatrixBase<T> mean = eigMean(x, dim);
  100 + if (dim == 1) {
  101 + Eigen::MatrixBase<T> y(1,x.cols());
  102 + for (int i = 0; i < x.cols(); i++) {
  103 + T value = 0;
  104 + for (int j = 0; j < x.rows(); j++)
  105 + value += pow(y(j, i) - mean(i), 2);
  106 + y(i) = sqrt(value / (x.rows() - 1));
  107 + }
  108 + return y;
  109 + } else if (dim == 2) {
  110 + Eigen::MatrixBase<T> y(x.rows(),1);
  111 + for (int i = 0; i < x.rows(); i++) {
  112 + T value = 0;
  113 + for (int j = 0; j < x.cols(); j++)
  114 + value += pow(y(i, j) - mean(j), 2);
  115 + y(i) = sqrt(value / (x.cols() - 1));
  116 + }
  117 + return y;
  118 + }
  119 + qFatal("A matrix can only have two dimensions");
  120 +}
70 121 #endif // EIGENUTILS_H
... ...
openbr/core/eval.cpp
... ... @@ -572,6 +572,7 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle
572 572 const QStringList predictedNames = File::get<QString>(predicted, "name");
573 573 const QStringList truthNames = File::get<QString>(truth, "name");
574 574  
  575 + int skipped = 0;
575 576 QList< QList<float> > pointErrors;
576 577 for (int i=0; i<predicted.size(); i++) {
577 578 const QString &predictedName = predictedNames[i];
... ... @@ -579,7 +580,10 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle
579 580 if (truthIndex == -1) qFatal("Could not identify ground truth for file: %s", qPrintable(predictedName));
580 581 const QList<QPointF> predictedPoints = predicted[i].file.points();
581 582 const QList<QPointF> truthPoints = truth[truthIndex].file.points();
582   - if (predictedPoints.size() != truthPoints.size()) qFatal("Points size mismatch for file: %s", qPrintable(predictedName));
  583 + if (predictedPoints.size() != truthPoints.size()) {
  584 + skipped++;
  585 + continue;
  586 + }
583 587 while (pointErrors.size() < predictedPoints.size())
584 588 pointErrors.append(QList<float>());
585 589 if (normalizationIndexA >= truthPoints.size()) qFatal("Normalization index A is out of range.");
... ... @@ -588,6 +592,7 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle
588 592 for (int j=0; j<predictedPoints.size(); j++)
589 593 pointErrors[j].append(QtUtils::euclideanLength(predictedPoints[j] - truthPoints[j])/normalizedLength);
590 594 }
  595 + qDebug() << "Skipped " << skipped << " files do to point size mismatch.";
591 596  
592 597 QList<float> averagePointErrors; averagePointErrors.reserve(pointErrors.size());
593 598 for (int i=0; i<pointErrors.size(); i++) {
... ... @@ -605,6 +610,8 @@ float EvalLandmarking(const QString &amp;predictedGallery, const QString &amp;truthGalle
605 610 lines.append(QString("Box,%1,%2").arg(QString::number(i), QString::number(pointError[j*(pointError.size()-1)/(keep-1)])));
606 611 }
607 612  
  613 + lines.append(QString("AvgError,0,%1").arg(averagePointError));
  614 +
608 615 QtUtils::writeFile(csv, lines);
609 616 qDebug("Average Error: %.3f", averagePointError);
610 617 return averagePointError;
... ...
openbr/core/qtutils.cpp
... ... @@ -24,6 +24,7 @@
24 24 #include <QProcess>
25 25 #include <QProcessEnvironment>
26 26 #include <QRegExp>
  27 +#include <QRegularExpression>
27 28 #include <QStack>
28 29 #include <QUrl>
29 30 #include <QMap>
... ... @@ -81,7 +82,7 @@ void readFile(const QString &amp;file, QStringList &amp;lines)
81 82 {
82 83 QByteArray data;
83 84 readFile(file, data);
84   - lines = QString(data).split('\n', QString::SkipEmptyParts);
  85 + lines = QString(data).split(QRegularExpression("[\n|\r\n|\r]"), QString::SkipEmptyParts);
85 86 for (int i=0; i<lines.size(); i++)
86 87 lines[i] = lines[i].simplified();
87 88 }
... ...
openbr/openbr.cpp
... ... @@ -109,9 +109,9 @@ void br_eval_classification(const char *predicted_gallery, const char *truth_gal
109 109 EvalClassification(predicted_gallery, truth_gallery, predicted_property, truth_property);
110 110 }
111 111  
112   -void br_eval_clustering(const char *csv, const char *gallery)
  112 +void br_eval_clustering(const char *csv, const char *gallery, const char * truth_property)
113 113 {
114   - EvalClustering(csv, gallery);
  114 + EvalClustering(csv, gallery, truth_property);
115 115 }
116 116  
117 117 float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv)
... ... @@ -435,7 +435,14 @@ br_template_list br_load_from_gallery(br_gallery gallery)
435 435 return (br_template_list)tl;
436 436 }
437 437  
438   -void br_add_to_gallery(br_gallery gallery, br_template_list tl)
  438 +void br_add_template_to_gallery(br_gallery gallery, br_template tmpl)
  439 +{
  440 + Gallery *gal = reinterpret_cast<Gallery*>(gallery);
  441 + Template *t = reinterpret_cast<Template*>(tmpl);
  442 + gal->write(*t);
  443 +}
  444 +
  445 +void br_add_template_list_to_gallery(br_gallery gallery, br_template_list tl)
439 446 {
440 447 Gallery *gal = reinterpret_cast<Gallery*>(gallery);
441 448 TemplateList *realTL = reinterpret_cast<TemplateList*>(tl);
... ...
openbr/openbr.h
... ... @@ -167,9 +167,10 @@ BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char
167 167 * \brief Evaluates and prints clustering accuracy to the terminal.
168 168 * \param csv The cluster results file.
169 169 * \param gallery The br::Gallery used to generate the \ref simmat that was clustered.
  170 + * \param truth_property (Optional) which metadata key to use from <i>gallery</i/>, defaults to Label
170 171 * \see br_cluster
171 172 */
172   -BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery);
  173 +BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery, const char * truth_property);
173 174  
174 175 /*!
175 176 * \brief Evaluates and prints detection accuracy to terminal.
... ... @@ -562,9 +563,13 @@ BR_EXPORT br_gallery br_make_gallery(const char *gallery);
562 563 */
563 564 BR_EXPORT br_template_list br_load_from_gallery(br_gallery gallery);
564 565 /*!
  566 + * \brief Write a br::Template to the br::Gallery on disk.
  567 + */
  568 +BR_EXPORT void br_add_template_to_gallery(br_gallery gallery, br_template tmpl);
  569 +/*!
565 570 * \brief Write a br::TemplateList to the br::Gallery on disk.
566 571 */
567   -BR_EXPORT void br_add_to_gallery(br_gallery gallery, br_template_list tl);
  572 +BR_EXPORT void br_add_template_list_to_gallery(br_gallery gallery, br_template_list tl);
568 573 /*!
569 574 * \brief Close the br::Gallery.
570 575 */
... ...
openbr/openbr_plugin.cpp
... ... @@ -137,6 +137,8 @@ QVariant File::parse(const QString &amp;value)
137 137 if (ok) return point;
138 138 const QRectF rect = QtUtils::toRect(value, &ok);
139 139 if (ok) return rect;
  140 + const int i = value.toInt(&ok);
  141 + if (ok) return i;
140 142 const float f = value.toFloat(&ok);
141 143 if (ok) return f;
142 144 return value;
... ...
openbr/plugins/algorithms.cpp
... ... @@ -44,7 +44,7 @@ class AlgorithmsInitializer : public Initializer
44 44 Globals->abbreviations.insert("AgeEstimation", "AgeRegression");
45 45 Globals->abbreviations.insert("FaceRecognition2", "{PP5Register+Affine(128,128,0.25,0.35)+Cvt(Gray)}+(Gradient+Bin(0,360,9,true))/(Blur(1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2,true)+Bin(0,10,10,true))+Merge+Integral+RecursiveIntegralSampler(4,2,8,LDA(.98)+Normalize(L1))+Cat+PCA(768)+Normalize(L1)+Quantize:UCharL1");
46 46 Globals->abbreviations.insert("CropFace", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.25,0.35)");
47   - Globals->abbreviations.insert("4SF", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+(Grid(10,10)+SIFTDescriptor(12)+ByRow)/(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))+PCA(0.95)+Normalize(L2)+Dup(12)+RndSubspace(0.05,1)+LDA(0.98)+Cat+PCA(0.95)+Normalize(L1)+Quantize:NegativeLogPlusOne(ByteL1)");
  47 + Globals->abbreviations.insert("4SF", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.33,0.45)+(Grid(10,10)+SIFTDescriptor(12)+ByRow)/(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))+PCA(0.95)+Cat+Normalize(L2)+Dup(12)+RndSubspace(0.05,1)+LDA(0.98)+Cat+PCA(0.95)+Normalize(L1)+Quantize:NegativeLogPlusOne(ByteL1)");
48 48  
49 49 // Video
50 50 Globals->abbreviations.insert("DisplayVideo", "Stream(FPSLimit(30)+Show(false,[FrameNumber])+Discard)");
... ... @@ -64,6 +64,7 @@ class AlgorithmsInitializer : public Initializer
64 64 Globals->abbreviations.insert("SmallSIFT", "Open+LimitSize(512)+KeyPointDetector(SIFT)+KeyPointDescriptor(SIFT):KeyPointMatcher(BruteForce)");
65 65 Globals->abbreviations.insert("SmallSURF", "Open+LimitSize(512)+KeyPointDetector(SURF)+KeyPointDescriptor(SURF):KeyPointMatcher(BruteForce)");
66 66 Globals->abbreviations.insert("ColorHist", "Open+LimitSize(512)+Expand+EnsureChannels(3)+SplitChannels+Hist(256,0,8)+Cat+Normalize(L1):L2");
  67 + Globals->abbreviations.insert("ImageSimilarity", "Open+EnsureChannels(3)+Resize(256,256)+SplitChannels+RectRegions(64,64,64,64)+Hist(256,0,8)+Cat:NegativeLogPlusOne(L2)");
67 68 Globals->abbreviations.insert("ImageClassification", "Open+CropSquare+LimitSize(256)+Cvt(Gray)+Gradient+Bin(0,360,9,true)+Merge+Integral+RecursiveIntegralSampler(4,2,8,Singleton(KMeans(256)))+Cat+CvtFloat+Hist(256)+KNN(5,Dist(L1),false,5)+Rename(KNN,Subject)");
68 69 Globals->abbreviations.insert("TanTriggs", "Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)");
69 70  
... ...
openbr/plugins/eigen3.cpp
... ... @@ -15,15 +15,35 @@
15 15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16 16  
17 17 #include <Eigen/Dense>
  18 +
18 19 #include "openbr_internal.h"
19 20  
20 21 #include "openbr/core/common.h"
21 22 #include "openbr/core/eigenutils.h"
  23 +#include "openbr/core/opencvutils.h"
22 24  
23 25 namespace br
24 26 {
25 27  
26 28 /*!
  29 + * \ingroup initializers
  30 + * \brief Initialize Eigen
  31 + * http://eigen.tuxfamily.org/dox/TopicMultiThreading.html
  32 + * \author Scott Klum \cite sklum
  33 + */
  34 +class EigenInitializer : public Initializer
  35 +{
  36 + Q_OBJECT
  37 +
  38 + void initialize() const
  39 + {
  40 + Eigen::initParallel();
  41 + }
  42 +};
  43 +
  44 +BR_REGISTER(Initializer, EigenInitializer)
  45 +
  46 +/*!
27 47 * \ingroup transforms
28 48 * \brief Projects input into learned Principal Component Analysis subspace.
29 49 * \author Brendan Klare \cite bklare
... ... @@ -296,6 +316,8 @@ BR_REGISTER(Transform, DFFSTransform)
296 316 */
297 317 class LDATransform : public Transform
298 318 {
  319 + friend class SparseLDATransform;
  320 +
299 321 Q_OBJECT
300 322 Q_PROPERTY(float pcaKeep READ get_pcaKeep WRITE set_pcaKeep RESET reset_pcaKeep STORED false)
301 323 Q_PROPERTY(bool pcaWhiten READ get_pcaWhiten WRITE set_pcaWhiten RESET reset_pcaWhiten STORED false)
... ... @@ -499,21 +521,30 @@ class LDATransform : public Transform
499 521  
500 522 // Do projection
501 523 outMap = projection.transpose() * (inMap - mean);
502   -
503 524 if (normalize && isBinary)
504 525 dst.m().at<float>(0,0) = dst.m().at<float>(0,0) / stdDev;
505 526 }
506 527  
507 528 void store(QDataStream &stream) const
508 529 {
509   - stream << pcaKeep << directLDA << directDrop << dimsOut << mean << projection;
  530 + stream << pcaKeep;
  531 + stream << directLDA;
  532 + stream << directDrop;
  533 + stream << dimsOut;
  534 + stream << mean;
  535 + stream << projection;
510 536 if (normalize && isBinary)
511 537 stream << stdDev;
512 538 }
513 539  
514 540 void load(QDataStream &stream)
515 541 {
516   - stream >> pcaKeep >> directLDA >> directDrop >> dimsOut >> mean >> projection;
  542 + stream >> pcaKeep;
  543 + stream >> directLDA;
  544 + stream >> directDrop;
  545 + stream >> dimsOut;
  546 + stream >> mean;
  547 + stream >> projection;
517 548 if (normalize && isBinary)
518 549 stream >> stdDev;
519 550 }
... ... @@ -522,6 +553,104 @@ class LDATransform : public Transform
522 553 BR_REGISTER(Transform, LDATransform)
523 554  
524 555 /*!
  556 + * \ingroup transforms
  557 + * \brief Projects input into learned Linear Discriminant Analysis subspace
  558 + * learned on a sparse subset of features with the highest weight
  559 + * in the original LDA algorithm.
  560 + * \author Brendan Klare \cite bklare
  561 + */
  562 +class SparseLDATransform : public Transform
  563 +{
  564 + Q_OBJECT
  565 + Q_PROPERTY(float varThreshold READ get_varThreshold WRITE set_varThreshold RESET reset_varThreshold STORED false)
  566 + Q_PROPERTY(float pcaKeep READ get_pcaKeep WRITE set_pcaKeep RESET reset_pcaKeep STORED false)
  567 + Q_PROPERTY(bool normalize READ get_normalize WRITE set_normalize RESET reset_normalize STORED false)
  568 + BR_PROPERTY(float, varThreshold, 1.5)
  569 + BR_PROPERTY(float, pcaKeep, 0.98)
  570 + BR_PROPERTY(bool, normalize, true)
  571 +
  572 + LDATransform ldaSparse;
  573 + int dimsOut;
  574 + QList<int> selections;
  575 +
  576 + Eigen::VectorXf mean;
  577 +
  578 + void init()
  579 + {
  580 + ldaSparse.init();
  581 + ldaSparse.pcaKeep = pcaKeep;
  582 + ldaSparse.inputVariable = "Label";
  583 + ldaSparse.isBinary = true;
  584 + ldaSparse.normalize = true;
  585 + }
  586 +
  587 + void train(const TemplateList &_trainingSet)
  588 + {
  589 +
  590 + LDATransform ldaOrig;
  591 + ldaOrig.init();
  592 + ldaOrig.inputVariable = "Label";
  593 + ldaOrig.pcaKeep = pcaKeep;
  594 + ldaOrig.isBinary = true;
  595 + ldaOrig.normalize = true;
  596 +
  597 + ldaOrig.train(_trainingSet);
  598 +
  599 + //Only works on binary class problems for now
  600 + assert(ldaOrig.projection.cols() == 1);
  601 + float ldaStd = eigStd(ldaOrig.projection);
  602 + for (int i = 0; i < ldaOrig.projection.rows(); i++)
  603 + if (abs(ldaOrig.projection(i)) > varThreshold * ldaStd)
  604 + selections.append(i);
  605 +
  606 + TemplateList newSet;
  607 + for (int i = 0; i < _trainingSet.size(); i++) {
  608 + cv::Mat x(_trainingSet[i]);
  609 + cv::Mat y = cv::Mat(selections.size(), 1, CV_32FC1);
  610 + int idx = 0;
  611 + int cnt = 0;
  612 + for (int j = 0; j < x.rows; j++)
  613 + for (int k = 0; k < x.cols; k++, cnt++)
  614 + if (selections.contains(cnt))
  615 + y.at<float>(idx++,0) = x.at<float>(j, k);
  616 + newSet.append(Template(_trainingSet[i].file, y));
  617 + }
  618 + ldaSparse.train(newSet);
  619 + dimsOut = ldaSparse.dimsOut;
  620 + }
  621 +
  622 + void project(const Template &src, Template &dst) const
  623 + {
  624 + Eigen::Map<Eigen::MatrixXf> inMap((float*)src.m().ptr<float>(), src.m().rows*src.m().cols, 1);
  625 + Eigen::Map<Eigen::MatrixXf> outMap(dst.m().ptr<float>(), dimsOut, 1);
  626 +
  627 + int d = selections.size();
  628 + cv::Mat inSelect(d,1,CV_32F);
  629 + for (int i = 0; i < d; i++)
  630 + inSelect.at<float>(i) = src.m().at<float>(selections[i]);
  631 + ldaSparse.project(Template(src.file, inSelect), dst);
  632 + }
  633 +
  634 + void store(QDataStream &stream) const
  635 + {
  636 + stream << pcaKeep;
  637 + stream << ldaSparse;
  638 + stream << dimsOut;
  639 + stream << selections;
  640 + }
  641 +
  642 + void load(QDataStream &stream)
  643 + {
  644 + stream >> pcaKeep;
  645 + stream >> ldaSparse;
  646 + stream >> dimsOut;
  647 + stream >> selections;
  648 + }
  649 +};
  650 +
  651 +BR_REGISTER(Transform, SparseLDATransform)
  652 +
  653 +/*!
525 654 * \ingroup distances
526 655 * \brief L1 distance computed using eigen.
527 656 * \author Josh Klontz \cite jklontz
... ...
openbr/plugins/format.cpp
... ... @@ -151,7 +151,7 @@ class csvFormat : public Format
151 151 {
152 152 QFile f(file.name);
153 153 f.open(QFile::ReadOnly);
154   - QStringList lines(QString(f.readAll()).split('\n'));
  154 + QStringList lines(QString(f.readAll()).split(QRegularExpression("[\n|\r\n|\r]"), QString::SkipEmptyParts));
155 155 f.close();
156 156  
157 157 bool isUChar = true;
... ...
openbr/plugins/gallery.cpp
... ... @@ -506,29 +506,78 @@ BR_REGISTER(Gallery, csvGallery)
506 506 * \brief Treats each line as a file.
507 507 * \author Josh Klontz \cite jklontz
508 508 *
509   - * The entire line is treated as the file path.
  509 + * The entire line is treated as the file path. An optional label may be specified using a space ' ' separator:
510 510 *
  511 +\verbatim
  512 +<FILE>
  513 +<FILE>
  514 +...
  515 +<FILE>
  516 +\endverbatim
  517 + * or
  518 +\verbatim
  519 +<FILE> <LABEL>
  520 +<FILE> <LABEL>
  521 +...
  522 +<FILE> <LABEL>
  523 +\endverbatim
511 524 * \see csvGallery
512 525 */
513 526 class txtGallery : public Gallery
514 527 {
515 528 Q_OBJECT
516   - Q_PROPERTY(QString metadataKey READ get_metadataKey WRITE set_metadataKey RESET reset_metadataKey STORED false)
517   - BR_PROPERTY(QString, metadataKey, "")
  529 + Q_PROPERTY(QString label READ get_label WRITE set_label RESET reset_label STORED false)
  530 + BR_PROPERTY(QString, label, "")
518 531  
519 532 QStringList lines;
520 533  
521 534 ~txtGallery()
522 535 {
523   - if (!lines.isEmpty()) QtUtils::writeFile(file.name, lines);
  536 + if (!lines.isEmpty())
  537 + QtUtils::writeFile(file.name, lines);
524 538 }
525 539  
526 540 TemplateList readBlock(bool *done)
527 541 {
528   - *done = true;
529 542 TemplateList templates;
530   - if (!file.exists()) return templates;
  543 + foreach (const QString &line, QtUtils::readLines(file)) {
  544 + int splitIndex = line.lastIndexOf(' ');
  545 + if (splitIndex == -1) templates.append(File(line));
  546 + else templates.append(File(line.mid(0, splitIndex), line.mid(splitIndex+1)));
  547 + }
  548 + *done = true;
  549 + return templates;
  550 + }
  551 +
  552 + void write(const Template &t)
  553 + {
  554 + QString line = t.file.name;
  555 + if (!label.isEmpty())
  556 + line += " " + t.file.get<QString>(label);
  557 + lines.append(line);
  558 + }
  559 +};
531 560  
  561 +BR_REGISTER(Gallery, txtGallery)
  562 +/*!
  563 + * \ingroup galleries
  564 + * \brief Treats each line as a call to File::flat()
  565 + * \author Josh Klontz \cite jklontz
  566 + */
  567 +class flatGallery : public Gallery
  568 +{
  569 + Q_OBJECT
  570 + QStringList lines;
  571 +
  572 + ~flatGallery()
  573 + {
  574 + if (!lines.isEmpty())
  575 + QtUtils::writeFile(file.name, lines);
  576 + }
  577 +
  578 + TemplateList readBlock(bool *done)
  579 + {
  580 + TemplateList templates;
532 581 foreach (const QString &line, QtUtils::readLines(file))
533 582 templates.append(File(line));
534 583 *done = true;
... ... @@ -537,11 +586,11 @@ class txtGallery : public Gallery
537 586  
538 587 void write(const Template &t)
539 588 {
540   - lines.append(metadataKey.isEmpty() ? t.file.flat() : t.file.get<QString>(metadataKey));
  589 + lines.append(t.file.flat());
541 590 }
542 591 };
543 592  
544   -BR_REGISTER(Gallery, txtGallery)
  593 +BR_REGISTER(Gallery, flatGallery)
545 594  
546 595 /*!
547 596 * \ingroup galleries
... ... @@ -687,7 +736,9 @@ class dbGallery : public Gallery
687 736 if (!subset.isEmpty()) {
688 737 const QStringList &words = subset.split(":");
689 738 QtUtils::checkArgsSize("Input", words, 2, 4);
690   - seed = QtUtils::toInt(words[0]);
  739 + if (words[0] == "train") seed = 0;
  740 + else if (words[0] == "test" ) seed = 1;
  741 + else seed = QtUtils::toInt(words[0]);
691 742 if (words[1].startsWith('{') && words[1].endsWith('}')) {
692 743 foreach (const QString &regexp, words[1].mid(1, words[1].size()-2).split(","))
693 744 metadataFields.append(QRegExp(regexp));
... ... @@ -813,16 +864,10 @@ class googleGallery : public Gallery
813 864 return templates;
814 865 }
815 866  
816   - void write(const Template &t)
  867 + void write(const Template &)
817 868 {
818   - (void) t;
819 869 qFatal("Not supported.");
820 870 }
821   -
822   - void init()
823   - {
824   - //
825   - }
826 871 };
827 872  
828 873 BR_REGISTER(Gallery, googleGallery)
... ...
openbr/plugins/gui.cpp
... ... @@ -413,13 +413,13 @@ private:
413 413  
414 414 };
415 415  
416   -class DisplayGUI : public QMainWindow
  416 +class GUIWindow : public QMainWindow
417 417 {
418 418 Q_OBJECT
419 419  
420 420 public:
421 421  
422   - DisplayGUI(QWidget * parent = NULL) : QMainWindow(parent)
  422 + GUIWindow(QWidget * parent = NULL) : QMainWindow(parent)
423 423 {
424 424 centralWidget = new QWidget();
425 425 layout = new QHBoxLayout();
... ... @@ -592,8 +592,10 @@ public:
592 592 template<typename WindowType>
593 593 void initActual()
594 594 {
595   - if (!Globals->useGui)
  595 + if (!Globals->useGui) {
  596 + qWarning("GUI transform %s created without enabling GUI support.\nRun \"br -gui ...\" to enable GUI support from the command line, or set\nGlobals->useGui to true.", this->metaObject()->className());
596 597 return;
  598 + }
597 599  
598 600 if (displayBuffer)
599 601 delete displayBuffer;
... ... @@ -795,7 +797,7 @@ class ElicitTransform : public ShowTransform
795 797  
796 798 Q_OBJECT
797 799  
798   - DisplayGUI *gui;
  800 + GUIWindow *gui;
799 801  
800 802 public:
801 803 ElicitTransform() : ShowTransform()
... ... @@ -835,14 +837,16 @@ public:
835 837  
836 838 void init()
837 839 {
838   - initActual<DisplayGUI>();
  840 + initActual<GUIWindow>();
839 841 }
840 842  
841 843 template<typename GUIType>
842 844 void initActual()
843 845 {
844   - if (!Globals->useGui)
  846 + if (!Globals->useGui) {
  847 + qWarning("GUI transform %s created without enabling GUI support.\nRun \"br -gui ...\" to enable GUI support from the command line, or set\nGlobals->useGui to true.", this->metaObject()->className());
845 848 return;
  849 + }
846 850  
847 851 TimeVaryingTransform::init();
848 852  
... ...
openbr/plugins/independent.cpp
... ... @@ -9,13 +9,14 @@ using namespace cv;
9 9 namespace br
10 10 {
11 11  
12   -static TemplateList Downsample(const TemplateList &templates, int classes, int instances, float fraction, const QString & inputVariable, const QStringList &gallery)
  12 +static TemplateList Downsample(const TemplateList &templates, int classes, int instances, float fraction, const QString & inputVariable, const QStringList &gallery, const QStringList &subjects)
13 13 {
14 14 // Return early when no downsampling is required
15 15 if ((classes == std::numeric_limits<int>::max()) &&
16 16 (instances == std::numeric_limits<int>::max()) &&
17 17 (fraction >= 1) &&
18   - (gallery.isEmpty()))
  18 + (gallery.isEmpty()) &&
  19 + (subjects.isEmpty()))
19 20 return templates;
20 21  
21 22 const bool atLeast = instances < 0;
... ... @@ -67,6 +68,11 @@ static TemplateList Downsample(const TemplateList &amp;templates, int classes, int i
67 68 if (!gallery.contains(downsample[i].file.get<QString>("Gallery")))
68 69 downsample.removeAt(i);
69 70  
  71 + if (!subjects.isEmpty())
  72 + for (int i=downsample.size()-1; i>=0; i--)
  73 + if (subjects.contains(downsample[i].file.get<QString>(inputVariable)))
  74 + downsample.removeAt(i);
  75 +
70 76 return downsample;
71 77 }
72 78  
... ... @@ -79,12 +85,15 @@ class DownsampleTrainingTransform : public Transform
79 85 Q_PROPERTY(float fraction READ get_fraction WRITE set_fraction RESET reset_fraction STORED false)
80 86 Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
81 87 Q_PROPERTY(QStringList gallery READ get_gallery WRITE set_gallery RESET reset_gallery STORED false)
  88 + Q_PROPERTY(QStringList subjects READ get_subjects WRITE set_subjects RESET reset_subjects STORED false)
82 89 BR_PROPERTY(br::Transform*, transform, NULL)
83 90 BR_PROPERTY(int, classes, std::numeric_limits<int>::max())
84 91 BR_PROPERTY(int, instances, std::numeric_limits<int>::max())
85 92 BR_PROPERTY(float, fraction, 1)
86 93 BR_PROPERTY(QString, inputVariable, "Label")
87 94 BR_PROPERTY(QStringList, gallery, QStringList())
  95 + BR_PROPERTY(QStringList, subjects, QStringList())
  96 +
88 97  
89 98 void project(const Template & src, Template & dst) const
90 99 {
... ... @@ -97,7 +106,7 @@ class DownsampleTrainingTransform : public Transform
97 106 if (!transform || !transform->trainable)
98 107 return;
99 108  
100   - TemplateList downsampled = Downsample(data, classes, instances, fraction, inputVariable, gallery);
  109 + TemplateList downsampled = Downsample(data, classes, instances, fraction, inputVariable, gallery, subjects);
101 110  
102 111 transform->train(downsampled);
103 112 }
... ...
openbr/plugins/stream.cpp
... ... @@ -1285,11 +1285,18 @@ public:
1285 1285 qWarning("Attempted to train untrainable transform, nothing will happen.");
1286 1286 return;
1287 1287 }
  1288 + QList<TemplateList> separated;
  1289 + foreach (const TemplateList & list, data) {
  1290 + foreach(const Template & t, list) {
  1291 + separated.append(TemplateList());
  1292 + separated.last().append(t);
  1293 + }
  1294 + }
1288 1295  
1289 1296 for (int i=0; i < transforms.size(); i++) {
1290 1297 // OK we have a trainable transform, we need to get input data for it.
1291 1298 if (transforms[i]->trainable) {
1292   - QList<TemplateList> copy = data;
  1299 + QList<TemplateList> copy = separated;
1293 1300 // Project from the start to the trainable stage.
1294 1301 subProject(copy,i);
1295 1302  
... ... @@ -1308,7 +1315,7 @@ public:
1308 1315 in.append(src);
1309 1316 TemplateList out;
1310 1317 CompositeTransform::project(in,out);
1311   - dst = out.first();
  1318 + if (!out.isEmpty()) dst = out.first();
1312 1319 if (out.size() > 1)
1313 1320 qDebug("Returning first output template only");
1314 1321 }
... ... @@ -1319,7 +1326,7 @@ public:
1319 1326 in.append(src);
1320 1327 TemplateList out;
1321 1328 projectUpdate(in,out);
1322   - dst = out.first();
  1329 + if (!out.isEmpty()) dst = out.first();
1323 1330 if (out.size() > 1)
1324 1331 qDebug("Returning first output template only");
1325 1332 }
... ...
scripts/brpy/__init__.py
... ... @@ -58,7 +58,7 @@ def init_brpy(br_loc=&#39;/usr/local/lib&#39;):
58 58 br.br_eval.argtypes = _string_args(3)
59 59 br.br_eval.restype = c_float
60 60 br.br_eval_classification.argtypes = _string_args(4)
61   - br.br_eval_clustering.argtypes = _string_args(2)
  61 + br.br_eval_clustering.argtypes = _string_args(3)
62 62 br.br_eval_detection.argtypes = _string_args(3)
63 63 br.br_eval_detection.restype = c_float
64 64 br.br_eval_landmarking.argtypes = _string_args(3) + [c_int, c_int]
... ... @@ -143,7 +143,8 @@ def init_brpy(br_loc=&#39;/usr/local/lib&#39;):
143 143 br.br_make_gallery.restype = c_void_p
144 144 br.br_load_from_gallery.argtypes = [c_void_p]
145 145 br.br_load_from_gallery.restype = c_void_p
146   - br.br_add_to_gallery.argtypes = [c_void_p, c_void_p]
  146 + br.br_add_template_to_gallery.argtypes = [c_void_p, c_void_p]
  147 + br.br_add_template_list_to_gallery.argtypes = [c_void_p, c_void_p]
147 148 br.br_close_gallery.argtypes = [c_void_p]
148 149  
149 150 return br
... ...