Commit cfa577b513e8d43b8b73473fdaa7f5be09d6e439

Authored by Scott Klum
2 parents f605bccf c2b1835e

Removed debug statement in core

.gitignore
... ... @@ -20,6 +20,7 @@ scripts/results
20 20  
21 21 ### QtCreator ###
22 22 *CMakeLists.txt.user*
  23 +*.autosave
23 24  
24 25 ### R ###
25 26 *.RData
... ...
CHANGELOG.md
1 1 0.4.0 - ??/??/??
2 2 ================
3 3 * Added -evalDetection and -plotDetection for evaluating and plotting object detection accuracy (#9)
  4 +* Deprecated Transform::backProject
4 5  
5 6 0.3.0 - 5/22/13
6 7 ===============
... ...
README.md
1   -www.openbiometrics.org
  1 +**www.openbiometrics.org**
2 2  
3 3 $ git clone https://github.com/biometrics/openbr.git
4 4 $ cd openbr
5 5 $ git submodule init
6 6 $ git submodule update
  7 +
  8 +To optionally check out a particular [tagged release](https://github.com/biometrics/openbr/releases):
7 9  
8   -[Build Instructions](http://openbiometrics.org/doxygen/latest/installation.html)
  10 + $ git checkout <tag>
  11 + $ git submodule update
  12 +
  13 +**[Build Instructions](http://openbiometrics.org/doxygen/latest/installation.html)**
... ...
app/br/br.cpp
... ... @@ -129,17 +129,20 @@ public:
129 129 check(parc == 3, "Incorrect parameter count for 'convert'.");
130 130 br_convert(parv[0], parv[1], parv[2]);
131 131 } else if (!strcmp(fun, "evalClassification")) {
132   - check(parc == 2, "Incorrect parameter count for 'evalClassification'.");
133   - br_eval_classification(parv[0], parv[1]);
  132 + check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalClassification'.");
  133 + br_eval_classification(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : "");
134 134 } else if (!strcmp(fun, "evalClustering")) {
135 135 check(parc == 2, "Incorrect parameter count for 'evalClustering'.");
136 136 br_eval_clustering(parv[0], parv[1]);
137 137 } else if (!strcmp(fun, "evalDetection")) {
138 138 check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalDetection'.");
139 139 br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : "");
  140 + } else if (!strcmp(fun, "evalLandmarking")) {
  141 + check((parc >= 2) && (parc <= 3), "Incorrect parameter count for 'evalLandmarking'.");
  142 + br_eval_detection(parv[0], parv[1], parc == 3 ? parv[2] : "");
140 143 } else if (!strcmp(fun, "evalRegression")) {
141   - check(parc == 2, "Incorrect parameter count for 'evalRegression'.");
142   - br_eval_regression(parv[0], parv[1]);
  144 + check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'.");
  145 + br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : "");
143 146 } else if (!strcmp(fun, "plotDetection")) {
144 147 check(parc >= 2, "Incorrect parameter count for 'plotDetection'.");
145 148 br_plot_detection(parc-1, parv, parv[parc-1], true);
... ... @@ -215,10 +218,11 @@ private:
215 218 "-combineMasks <mask> ... <mask> {mask} (And|Or)\n"
216 219 "-cat <gallery> ... <gallery> {gallery}\n"
217 220 "-convert (Format|Gallery|Output) <input_file> {output_file}\n"
218   - "-evalClassification <predicted_gallery> <truth_gallery>\n"
  221 + "-evalClassification <predicted_gallery> <truth_gallery> <predicted property name> <ground truth proprty name>\n"
219 222 "-evalClustering <clusters> <gallery>\n"
220 223 "-evalDetection <predicted_gallery> <truth_gallery> [{csv}]\n"
221   - "-evalRegression <predicted_gallery> <truth_gallery>\n"
  224 + "-evalLandmarking <predicted_gallery> <truth_gallery> [{csv}]\n"
  225 + "-evalRegression <predicted_gallery> <truth_gallery> <predicted property name> <ground truth property name>\n"
222 226 "-plotDetection <file> ... <file> {destination}\n"
223 227 "-plotMetadata <file> ... <file> <columns>\n"
224 228 "-getHeader <matrix>\n"
... ...
app/examples/age_estimation.cpp
... ... @@ -29,7 +29,7 @@
29 29  
30 30 static void printTemplate(const br::Template &t)
31 31 {
32   - printf("%s age: %d\n", qPrintable(t.file.fileName()), int(t.file.get<float>("Subject")));
  32 + printf("%s age: %d\n", qPrintable(t.file.fileName()), int(t.file.get<float>("Age")));
33 33 }
34 34  
35 35 int main(int argc, char *argv[])
... ...
app/examples/face_recognition_evaluation.cpp
... ... @@ -25,8 +25,7 @@
25 25 * -compare target.gal query.gal scores.mtx \
26 26 * -makeMask ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml MEDS.mask \
27 27 * -eval scores.mtx MEDS.mask Algorithm_Dataset/FaceRecognition_MEDS.csv \
28   - * -eval ../data/MEDS/simmat/COTS_MEDS.mtx MEDS.mask Algorithm_Dataset/COTS_MEDS.csv \
29   - * -plot Algorithm_Dataset/FaceRecognition_MEDS.csv Algorithm_Dataset/COTS_MEDS.csv MEDS
  28 + * -plot Algorithm_Dataset/FaceRecognition_MEDS.csv MEDS
30 29 * \endcode
31 30 */
32 31  
... ...
app/examples/gender_estimation.cpp
... ... @@ -29,7 +29,7 @@
29 29  
30 30 static void printTemplate(const br::Template &t)
31 31 {
32   - printf("%s gender: %s\n", qPrintable(t.file.fileName()), qPrintable(t.file.get<QString>("Subject")));
  32 + printf("%s gender: %s\n", qPrintable(t.file.fileName()), qPrintable(t.file.get<QString>("Gender")));
33 33 }
34 34  
35 35 int main(int argc, char *argv[])
... ...
openbr/core/bee.cpp
... ... @@ -99,10 +99,10 @@ void BEE::writeSigset(const QString &amp;sigset, const br::FileList &amp;files, bool ign
99 99 QStringList metadata;
100 100 if (!ignoreMetadata)
101 101 foreach (const QString &key, file.localKeys()) {
102   - if ((key == "Index") || (key == "Subject")) continue;
  102 + if ((key == "Index") || (key == "Label")) continue;
103 103 metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\"");
104 104 }
105   - lines.append("\t<biometric-signature name=\"" + file.get<QString>("Subject",file.fileName()) +"\">");
  105 + lines.append("\t<biometric-signature name=\"" + file.get<QString>("Label",file.fileName()) +"\">");
106 106 lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>");
107 107 lines.append("\t</biometric-signature>");
108 108 }
... ... @@ -266,10 +266,11 @@ void BEE::makeMask(const QString &amp;targetInput, const QString &amp;queryInput, const
266 266  
267 267 cv::Mat BEE::makeMask(const br::FileList &targets, const br::FileList &queries, int partition)
268 268 {
269   - // Would like to use indexProperty for this, but didn't make a version of that for Filelist yet
270   - // -cao
271   - QList<QString> targetLabels = File::get<QString>(targets, "Subject", "-1");
272   - QList<QString> queryLabels = File::get<QString>(queries, "Subject", "-1");
  269 + // Direct use of "Label" isn't general, also would prefer to use indexProperty, rather than
  270 + // doing string comparisons (but that isn't implemented yet for FileList) -cao
  271 + QList<QString> targetLabels = File::get<QString>(targets, "Label", "-1");
  272 + QList<QString> queryLabels = File::get<QString>(queries, "Label", "-1");
  273 +
273 274 QList<int> targetPartitions = targets.crossValidationPartitions();
274 275 QList<int> queryPartitions = queries.crossValidationPartitions();
275 276  
... ...
openbr/core/cluster.cpp
... ... @@ -279,8 +279,8 @@ void br::EvalClustering(const QString &amp;csv, const QString &amp;input)
279 279 qDebug("Evaluating %s against %s", qPrintable(csv), qPrintable(input));
280 280  
281 281 // We assume clustering algorithms store assigned cluster labels as integers (since the clusters are
282   - // not named).
283   - QList<int> labels = File::get<int>(TemplateList::fromGallery(input), "Subject");
  282 + // not named). Direct use of ClusterID is not general -cao
  283 + QList<int> labels = File::get<int>(TemplateList::fromGallery(input), "ClusterID");
284 284  
285 285 QHash<int, int> labelToIndex;
286 286 int nClusters = 0;
... ...
openbr/core/common.cpp
... ... @@ -15,11 +15,15 @@
15 15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16 16  
17 17 #include "common.h"
  18 +#include <QMutex>
18 19  
19 20 using namespace std;
20 21  
21 22 /**** GLOBAL ****/
22 23 void Common::seedRNG() {
  24 + static QMutex seedControl;
  25 + QMutexLocker lock(&seedControl);
  26 +
23 27 static bool seeded = false;
24 28 if (!seeded) {
25 29 srand(0); // We seed with 0 instead of time(NULL) to have reproducible randomness
... ... @@ -29,8 +33,6 @@ void Common::seedRNG() {
29 33  
30 34 QList<int> Common::RandSample(int n, int max, int min, bool unique)
31 35 {
32   - seedRNG();
33   -
34 36 QList<int> samples; samples.reserve(n);
35 37 int range = max-min;
36 38 if (range <= 0) qFatal("Non-positive range.");
... ... @@ -50,8 +52,6 @@ QList&lt;int&gt; Common::RandSample(int n, int max, int min, bool unique)
50 52  
51 53 QList<int> Common::RandSample(int n, const QSet<int> &values, bool unique)
52 54 {
53   - seedRNG();
54   -
55 55 QList<int> valueList = values.toList();
56 56 if (unique && (values.size() <= n)) return valueList;
57 57  
... ...
openbr/core/core.cpp
... ... @@ -147,13 +147,7 @@ struct AlgorithmCore
147 147 data.removeAt(i);
148 148 const int numFiles = data.size();
149 149  
150   - if (Globals->backProject) {
151   - TemplateList backProjectedData;
152   - transform->backProject(data, backProjectedData);
153   - data = backProjectedData;
154   - } else {
155   - data >> *transform;
156   - }
  150 + data >> *transform;
157 151  
158 152 g->writeBlock(data);
159 153 const FileList newFiles = data.files();
... ... @@ -394,7 +388,6 @@ void br::Convert(const File &amp;fileType, const File &amp;inputFile, const File &amp;output
394 388 QSharedPointer<Output> o(Factory<Output>::make(outputFile));
395 389 o->initialize(targetFiles, queryFiles);
396 390  
397   - qDebug() << m.rows << m.cols << targetFiles.size() << queryFiles.size();
398 391 for (int i=0; i<queryFiles.size(); i++)
399 392 for (int j=0; j<targetFiles.size(); j++)
400 393 o->setRelative(m.at<float>(i,j), i, j);
... ...
openbr/core/eval.cpp
... ... @@ -255,9 +255,20 @@ struct Counter
255 255 }
256 256 };
257 257  
258   -void EvalClassification(const QString &predictedInput, const QString &truthInput)
  258 +void EvalClassification(const QString &predictedInput, const QString &truthInput, QString predictedProperty, QString truthProperty)
259 259 {
260 260 qDebug("Evaluating classification of %s against %s", qPrintable(predictedInput), qPrintable(truthInput));
  261 +
  262 + if (predictedProperty.isEmpty())
  263 + predictedProperty = "Label";
  264 + // If predictedProperty is specified, but truthProperty isn't, copy over the value from
  265 + // predicted property
  266 + else if (truthProperty.isEmpty())
  267 + truthProperty = predictedProperty;
  268 +
  269 + if (truthProperty.isEmpty())
  270 + truthProperty = "Label";
  271 +
261 272 TemplateList predicted(TemplateList::fromGallery(predictedInput));
262 273 TemplateList truth(TemplateList::fromGallery(truthInput));
263 274 if (predicted.size() != truth.size()) qFatal("Input size mismatch.");
... ... @@ -267,9 +278,8 @@ void EvalClassification(const QString &amp;predictedInput, const QString &amp;truthInput
267 278 if (predicted[i].file.name != truth[i].file.name)
268 279 qFatal("Input order mismatch.");
269 280  
270   - // Typically these lists will be of length one, but this generalization allows measuring multi-class labeling accuracy.
271   - QString predictedSubject = predicted[i].file.get<QString>("Subject");
272   - QString trueSubject = truth[i].file.get<QString>("Subject");
  281 + QString predictedSubject = predicted[i].file.get<QString>(predictedProperty);
  282 + QString trueSubject = truth[i].file.get<QString>(truthProperty);
273 283  
274 284 QStringList predictedSubjects(predictedSubject);
275 285 QStringList trueSubjects(trueSubject);
... ... @@ -373,6 +383,8 @@ static QStringList computeDetectionResults(const QList&lt;ResolvedDetection&gt; &amp;detec
373 383 }
374 384  
375 385 const int keep = qMin(points.size(), Max_Points);
  386 + if (keep < 2) qFatal("Insufficient points.");
  387 +
376 388 QStringList lines; lines.reserve(keep);
377 389 for (int i=0; i<keep; i++) {
378 390 const DetectionOperatingPoint &point = points[double(i) / double(keep-1) * double(points.size()-1)];
... ... @@ -382,6 +394,15 @@ static QStringList computeDetectionResults(const QList&lt;ResolvedDetection&gt; &amp;detec
382 394 return lines;
383 395 }
384 396  
  397 +QString getDetectKey(const TemplateList &templates)
  398 +{
  399 + const File &f = templates.first().file;
  400 + foreach (const QString &key, f.localKeys())
  401 + if (!f.get<QRectF>(key, QRectF()).isNull())
  402 + return key;
  403 + return "";
  404 +}
  405 +
385 406 float EvalDetection(const QString &predictedInput, const QString &truthInput, const QString &csv)
386 407 {
387 408 qDebug("Evaluating detection of %s against %s", qPrintable(predictedInput), qPrintable(truthInput));
... ... @@ -389,20 +410,22 @@ float EvalDetection(const QString &amp;predictedInput, const QString &amp;truthInput, co
389 410 const TemplateList truth(TemplateList::fromGallery(truthInput));
390 411  
391 412 // Figure out which metadata field contains a bounding box
392   - QString detectKey;
393   - foreach (const QString &key, truth.first().file.localKeys())
394   - if (!truth.first().file.get<QRectF>(key, QRectF()).isNull()) {
395   - detectKey = key;
396   - break;
397   - }
398   - if (detectKey.isNull()) qFatal("No suitable metadata key found.");
399   - else qDebug("Using metadata key: %s", qPrintable(detectKey));
  413 + QString truthDetectKey = getDetectKey(truth);
  414 + if (truthDetectKey.isEmpty()) qFatal("No suitable ground truth metadata key found.");
  415 + QString predictedDetectKey = truthDetectKey;
  416 + if (predicted.first().file.get<QRectF>(predictedDetectKey, QRectF()).isNull())
  417 + predictedDetectKey = getDetectKey(predicted);
  418 + if (predictedDetectKey.isEmpty()) qFatal("No suitable predicted metadata key found.");
  419 +
  420 + qDebug("Using metadata key: %s%s",
  421 + qPrintable(predictedDetectKey),
  422 + qPrintable(predictedDetectKey == truthDetectKey ? QString() : "/"+truthDetectKey));
400 423  
401 424 QMap<QString, Detections> allDetections; // Organized by file, QMap used to preserve order
402 425 foreach (const Template &t, predicted)
403   - allDetections[t.file.baseName()].predicted.append(Detection(t.file.get<QRectF>(detectKey), t.file.get<float>("Confidence", -1)));
  426 + allDetections[t.file.baseName()].predicted.append(Detection(t.file.get<QRectF>(predictedDetectKey), t.file.get<float>("Confidence", -1)));
404 427 foreach (const Template &t, truth)
405   - allDetections[t.file.baseName()].truth.append(Detection(t.file.get<QRectF>(detectKey)));
  428 + allDetections[t.file.baseName()].truth.append(Detection(t.file.get<QRectF>(truthDetectKey)));
406 429  
407 430 QList<ResolvedDetection> resolvedDetections, falseNegativeDetections;
408 431 foreach (Detections detections, allDetections.values()) {
... ... @@ -453,21 +476,44 @@ float EvalDetection(const QString &amp;predictedInput, const QString &amp;truthInput, co
453 476 return averageOverlap;
454 477 }
455 478  
456   -void EvalRegression(const QString &predictedInput, const QString &truthInput)
  479 +void EvalLandmarking(const QString &predictedInput, const QString &truthInput, const QString &csv)
  480 +{
  481 + (void) predictedInput;
  482 + (void) truthInput;
  483 + (void) csv;
  484 +}
  485 +
  486 +void EvalRegression(const QString &predictedInput, const QString &truthInput, QString predictedProperty, QString truthProperty)
457 487 {
458 488 qDebug("Evaluating regression of %s against %s", qPrintable(predictedInput), qPrintable(truthInput));
  489 +
  490 + if (predictedProperty.isEmpty())
  491 + predictedProperty = "Regressor";
  492 + // If predictedProperty is specified, but truthProperty isn't, copy the value over
  493 + // rather than using the default for truthProperty
  494 + else if (truthProperty.isEmpty())
  495 + truthProperty = predictedProperty;
  496 +
  497 + if (truthProperty.isEmpty())
  498 + predictedProperty = "Regressand";
  499 +
459 500 const TemplateList predicted(TemplateList::fromGallery(predictedInput));
460 501 const TemplateList truth(TemplateList::fromGallery(truthInput));
461 502 if (predicted.size() != truth.size()) qFatal("Input size mismatch.");
462 503  
463 504 float rmsError = 0;
  505 + float maeError = 0;
464 506 QStringList truthValues, predictedValues;
465 507 for (int i=0; i<predicted.size(); i++) {
466 508 if (predicted[i].file.name != truth[i].file.name)
467 509 qFatal("Input order mismatch.");
468   - rmsError += pow(predicted[i].file.get<float>("Subject")-truth[i].file.get<float>("Subject"), 2.f);
469   - truthValues.append(QString::number(truth[i].file.get<float>("Subject")));
470   - predictedValues.append(QString::number(predicted[i].file.get<float>("Subject")));
  510 +
  511 + float difference = predicted[i].file.get<float>(predictedProperty) - truth[i].file.get<float>(truthProperty);
  512 +
  513 + rmsError += pow(difference, 2.f);
  514 + maeError += fabsf(difference);
  515 + truthValues.append(QString::number(truth[i].file.get<float>(truthProperty)));
  516 + predictedValues.append(QString::number(predicted[i].file.get<float>(predictedProperty)));
471 517 }
472 518  
473 519 QStringList rSource;
... ... @@ -487,6 +533,7 @@ void EvalRegression(const QString &amp;predictedInput, const QString &amp;truthInput)
487 533 if (success) QtUtils::showFile("EvalRegression.pdf");
488 534  
489 535 qDebug("RMS Error = %f", sqrt(rmsError/predicted.size()));
  536 + qDebug("MAE = %f", maeError/predicted.size());
490 537 }
491 538  
492 539 } // namespace br
... ...
openbr/core/eval.h
... ... @@ -26,9 +26,10 @@ namespace br
26 26 float Evaluate(const QString &simmat, const QString &mask = "", const QString &csv = ""); // Returns TAR @ FAR = 0.001
27 27 float Evaluate(const cv::Mat &scores, const FileList &target, const FileList &query, const QString &csv = "", int parition = 0);
28 28 float Evaluate(const cv::Mat &scores, const cv::Mat &masks, const QString &csv = "");
29   - void EvalClassification(const QString &predictedInput, const QString &truthInput);
  29 + void EvalClassification(const QString &predictedInput, const QString &truthInput, QString predictedProperty="", QString truthProperty="");
30 30 float EvalDetection(const QString &predictedInput, const QString &truthInput, const QString &csv = ""); // Return average overlap
31   - void EvalRegression(const QString &predictedInput, const QString &truthInput);
  31 + void EvalLandmarking(const QString &predictedInput, const QString &truthInput, const QString &csv = "");
  32 + void EvalRegression(const QString &predictedInput, const QString &truthInput, QString predictedProperty="", QString truthProperty="");
32 33 }
33 34  
34 35 #endif // __EVAL_H
... ...
openbr/core/qtutils.cpp
... ... @@ -334,8 +334,8 @@ QRectF QtUtils::toRect(const QString &amp;string, bool *ok)
334 334 bool okX, okY, okWidth, okHeight;
335 335 x = words[0].toFloat(&okX);
336 336 y = words[1].toFloat(&okY);
337   - width = words[0].toFloat(&okWidth);
338   - height = words[1].toFloat(&okHeight);
  337 + width = words[2].toFloat(&okWidth);
  338 + height = words[3].toFloat(&okHeight);
339 339 if (okX && okY && okWidth && okHeight) {
340 340 if (ok) *ok = true;
341 341 return QRectF(x, y, width, height);
... ...
openbr/core/resource.h
... ... @@ -24,6 +24,7 @@
24 24 #include <QSharedPointer>
25 25 #include <QString>
26 26 #include <QThread>
  27 +#include <openbr/openbr_plugin.h>
27 28  
28 29 template <typename T>
29 30 class ResourceMaker
... ... @@ -52,7 +53,7 @@ public:
52 53 : resourceMaker(rm)
53 54 , availableResources(new QList<T*>())
54 55 , lock(new QMutex())
55   - , totalResources(new QSemaphore(QThread::idealThreadCount()))
  56 + , totalResources(new QSemaphore(br::Globals->parallelism))
56 57 {}
57 58  
58 59 ~Resource()
... ...
openbr/frvt2012.cpp
... ... @@ -132,7 +132,7 @@ int32_t SdkEstimator::estimate_age(const ONEFACE &amp;input_face, int32_t &amp;age)
132 132 TemplateList templates;
133 133 templates.append(templateFromONEFACE(input_face));
134 134 templates >> *frvt2012_age_transform.data();
135   - age = templates.first().file.get<float>("Subject");
  135 + age = templates.first().file.get<float>("Age");
136 136 return templates.first().file.failed() ? 4 : 0;
137 137 }
138 138  
... ... @@ -141,6 +141,6 @@ int32_t SdkEstimator::estimate_gender(const ONEFACE &amp;input_face, int8_t &amp;gender,
141 141 TemplateList templates;
142 142 templates.append(templateFromONEFACE(input_face));
143 143 templates >> *frvt2012_gender_transform.data();
144   - mf = gender = templates.first().file.get<QString>("Subject") == "Male" ? 0 : 1;
  144 + mf = gender = templates.first().file.get<QString>("Gender") == "Male" ? 0 : 1;
145 145 return templates.first().file.failed() ? 4 : 0;
146 146 }
... ...
openbr/gui/classifier.cpp
... ... @@ -39,19 +39,23 @@ void Classifier::_classify(File file)
39 39 {
40 40 QString key, value;
41 41 foreach (const File &f, Enroll(file.flat(), File("[algorithm=" + algorithm + "]"))) {
42   - if (!f.contains("Label"))
43   - continue;
44 42  
45 43 if (algorithm == "GenderClassification") {
46 44 key = "Gender";
47   - value = (f.get<QString>("Subject"));
48 45 } else if (algorithm == "AgeRegression") {
49 46 key = "Age";
50   - value = QString::number(int(f.get<float>("Subject")+0.5)) + " Years";
51 47 } else {
52 48 key = algorithm;
53   - value = f.get<QString>("Subject");
54 49 }
  50 +
  51 + if (!f.contains(key))
  52 + continue;
  53 +
  54 + if (algorithm == "AgeRegression")
  55 + value = QString::number(int(f.get<float>(key)+0.5)) + " Years";
  56 + else
  57 + value = f.get<QString>(key);
  58 +
55 59 break;
56 60 }
57 61  
... ...
openbr/openbr.cpp
... ... @@ -72,9 +72,9 @@ float br_eval(const char *simmat, const char *mask, const char *csv)
72 72 return Evaluate(simmat, mask, csv);
73 73 }
74 74  
75   -void br_eval_classification(const char *predicted_gallery, const char *truth_gallery)
  75 +void br_eval_classification(const char *predicted_gallery, const char *truth_gallery, const char *predicted_property, const char * truth_property)
76 76 {
77   - EvalClassification(predicted_gallery, truth_gallery);
  77 + EvalClassification(predicted_gallery, truth_gallery, predicted_property, truth_property);
78 78 }
79 79  
80 80 void br_eval_clustering(const char *csv, const char *gallery)
... ... @@ -87,9 +87,14 @@ float br_eval_detection(const char *predicted_gallery, const char *truth_gallery
87 87 return EvalDetection(predicted_gallery, truth_gallery, csv);
88 88 }
89 89  
90   -void br_eval_regression(const char *predicted_gallery, const char *truth_gallery)
  90 +void br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv)
91 91 {
92   - EvalRegression(predicted_gallery, truth_gallery);
  92 + return EvalLandmarking(predicted_gallery, truth_gallery, csv);
  93 +}
  94 +
  95 +void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char * predicted_property, const char * truth_property)
  96 +{
  97 + EvalRegression(predicted_gallery, truth_gallery, predicted_property, truth_property);
93 98 }
94 99  
95 100 void br_finalize()
... ...
openbr/openbr.h
... ... @@ -149,7 +149,7 @@ BR_EXPORT float br_eval(const char *simmat, const char *mask, const char *csv =
149 149 * \param predicted_gallery The predicted br::Gallery.
150 150 * \param truth_gallery The ground truth br::Gallery.
151 151 */
152   -BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char *truth_gallery);
  152 +BR_EXPORT void br_eval_classification(const char *predicted_gallery, const char *truth_gallery, const char * predicted_property="", const char * truth_property="");
153 153  
154 154 /*!
155 155 * \brief Evaluates and prints clustering accuracy to the terminal.
... ... @@ -169,11 +169,19 @@ BR_EXPORT void br_eval_clustering(const char *csv, const char *gallery);
169 169 BR_EXPORT float br_eval_detection(const char *predicted_gallery, const char *truth_gallery, const char *csv = "");
170 170  
171 171 /*!
  172 + * \brief Evaluates and prints landmarking accuracy to terminal.
  173 + * \param predicted_gallery The predicted br::Gallery.
  174 + * \param truth_gallery The ground truth br::Gallery.
  175 + * \param csv Optional \c .csv file to contain performance metrics.
  176 + */
  177 +BR_EXPORT void br_eval_landmarking(const char *predicted_gallery, const char *truth_gallery, const char *csv = "");
  178 +
  179 +/*!
172 180 * \brief Evaluates regression accuracy to disk.
173 181 * \param predicted_gallery The predicted br::Gallery.
174 182 * \param truth_gallery The ground truth br::Gallery.
175 183 */
176   -BR_EXPORT void br_eval_regression(const char *predicted_gallery, const char *truth_gallery);
  184 +BR_EXPORT void br_eval_regression(const char *predicted_gallery, const char *truth_gallery, const char * predicted_property="", const char * truth_property="");
177 185  
178 186 /*!
179 187 * \brief Wraps br::Context::finalize()
... ...
openbr/openbr_export.cpp
... ... @@ -69,7 +69,6 @@
69 69 $ cd bin
70 70 $ export LD_LIBRARY_PATH=../lib:${LD_LIBRARY_PATH}
71 71 $ sudo ldconfig
72   -$ sudo cp ../share/openbr/70-yubikey.rules /etc/udev/rules.d # Only needed if you were given a license dongle.
73 72 \endverbatim
74 73 * \par OS X
75 74 \verbatim
... ... @@ -80,10 +79,6 @@ $ export DYLD_FRAMEWORK_PATH=../lib:${DYLD_FRAMEWORK_PATH}
80 79 * \par Windows
81 80 * No configuration is necessary!
82 81 *
83   - * \section installation_license_dongle License Dongle
84   - * In the unlikely event that you were given a USB License Dongle, then dongle must be in the computer in order to use the SDK.
85   - * No configuration of the dongle is needed.
86   - *
87 82 * \section installation_done Start Working
88 83 * To test for successful installation:
89 84 \verbatim
... ...
openbr/openbr_plugin.cpp
... ... @@ -437,6 +437,18 @@ TemplateList TemplateList::fromGallery(const br::File &amp;gallery)
437 437 // Select the right 8 hex characters so that it can be represented as a 64 bit integer without overflow
438 438 newTemplates[i].file.set("Partition", md5.toHex().right(8).toULongLong(0, 16) % crossValidate);
439 439 }
  440 +<<<<<<< HEAD
  441 +=======
  442 + } else if (newTemplates[i].file.getBool("allPartitions")) {
  443 + // The allPartitions flag is used to add an extended set
  444 + // of target images to every partition
  445 + newTemplates[i].file.set("Partition", -1);
  446 + } else {
  447 + // Direct use of "Label" is not general -cao
  448 + const QByteArray md5 = QCryptographicHash::hash(newTemplates[i].file.get<QString>("Label").toLatin1(), QCryptographicHash::Md5);
  449 + // Select the right 8 hex characters so that it can be represented as a 64 bit integer without overflow
  450 + newTemplates[i].file.set("Partition", md5.toHex().right(8).toULongLong(0, 16) % crossValidate);
  451 +>>>>>>> c2b1835e05d3b229db72d8829fb9ebf7e3cf31d8
440 452 }
441 453 }
442 454 }
... ... @@ -834,7 +846,7 @@ float br::Context::progress() const
834 846  
835 847 void br::Context::setProperty(const QString &key, const QString &value)
836 848 {
837   - Object::setProperty(key, value);
  849 + Object::setProperty(key, value.isEmpty() ? QVariant() : value);
838 850 qDebug("Set %s%s", qPrintable(key), value.isEmpty() ? "" : qPrintable(" to " + value));
839 851  
840 852 if (key == "parallelism") {
... ... @@ -912,6 +924,8 @@ void br::Context::initialize(int &amp;argc, char *argv[], QString sdkPath, bool use_
912 924  
913 925 qInstallMessageHandler(messageHandler);
914 926  
  927 + Common::seedRNG();
  928 +
915 929 // Search for SDK
916 930 if (sdkPath.isEmpty()) {
917 931 QStringList checkPaths; checkPaths << QDir::currentPath() << QCoreApplication::applicationDirPath();
... ... @@ -1104,9 +1118,6 @@ Transform::Transform(bool _independent, bool _trainable)
1104 1118 {
1105 1119 independent = _independent;
1106 1120 trainable = _trainable;
1107   - classes = std::numeric_limits<int>::max();
1108   - instances = std::numeric_limits<int>::max();
1109   - fraction = 1;
1110 1121 }
1111 1122  
1112 1123 Transform *Transform::make(QString str, QObject *parent)
... ... @@ -1162,9 +1173,6 @@ Transform *Transform::make(QString str, QObject *parent)
1162 1173 Transform *Transform::clone() const
1163 1174 {
1164 1175 Transform *clone = Factory<Transform>::make(file.flat());
1165   - clone->classes = classes;
1166   - clone->instances = instances;
1167   - clone->fraction = fraction;
1168 1176 return clone;
1169 1177 }
1170 1178  
... ... @@ -1193,28 +1201,6 @@ void Transform::project(const TemplateList &amp;src, TemplateList &amp;dst) const
1193 1201 futures.waitForFinished();
1194 1202 }
1195 1203  
1196   -static void _backProject(const Transform *transform, const Template *dst, Template *src)
1197   -{
1198   - try {
1199   - transform->backProject(*dst, *src);
1200   - } catch (...) {
1201   - qWarning("Exception triggered when processing %s with transform %s", qPrintable(src->file.flat()), qPrintable(transform->objectName()));
1202   - *src = Template(dst->file);
1203   - src->file.set("FTE", true);
1204   - }
1205   -}
1206   -
1207   -void Transform::backProject(const TemplateList &dst, TemplateList &src) const
1208   -{
1209   - src.reserve(dst.size());
1210   - for (int i=0; i<dst.size(); i++) src.append(Template());
1211   -
1212   - QFutureSynchronizer<void> futures;
1213   - for (int i=0; i<dst.size(); i++)
1214   - futures.addFuture(QtConcurrent::run(_backProject, this, &dst[i], &src[i]));
1215   - futures.waitForFinished();
1216   -}
1217   -
1218 1204 QList<Transform *> Transform::getChildren() const
1219 1205 {
1220 1206 QList<Transform *> output;
... ...
openbr/openbr_plugin.h
... ... @@ -130,13 +130,6 @@ void reset_##NAME() { NAME = DEFAULT; }
130 130 * -# If the value is convertable to a floating point number then it is represented with \c float.
131 131 * -# Otherwise, it is represented with \c QString.
132 132 *
133   - * The metadata keys \c Subject and \c Label have special significance in the system.
134   - * \c Subject is a string specifying a unique identifier used to determine ground truth match/non-match.
135   - * \c Label is a floating point value used for supervised learning.
136   - * When the system needs labels for training, but only subjects are provided in the file metadata, the rule for generating labels is as follows.
137   - * If the subject value can be converted to a float then do so and consider that the label.
138   - * Otherwise, generate a unique integer ID for the string starting from zero and incrementing by one everytime another ID is needed.
139   - *
140 133 * Metadata keys fall into one of two categories:
141 134 * - \c camelCaseKeys are inputs that specify how to process the file.
142 135 * - \c Capitalized_Underscored_Keys are outputs computed from processing the file.
... ... @@ -147,8 +140,6 @@ void reset_##NAME() { NAME = DEFAULT; }
147 140 * --- | ---- | -----------
148 141 * separator | QString | Seperate #name into multiple files
149 142 * Index | int | Index of a template in a template list
150   - * Subject | QString | Class name
151   - * Label | float | Class value
152 143 * Confidence | float | Classification/Regression quality
153 144 * FTE | bool | Failure to enroll
154 145 * FTO | bool | Failure to open
... ... @@ -157,13 +148,15 @@ void reset_##NAME() { NAME = DEFAULT; }
157 148 * *_Width | float | Size
158 149 * *_Height | float | Size
159 150 * *_Radius | float | Size
  151 + * Label | QString | Class label
160 152 * Theta | float | Pose
161 153 * Roll | float | Pose
162 154 * Pitch | float | Pose
163 155 * Yaw | float | Pose
164 156 * Points | QList<QPointF> | List of unnamed points
165 157 * Rects | QList<Rect> | List of unnamed rects
166   - * Age | QString | Age used for demographic filtering
  158 + * Age | float | Age used for demographic filtering
  159 + * Gender | QString | Subject gender
167 160 * _* | * | Reserved for internal use
168 161 */
169 162 struct BR_EXPORT File
... ... @@ -172,7 +165,7 @@ struct BR_EXPORT File
172 165  
173 166 File() {}
174 167 File(const QString &file) { init(file); } /*!< \brief Construct a file from a string. */
175   - File(const QString &file, const QVariant &subject) { init(file); set("Subject", subject); } /*!< \brief Construct a file from a string and assign a label. */
  168 + File(const QString &file, const QVariant &label) { init(file); set("Label", label); } /*!< \brief Construct a file from a string and assign a label. */
176 169 File(const char *file) { init(file); } /*!< \brief Construct a file from a c-style string. */
177 170 inline operator QString() const { return name; } /*!< \brief Returns #name. */
178 171 QString flat() const; /*!< \brief A stringified version of the file with metadata. */
... ... @@ -616,13 +609,6 @@ public:
616 609 BR_PROPERTY(int, blockSize, parallelism * ((sizeof(void*) == 4) ? 128 : 1024))
617 610  
618 611 /*!
619   - * \brief true if backProject should be used instead of project (the algorithm should be inverted)
620   - */
621   - Q_PROPERTY(bool backProject READ get_backProject WRITE set_backProject RESET reset_backProject)
622   - BR_PROPERTY(bool, backProject, false)
623   -
624   -
625   - /*!
626 612 * \brief If \c true no messages will be sent to the terminal, \c false by default.
627 613 */
628 614 Q_PROPERTY(bool quiet READ get_quiet WRITE set_quiet RESET reset_quiet)
... ... @@ -1048,6 +1034,10 @@ private:
1048 1034 * @{
1049 1035 */
1050 1036  
  1037 +/*!
  1038 + * \brief For asynchronous events during template projection.
  1039 + * \see #Transform::getEvent
  1040 + */
1051 1041 class TemplateEvent : public QObject
1052 1042 {
1053 1043 Q_OBJECT
... ... @@ -1062,7 +1052,6 @@ signals:
1062 1052 void theSignal(const Template & output) const;
1063 1053 };
1064 1054  
1065   -
1066 1055 /*!
1067 1056 * \brief Plugin base class for processing a template.
1068 1057 *
... ... @@ -1076,12 +1065,6 @@ class BR_EXPORT Transform : public Object
1076 1065 Q_OBJECT
1077 1066  
1078 1067 public:
1079   - Q_PROPERTY(int classes READ get_classes WRITE set_classes RESET reset_classes STORED false)
1080   - Q_PROPERTY(int instances READ get_instances WRITE set_instances RESET reset_instances STORED false)
1081   - Q_PROPERTY(float fraction READ get_fraction WRITE set_fraction RESET reset_fraction STORED false)
1082   - BR_PROPERTY(int, classes, std::numeric_limits<int>::max())
1083   - BR_PROPERTY(int, instances, std::numeric_limits<int>::max())
1084   - BR_PROPERTY(float, fraction, 1)
1085 1068 bool independent, trainable;
1086 1069  
1087 1070 virtual ~Transform() {}
... ... @@ -1092,8 +1075,6 @@ public:
1092 1075 virtual void train(const TemplateList &data) = 0; /*!< \brief Train the transform. */
1093 1076 virtual void project(const Template &src, Template &dst) const = 0; /*!< \brief Apply the transform. */
1094 1077 virtual void project(const TemplateList &src, TemplateList &dst) const; /*!< \brief Apply the transform. */
1095   - virtual void backProject(const Template &dst, Template &src) const { src = dst; } /*!< \brief Invert the transform. */
1096   - virtual void backProject(const TemplateList &dst, TemplateList &src) const; /*!< \brief Invert the transform. */
1097 1078  
1098 1079 /*!< \brief Apply the transform, may update the transform's internal state */
1099 1080 virtual void projectUpdate(const Template &src, Template &dst)
... ...
openbr/plugins/algorithms.cpp
... ... @@ -46,9 +46,12 @@ class AlgorithmsInitializer : public Initializer
46 46 Globals->abbreviations.insert("CropFace", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.25,0.35)");
47 47  
48 48 // Video
49   - Globals->abbreviations.insert("DisplayVideo", "Stream([Show(false,[FrameNumber])+Discard])");
  49 + Globals->abbreviations.insert("DisplayVideo", "Stream([FPSLimit(30)+Show(false,[FrameNumber])+Discard])");
50 50 Globals->abbreviations.insert("PerFrameDetection", "Stream([SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+RestoreMat(original)+Draw(inPlace=true),Show(false,[FrameNumber])+Discard])");
51   - Globals->abbreviations.insert("AgeGenderDemo", "Stream([SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+<FaceClassificationRegistration>+<FaceClassificationExtraction>+(<AgeRegressor>+Rename(Subject,Age)+Discard)/(<GenderClassifier>+Rename(Subject,Gender)+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])");
  51 + Globals->abbreviations.insert("AgeGenderDemo", "Stream([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])");
  52 + Globals->abbreviations.insert("HOG", "Stream([KeyPointDetector(SIFT)+ROI+Expand+Resize(32,32)+Gradient+RectRegions+Bin(0,360,8)+Hist(8)+Cat])+Contract+CatRows+KMeans(500)+Hist(500)+SVM");
  53 + Globals->abbreviations.insert("HOF", "Stream([KeyPointDetector(SIFT),AggregateFrames(2)+OpticalFlow+Gradient+Bin(0,360,8)+ROI+Hist(8)])+Contract+CatRows+KMeans(500)+Hist(500)");
  54 + Globals->abbreviations.insert("HOGHOF", "Stream([Cvt(Gray),KeyPointDetector(SIFT),AggregateFrames(2),(OpticalFlow+Gradient+Bin(0,360,8)+ROI+Hist(8))/(First+Gradient+Bin(0,360,8)+ROI+Hist(8)),CatCols])+Contract+CatRows+KMeans(500)+Hist(500)");
52 55  
53 56 // Generic Image Processing
54 57 Globals->abbreviations.insert("SIFT", "Open+KeyPointDetector(SIFT)+KeyPointDescriptor(SIFT):KeyPointMatcher(BruteForce)");
... ... @@ -74,14 +77,14 @@ class AlgorithmsInitializer : public Initializer
74 77 Globals->abbreviations.insert("FaceDetection", "(Open+Cvt(Gray)+Cascade(FrontalFace))");
75 78 Globals->abbreviations.insert("DenseLBP", "(Blur(1.1)+Gamma(0.2)+DoG(1,2)+ContrastEq(0.1,10)+LBP(1,2)+RectRegions(8,8,6,6)+Hist(59))");
76 79 Globals->abbreviations.insert("DenseSIFT", "(Grid(10,10)+SIFTDescriptor(12)+ByRow)");
77   - Globals->abbreviations.insert("FaceRecognitionRegistration", "(ASEFEyes+Affine(88,88,0.25,0.35)+FTE(DFFS,instances=1))");
78   - Globals->abbreviations.insert("FaceRecognitionExtraction", "(Mask+DenseSIFT/DenseLBP+PCA(0.95,instances=1)+Normalize(L2)+Cat)");
79   - Globals->abbreviations.insert("FaceRecognitionEmbedding", "(Dup(12)+RndSubspace(0.05,1)+LDA(0.98,instances=-2)+Cat+PCA(768,instances=1))");
  80 + Globals->abbreviations.insert("FaceRecognitionRegistration", "(ASEFEyes+Affine(88,88,0.25,0.35)+DownsampleTraining(FTE(DFFS),instances=1))");
  81 + Globals->abbreviations.insert("FaceRecognitionExtraction", "(Mask+DenseSIFT/DenseLBP+DownsampleTraining(PCA(0.95),instances=1)+Normalize(L2)+Cat)");
  82 + Globals->abbreviations.insert("FaceRecognitionEmbedding", "(Dup(12)+RndSubspace(0.05,1)+DownsampleTraining(LDA(0.98),instances=-2)+Cat+DownsampleTraining(PCA(768),instances=1))");
80 83 Globals->abbreviations.insert("FaceRecognitionQuantization", "(Normalize(L1)+Quantize)");
81 84 Globals->abbreviations.insert("FaceClassificationRegistration", "(ASEFEyes+Affine(56,72,0.33,0.45)+FTE(DFFS))");
82   - Globals->abbreviations.insert("FaceClassificationExtraction", "((Grid(7,7)+SIFTDescriptor(8)+ByRow)/DenseLBP+PCA(0.95,instances=-1)+Cat)");
83   - Globals->abbreviations.insert("AgeRegressor", "Center(Range,instances=-1)+SVM(RBF,EPS_SVR,instances=100)");
84   - Globals->abbreviations.insert("GenderClassifier", "Center(Range,instances=-1)+SVM(RBF,C_SVC,instances=4000)");
  85 + Globals->abbreviations.insert("FaceClassificationExtraction", "((Grid(7,7)+SIFTDescriptor(8)+ByRow)/DenseLBP+DownsampleTraining(PCA(0.95),instances=-1, inputVariable=Gender)+Cat)");
  86 + Globals->abbreviations.insert("AgeRegressor", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Age)+DownsampleTraining(SVM(RBF,EPS_SVR,inputVariable=Age),instances=100, inputVariable=Age)");
  87 + Globals->abbreviations.insert("GenderClassifier", "DownsampleTraining(Center(Range),instances=-1, inputVariable=Gender)+DownsampleTraining(SVM(RBF,C_SVC,inputVariable=Gender),instances=4000, inputVariable=Gender)");
85 88 Globals->abbreviations.insert("UCharL1", "Unit(ByteL1)");
86 89 }
87 90 };
... ...
openbr/plugins/cascade.cpp
... ... @@ -49,19 +49,20 @@ private:
49 49 }
50 50 };
51 51  
52   -
53 52 /*!
54 53 * \ingroup transforms
55 54 * \brief Wraps OpenCV cascade classifier
56 55 * \author Josh Klontz \cite jklontz
57 56 */
58   -class CascadeTransform : public UntrainableTransform
  57 +class CascadeTransform : public UntrainableMetaTransform
59 58 {
60 59 Q_OBJECT
61 60 Q_PROPERTY(QString model READ get_model WRITE set_model RESET reset_model STORED false)
62 61 Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false)
  62 + Q_PROPERTY(bool ROCMode READ get_ROCMode WRITE set_ROCMode RESET reset_ROCMode STORED false)
63 63 BR_PROPERTY(QString, model, "FrontalFace")
64 64 BR_PROPERTY(int, minSize, 64)
  65 + BR_PROPERTY(bool, ROCMode, false)
65 66  
66 67 Resource<CascadeClassifier> cascadeResource;
67 68  
... ... @@ -72,18 +73,54 @@ class CascadeTransform : public UntrainableTransform
72 73  
73 74 void project(const Template &src, Template &dst) const
74 75 {
  76 + TemplateList temp;
  77 + project(TemplateList() << src, temp);
  78 + if (!temp.isEmpty()) dst = temp.first();
  79 + }
  80 +
  81 + void project(const TemplateList &src, TemplateList &dst) const
  82 + {
75 83 CascadeClassifier *cascade = cascadeResource.acquire();
76   - vector<Rect> rects;
77   - cascade->detectMultiScale(src, rects, 1.2, 5, src.file.get<bool>("enrollAll", false) ? 0 : CV_HAAR_FIND_BIGGEST_OBJECT, Size(minSize, minSize));
  84 + foreach (const Template &t, src) {
  85 + const bool enrollAll = t.file.getBool("enrollAll");
  86 +
  87 + for (int i=0; i<t.size(); i++) {
  88 + const Mat &m = t[i];
  89 + vector<Rect> rects;
  90 + vector<int> rejectLevels;
  91 + vector<double> levelWeights;
  92 + if (ROCMode) cascade->detectMultiScale(m, rects, rejectLevels, levelWeights, 1.2, 5, (enrollAll ? 0 : CV_HAAR_FIND_BIGGEST_OBJECT) | CV_HAAR_SCALE_IMAGE, Size(minSize, minSize), Size(), true);
  93 + else cascade->detectMultiScale(m, rects, 1.2, 5, enrollAll ? 0 : CV_HAAR_FIND_BIGGEST_OBJECT, Size(minSize, minSize));
  94 +
  95 + if (!enrollAll && rects.empty())
  96 + rects.push_back(Rect(0, 0, m.cols, m.rows));
  97 +
  98 + for (size_t j=0; j<rects.size(); j++) {
  99 + Template u(t.file, m);
  100 + if (rejectLevels.size() > j)
  101 + u.file.set("Confidence", rejectLevels[j]*levelWeights[j]);
  102 + const QRectF rect = OpenCVUtils::fromRect(rects[j]);
  103 + u.file.appendRect(rect);
  104 + u.file.set(model, rect);
  105 + dst.append(u);
  106 + }
  107 + }
  108 + }
  109 +
78 110 cascadeResource.release(cascade);
  111 + }
79 112  
80   - if (!src.file.get<bool>("enrollAll", false) && rects.empty())
81   - rects.push_back(Rect(0, 0, src.m().cols, src.m().rows));
  113 + // TODO: Remove this code when ready to break binary compatibility
  114 + void store(QDataStream &stream) const
  115 + {
  116 + int size = 1;
  117 + stream << size;
  118 + }
82 119  
83   - foreach (const Rect &rect, rects) {
84   - dst += src;
85   - dst.file.appendRect(OpenCVUtils::fromRect(rect));
86   - }
  120 + void load(QDataStream &stream)
  121 + {
  122 + int size;
  123 + stream >> size;
87 124 }
88 125 };
89 126  
... ...
openbr/plugins/cluster.cpp
... ... @@ -89,10 +89,14 @@ class KNNTransform : public Transform
89 89 Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance STORED false)
90 90 Q_PROPERTY(bool weighted READ get_weighted WRITE set_weighted RESET reset_weighted STORED false)
91 91 Q_PROPERTY(int numSubjects READ get_numSubjects WRITE set_numSubjects RESET reset_numSubjects STORED false)
  92 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
  93 + Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false)
92 94 BR_PROPERTY(int, k, 1)
93 95 BR_PROPERTY(br::Distance*, distance, NULL)
94 96 BR_PROPERTY(bool, weighted, false)
95 97 BR_PROPERTY(int, numSubjects, 1)
  98 + BR_PROPERTY(QString, inputVariable, "Label")
  99 + BR_PROPERTY(QString, outputVariable, "KNN")
96 100  
97 101 TemplateList gallery;
98 102  
... ... @@ -111,17 +115,17 @@ class KNNTransform : public Transform
111 115 QHash<QString, float> votes;
112 116 const int max = (k < 1) ? sortedScores.size() : std::min(k, sortedScores.size());
113 117 for (int j=0; j<max; j++)
114   - votes[gallery[sortedScores[j].second].file.get<QString>("Subject")] += (weighted ? sortedScores[j].first : 1);
  118 + votes[gallery[sortedScores[j].second].file.get<QString>(inputVariable)] += (weighted ? sortedScores[j].first : 1);
115 119 subjects.append(votes.keys()[votes.values().indexOf(Common::Max(votes.values()))]);
116 120  
117 121 // Remove subject from consideration
118 122 if (subjects.size() < numSubjects)
119 123 for (int j=sortedScores.size()-1; j>=0; j--)
120   - if (gallery[sortedScores[j].second].file.get<QString>("Subject") == subjects.last())
  124 + if (gallery[sortedScores[j].second].file.get<QString>(inputVariable) == subjects.last())
121 125 sortedScores.removeAt(j);
122 126 }
123 127  
124   - dst.file.set("KNN", subjects.size() > 1 ? "[" + subjects.join(",") + "]" : subjects.first());
  128 + dst.file.set(outputVariable, subjects.size() > 1 ? "[" + subjects.join(",") + "]" : subjects.first());
125 129 }
126 130  
127 131 void store(QDataStream &stream) const
... ...
openbr/plugins/eigen3.cpp
... ... @@ -59,21 +59,6 @@ public:
59 59 PCATransform() : keep(0.95), drop(0), whiten(false) {}
60 60  
61 61 private:
62   - /*
63   - void backProject(const Template &src, Template &dst) const
64   - {
65   - const cv::Mat &m = src;
66   - dst = cv::Mat(originalRows, m.rows*m.cols/originalRows, CV_32FC1);
67   -
68   - // Map Eigen into OpenCV
69   - Eigen::Map<const Eigen::MatrixXf> inMap(m.ptr<float>(), keep, 1);
70   - Eigen::Map<Eigen::MatrixXf> outMap(dst.m().ptr<float>(), m.rows*m.cols, 1);
71   -
72   - // Do projection
73   - outMap = (eVecs * inMap) + mean;
74   - }
75   - */
76   -
77 62 double residualReconstructionError(const Template &src) const
78 63 {
79 64 Template proj;
... ... @@ -318,10 +303,12 @@ class LDATransform : public Transform
318 303 Q_PROPERTY(bool pcaWhiten READ get_pcaWhiten WRITE set_pcaWhiten RESET reset_pcaWhiten STORED false)
319 304 Q_PROPERTY(int directLDA READ get_directLDA WRITE set_directLDA RESET reset_directLDA STORED false)
320 305 Q_PROPERTY(float directDrop READ get_directDrop WRITE set_directDrop RESET reset_directDrop STORED false)
  306 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
321 307 BR_PROPERTY(float, pcaKeep, 0.98)
322 308 BR_PROPERTY(bool, pcaWhiten, false)
323 309 BR_PROPERTY(int, directLDA, 0)
324 310 BR_PROPERTY(float, directDrop, 0.1)
  311 + BR_PROPERTY(QString, inputVariable, "Label")
325 312  
326 313 int dimsOut;
327 314 Eigen::VectorXf mean;
... ... @@ -330,7 +317,7 @@ class LDATransform : public Transform
330 317 void train(const TemplateList &_trainingSet)
331 318 {
332 319 // creates "Label"
333   - TemplateList trainingSet = TemplateList::relabel(_trainingSet, "Subject");
  320 + TemplateList trainingSet = TemplateList::relabel(_trainingSet, inputVariable);
334 321  
335 322 int instances = trainingSet.size();
336 323  
... ...
openbr/plugins/eyes.cpp
... ... @@ -186,7 +186,6 @@ private:
186 186 //dst.file.appendPoint(QPointF(second_eye_x, second_eye_y));
187 187 dst.file.set("First_Eye", QPointF(first_eye_x, first_eye_y));
188 188 dst.file.set("Second_Eye", QPointF(second_eye_x, second_eye_y));
189   - dst.file.set("Face", QRect(roi.x, roi.y, roi.width, roi.height));
190 189 }
191 190 };
192 191  
... ...
openbr/plugins/gallery.cpp
... ... @@ -71,7 +71,7 @@ class arffGallery : public Gallery
71 71 }
72 72  
73 73 arffFile.write(qPrintable(OpenCVUtils::matrixToStringList(t).join(',')));
74   - arffFile.write(qPrintable(",'" + t.file.get<QString>("Subject") + "'\n"));
  74 + arffFile.write(qPrintable(",'" + t.file.get<QString>("Label") + "'\n"));
75 75 }
76 76 };
77 77  
... ... @@ -643,11 +643,16 @@ class dbGallery : public Gallery
643 643 query = query.mid(1, query.size()-2);
644 644 if (!q.exec(query))
645 645 qFatal("%s.", qPrintable(q.lastError().text()));
  646 +
646 647 if ((q.record().count() == 0) || (q.record().count() > 3))
647 648 qFatal("Query record expected one to three fields, got %d.", q.record().count());
648 649 const bool hasMetadata = (q.record().count() >= 2);
649 650 const bool hasFilter = (q.record().count() >= 3);
650 651  
  652 + QString labelName = "Label";
  653 + if (q.record().count() >= 2)
  654 + labelName = q.record().fieldName(1);
  655 +
651 656 // subset = seed:subjectMaxSize:numSubjects:subjectMinSize or
652 657 // subset = seed:{Metadata,...,Metadata}:numSubjects
653 658 int seed = 0, subjectMaxSize = std::numeric_limits<int>::max(), numSubjects = std::numeric_limits<int>::max(), subjectMinSize = 0;
... ... @@ -673,6 +678,7 @@ class dbGallery : public Gallery
673 678 QHash<QString, QList<Entry> > entries; // QHash<Label, QList<Entry> >
674 679 while (q.next()) {
675 680 if (hasFilter && (seed >= 0) && (qHash(q.value(2).toString()) % 2 != (uint)seed % 2)) continue; // Ensures training and testing filters don't overlap
  681 +
676 682 if (metadataFields.isEmpty())
677 683 entries[hasMetadata ? q.value(1).toString() : ""].append(QPair<QString,QString>(q.value(0).toString(), hasFilter ? q.value(2).toString() : ""));
678 684 else
... ... @@ -707,8 +713,10 @@ class dbGallery : public Gallery
707 713  
708 714 if (entryList.size() > subjectMaxSize)
709 715 std::random_shuffle(entryList.begin(), entryList.end());
710   - foreach (const Entry &entry, entryList.mid(0, subjectMaxSize))
711   - templates.append(File(entry.first, label));
  716 + foreach (const Entry &entry, entryList.mid(0, subjectMaxSize)) {
  717 + templates.append(File(entry.first));
  718 + templates.last().file.set(labelName, label);
  719 + }
712 720 numSubjects--;
713 721 }
714 722 }
... ... @@ -816,7 +824,7 @@ class statGallery : public Gallery
816 824  
817 825 void write(const Template &t)
818 826 {
819   - subjects.insert(t.file.get<QString>("Subject"));
  827 + subjects.insert(t.file.get<QString>("Label"));
820 828 bytes.append(t.bytes());
821 829 }
822 830 };
... ...
openbr/plugins/gui.cpp
... ... @@ -52,26 +52,33 @@ QImage toQImage(const Mat &amp;mat)
52 52 return QImage(mat8uc3.data, mat8uc3.cols, mat8uc3.rows, 3*mat8uc3.cols, QImage::Format_RGB888).copy();
53 53 }
54 54  
55   -
56   -// Provides slots for manipulating a QLabel, but does not inherit from QWidget.
57   -// Therefore, it can be moved to the main thread if not created there initially
58   -// since god forbid you create a QWidget subclass in not the main thread.
59   -class GUIProxy : public QObject
  55 +class DisplayWindow : public QLabel
60 56 {
61 57 Q_OBJECT
  58 +
  59 +protected:
62 60 QMutex lock;
63 61 QWaitCondition wait;
  62 + QPixmap pixmap;
64 63  
65 64 public:
66 65  
67   - QLabel *window;
68   - QPixmap pixmap;
  66 + DisplayWindow(QWidget * parent = NULL) : QLabel(parent)
  67 + {
  68 + QApplication::instance()->installEventFilter(this);
  69 + }
69 70  
70   - GUIProxy()
  71 +public slots:
  72 + void showImage(const QPixmap & input)
71 73 {
72   - window = NULL;
  74 + pixmap = input;
  75 +
  76 + show();
  77 + setPixmap(pixmap);
  78 + setFixedSize(input.size());
73 79 }
74 80  
  81 +
75 82 bool eventFilter(QObject * obj, QEvent * event)
76 83 {
77 84 if (event->type() == QEvent::KeyPress)
... ... @@ -92,41 +99,10 @@ public:
92 99  
93 100 return QList<QPointF>();
94 101 }
95   -
96   -public slots:
97   -
98   - void showImage(const QPixmap & input)
99   - {
100   - pixmap = input;
101   -
102   - window->show();
103   - window->setPixmap(pixmap);
104   - window->setFixedSize(input.size());
105   - }
106   -
107   - void createWindow()
108   - {
109   - delete window;
110   - QApplication::instance()->removeEventFilter(this);
111   -
112   - window = new QLabel();
113   - window->setVisible(true);
114   -
115   - QApplication::instance()->installEventFilter(this);
116   - Qt::WindowFlags flags = window->windowFlags();
117   -
118   - flags = flags & ~Qt::WindowCloseButtonHint;
119   - window->setWindowFlags(flags);
120   - }
121   -
122 102 };
123 103  
124   -class LandmarkProxy : public GUIProxy
  104 +class PointMarkingWindow : public DisplayWindow
125 105 {
126   - Q_OBJECT
127   -
128   -public:
129   -
130 106 bool eventFilter(QObject *obj, QEvent *event)
131 107 {
132 108 if (event->type() == QEvent::MouseButtonPress)
... ... @@ -144,11 +120,11 @@ public:
144 120 painter.setBrush(Qt::red);
145 121 foreach(const QPointF &point, points) painter.drawEllipse(point, 4, 4);
146 122  
147   - window->setPixmap(pixmapBuffer);
  123 + setPixmap(pixmapBuffer);
148 124  
149 125 return true;
150 126 } else {
151   - return GUIProxy::eventFilter(obj, event);
  127 + return DisplayWindow::eventFilter(obj, event);
152 128 }
153 129 }
154 130  
... ... @@ -156,14 +132,85 @@ public:
156 132 {
157 133 points.clear();
158 134  
159   - GUIProxy::waitForKey();
  135 + DisplayWindow::waitForKey();
160 136  
161 137 return points;
162 138 }
163 139  
164 140 private:
165   -
166 141 QList<QPointF> points;
  142 +
  143 +
  144 +};
  145 +
  146 +
  147 +// I want a template class that doesn't look like a template class
  148 +class NominalCreation
  149 +{
  150 +public:
  151 + virtual ~NominalCreation() {}
  152 + virtual void creation()=0;
  153 +};
  154 +
  155 +// Putting the template on a subclass means we can maintain a pointer that
  156 +// doesn't include T in its type.
  157 +template<typename T>
  158 +class ActualCreation : public NominalCreation
  159 +{
  160 +public:
  161 + T * basis;
  162 +
  163 + void creation()
  164 + {
  165 + basis = new T();
  166 + }
  167 +};
  168 +
  169 +// We want to create a QLabel subclass on the main thread, but are running in another thread.
  170 +// We cannot move QWidget subclasses to a different thread (obviously that would be crazy), but
  171 +// we can create one of these, and move it to the main thread, and then use it to create the object
  172 +// we want.
  173 +// Additional fact: QObject subclasses cannot be template classes.
  174 +class MainThreadCreator : public QObject
  175 +{
  176 + Q_OBJECT
  177 +public:
  178 +
  179 + MainThreadCreator()
  180 + {
  181 + this->moveToThread(QApplication::instance()->thread());
  182 +
  183 + connect(this, SIGNAL(needCreation()), this, SLOT(createThing()), Qt::BlockingQueuedConnection);
  184 + }
  185 +
  186 + // While this cannot be a template class, it can still have a template method.
  187 + template<typename T>
  188 + T * getItem()
  189 + {
  190 + if (QThread::currentThread() == QApplication::instance()->thread())
  191 + return new T();
  192 +
  193 + ActualCreation<T> * actualWorker;
  194 + actualWorker = new ActualCreation<T> ();
  195 + worker = actualWorker;
  196 +
  197 + emit needCreation();
  198 +
  199 + T * output = actualWorker->basis;
  200 + delete actualWorker;
  201 + return output;
  202 + }
  203 +
  204 + NominalCreation * worker;
  205 +
  206 +signals:
  207 + void needCreation();
  208 +
  209 +public slots:
  210 + void createThing()
  211 + {
  212 + worker->creation();
  213 + }
167 214 };
168 215  
169 216 /*!
... ... @@ -184,14 +231,14 @@ public:
184 231  
185 232 ShowTransform() : TimeVaryingTransform(false, false)
186 233 {
187   - gui = NULL;
188 234 displayBuffer = NULL;
  235 + window = NULL;
189 236 }
190 237  
191 238 ~ShowTransform()
192 239 {
193   - delete gui;
194 240 delete displayBuffer;
  241 + delete window;
195 242 }
196 243  
197 244 void train(const TemplateList &data) { (void) data; }
... ... @@ -235,7 +282,7 @@ public:
235 282  
236 283 // Blocking wait for a key-press
237 284 if (this->waitInput)
238   - gui->waitForKey();
  285 + window->waitForKey();
239 286  
240 287 }
241 288 }
... ... @@ -249,33 +296,36 @@ public:
249 296  
250 297 void init()
251 298 {
  299 + initActual<DisplayWindow>();
  300 + }
  301 +
  302 + template<typename WindowType>
  303 + void initActual()
  304 + {
252 305 if (!Globals->useGui)
253 306 return;
254 307  
  308 + if (displayBuffer)
  309 + delete displayBuffer;
255 310 displayBuffer = new QPixmap();
256 311  
257   - // Create our GUI proxy
258   - gui = new GUIProxy();
259   - // Move it to the main thread, this means signals we send to it will
260   - // be run in the main thread, which is hopefully in an event loop
261   - gui->moveToThread(QApplication::instance()->thread());
262   -
263   - // Connect our signals to the proxy's slots
264   - connect(this, SIGNAL(needWindow()), gui, SLOT(createWindow()), Qt::BlockingQueuedConnection);
265   - connect(this, SIGNAL(updateImage(QPixmap)), gui,SLOT(showImage(QPixmap)));
  312 + if (window)
  313 + delete window;
266 314  
267   - emit needWindow();
268   - connect(this, SIGNAL(changeTitle(QString)), gui->window, SLOT(setWindowTitle(QString)));
269   - connect(this, SIGNAL(hideWindow()), gui->window, SLOT(hide()));
  315 + window = creator.getItem<WindowType>();
  316 + // Connect our signals to the window's slots
  317 + connect(this, SIGNAL(updateImage(QPixmap)), window,SLOT(showImage(QPixmap)));
  318 + connect(this, SIGNAL(changeTitle(QString)), window, SLOT(setWindowTitle(QString)));
  319 + connect(this, SIGNAL(hideWindow()), window, SLOT(hide()));
270 320 }
271 321  
272 322 protected:
273   - GUIProxy * gui;
  323 + MainThreadCreator creator;
  324 + DisplayWindow * window;
274 325 QImage qImageBuffer;
275 326 QPixmap * displayBuffer;
276 327  
277 328 signals:
278   - void needWindow();
279 329 void updateImage(const QPixmap & input);
280 330 void changeTitle(const QString & input);
281 331 void hideWindow();
... ... @@ -312,7 +362,7 @@ public:
312 362  
313 363 // Blocking wait for a key-press
314 364 if (this->waitInput) {
315   - QList<QPointF> points = gui->waitForKey();
  365 + QList<QPointF> points = window->waitForKey();
316 366 if (keys.isEmpty()) dst[i].file.appendPoints(points);
317 367 else {
318 368 if (keys.size() == points.size())
... ... @@ -326,24 +376,7 @@ public:
326 376  
327 377 void init()
328 378 {
329   - if (!Globals->useGui)
330   - return;
331   -
332   - displayBuffer = new QPixmap();
333   -
334   - // Create our GUI proxy
335   - gui = new LandmarkProxy();
336   -
337   - // Move it to the main thread, this means signals we send to it will
338   - // be run in the main thread, which is hopefully in an event loop
339   - gui->moveToThread(QApplication::instance()->thread());
340   -
341   - // Connect our signals to the proxy's slots
342   - connect(this, SIGNAL(needWindow()), gui, SLOT(createWindow()), Qt::BlockingQueuedConnection);
343   - connect(this, SIGNAL(updateImage(QPixmap)), gui,SLOT(showImage(QPixmap)));
344   -
345   - emit needWindow();
346   - connect(this, SIGNAL(hideWindow()), gui->window, SLOT(hide()));
  379 + initActual<PointMarkingWindow>();
347 380 }
348 381 };
349 382  
... ...
openbr/plugins/hist.cpp
... ... @@ -52,8 +52,11 @@ class HistTransform : public UntrainableTransform
52 52 int histSize[] = {dims};
53 53 float range[] = {min, max};
54 54 const float* ranges[] = {range};
55   - Mat hist;
56   - calcHist(&mv[i], 1, channels, Mat(), hist, 1, histSize, ranges);
  55 + Mat hist, chan = mv[i];
  56 + // calcHist requires F or U, might as well convert just in case
  57 + if (mv[i].depth() != CV_8U || mv[i].depth() == CV_32F)
  58 + mv[i].convertTo(chan, CV_32F);
  59 + calcHist(&chan, 1, channels, Mat(), hist, 1, histSize, ranges);
57 60 memcpy(m.ptr(i), hist.ptr(), dims * sizeof(float));
58 61 }
59 62  
... ...
openbr/plugins/independent.cpp
... ... @@ -9,37 +9,36 @@ using namespace cv;
9 9 namespace br
10 10 {
11 11  
12   -static TemplateList Downsample(const TemplateList &templates, const Transform *transform)
  12 +static TemplateList Downsample(const TemplateList &templates, int classes, int instances, float fraction, const QString & inputVariable)
13 13 {
14 14 // Return early when no downsampling is required
15   - if ((transform->classes == std::numeric_limits<int>::max()) &&
16   - (transform->instances == std::numeric_limits<int>::max()) &&
17   - (transform->fraction >= 1))
  15 + if ((classes == std::numeric_limits<int>::max()) &&
  16 + (instances == std::numeric_limits<int>::max()) &&
  17 + (fraction >= 1))
18 18 return templates;
19 19  
20   - const bool atLeast = transform->instances < 0;
21   - const int instances = abs(transform->instances);
  20 + const bool atLeast = instances < 0;
  21 + instances = abs(instances);
22 22  
23   - QList<QString> allLabels = File::get<QString>(templates, "Subject");
  23 + QList<QString> allLabels = File::get<QString>(templates, inputVariable);
24 24 QList<QString> uniqueLabels = allLabels.toSet().toList();
25 25 qSort(uniqueLabels);
26 26  
27   - QMap<QString,int> counts = templates.countValues<QString>("Subject", instances != std::numeric_limits<int>::max());
  27 + QMap<QString,int> counts = templates.countValues<QString>(inputVariable, instances != std::numeric_limits<int>::max());
28 28  
29   - if ((instances != std::numeric_limits<int>::max()) && (transform->classes != std::numeric_limits<int>::max()))
  29 + if ((instances != std::numeric_limits<int>::max()) && (classes != std::numeric_limits<int>::max()))
30 30 foreach (const QString & label, counts.keys())
31 31 if (counts[label] < instances)
32 32 counts.remove(label);
33 33  
34 34 uniqueLabels = counts.keys();
35   - if ((transform->classes != std::numeric_limits<int>::max()) && (uniqueLabels.size() < transform->classes))
36   - qWarning("Downsample requested %d classes but only %d are available.", transform->classes, uniqueLabels.size());
  35 + if ((classes != std::numeric_limits<int>::max()) && (uniqueLabels.size() < classes))
  36 + qWarning("Downsample requested %d classes but only %d are available.", classes, uniqueLabels.size());
37 37  
38   - Common::seedRNG();
39 38 QList<QString> selectedLabels = uniqueLabels;
40   - if (transform->classes < uniqueLabels.size()) {
  39 + if (classes < uniqueLabels.size()) {
41 40 std::random_shuffle(selectedLabels.begin(), selectedLabels.end());
42   - selectedLabels = selectedLabels.mid(0, transform->classes);
  41 + selectedLabels = selectedLabels.mid(0, classes);
43 42 }
44 43  
45 44 TemplateList downsample;
... ... @@ -56,14 +55,45 @@ static TemplateList Downsample(const TemplateList &amp;templates, const Transform *t
56 55 downsample.append(templates.value(indices[j]));
57 56 }
58 57  
59   - if (transform->fraction < 1) {
  58 + if (fraction < 1) {
60 59 std::random_shuffle(downsample.begin(), downsample.end());
61   - downsample = downsample.mid(0, downsample.size()*transform->fraction);
  60 + downsample = downsample.mid(0, downsample.size()*fraction);
62 61 }
63 62  
64 63 return downsample;
65 64 }
66 65  
  66 +class DownsampleTrainingTransform : public Transform
  67 +{
  68 + Q_OBJECT
  69 + Q_PROPERTY(br::Transform* transform READ get_transform WRITE set_transform RESET reset_transform STORED true)
  70 + Q_PROPERTY(int classes READ get_classes WRITE set_classes RESET reset_classes STORED false)
  71 + Q_PROPERTY(int instances READ get_instances WRITE set_instances RESET reset_instances STORED false)
  72 + Q_PROPERTY(float fraction READ get_fraction WRITE set_fraction RESET reset_fraction STORED false)
  73 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
  74 + BR_PROPERTY(br::Transform*, transform, NULL)
  75 + BR_PROPERTY(int, classes, std::numeric_limits<int>::max())
  76 + BR_PROPERTY(int, instances, std::numeric_limits<int>::max())
  77 + BR_PROPERTY(float, fraction, 1)
  78 + BR_PROPERTY(QString, inputVariable, "Label")
  79 +
  80 + void project(const Template & src, Template & dst) const
  81 + {
  82 + transform->project(src,dst);
  83 + }
  84 +
  85 +
  86 + void train(const TemplateList &data)
  87 + {
  88 + if (!transform || !transform->trainable)
  89 + return;
  90 +
  91 + TemplateList downsampled = Downsample(data, classes, instances, fraction, inputVariable);
  92 + transform->train(downsampled);
  93 + }
  94 +};
  95 +BR_REGISTER(Transform, DownsampleTrainingTransform)
  96 +
67 97 /*!
68 98 * \ingroup transforms
69 99 * \brief Clones the transform so that it can be applied independently.
... ... @@ -124,13 +154,10 @@ class IndependentTransform : public MetaTransform
124 154 while (transforms.size() < templatesList.size())
125 155 transforms.append(transform->clone());
126 156  
127   - for (int i=0; i<templatesList.size(); i++)
128   - templatesList[i] = Downsample(templatesList[i], transforms[i]);
129   -
130 157 QFutureSynchronizer<void> futures;
131 158 for (int i=0; i<templatesList.size(); i++)
132   - futures.addFuture(QtConcurrent::run(_train, transforms[i], &templatesList[i]));
133   - futures.waitForFinished();
  159 + futures.addFuture(QtConcurrent::run(_train, transforms[i], &templatesList[i]));
  160 + futures.waitForFinished();
134 161 }
135 162  
136 163 void project(const Template &src, Template &dst) const
... ...
openbr/plugins/keypoint.cpp
... ... @@ -15,6 +15,7 @@
15 15 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
16 16  
17 17 #include <opencv2/features2d/features2d.hpp>
  18 +#include <opencv2/objdetect/objdetect.hpp>
18 19 #include <opencv2/nonfree/nonfree.hpp>
19 20 #include "openbr_internal.h"
20 21 #include "openbr/core/opencvutils.h"
... ... @@ -178,6 +179,33 @@ BR_REGISTER(Transform, SIFTDescriptorTransform)
178 179  
179 180 /*!
180 181 * \ingroup transforms
  182 + * \brief OpenCV HOGDescriptor wrapper
  183 + * \author Austin Blanton \cite imaus10
  184 + */
  185 +class HoGDescriptorTransform : public UntrainableTransform
  186 +{
  187 + Q_OBJECT
  188 +
  189 + HOGDescriptor hog;
  190 +
  191 + void project(const Template &src, Template &dst) const
  192 + {
  193 + std::vector<float> descriptorVals;
  194 + std::vector<Point> locations;
  195 + Size winStride = Size(0,0);
  196 + Size padding = Size(0,0);
  197 + foreach (const Mat &rect, src) {
  198 + hog.compute(rect, descriptorVals, winStride, padding, locations);
  199 + Mat HoGFeats(descriptorVals, true);
  200 + dst += HoGFeats;
  201 + }
  202 + }
  203 +};
  204 +
  205 +BR_REGISTER(Transform, HoGDescriptorTransform)
  206 +
  207 +/*!
  208 + * \ingroup transforms
181 209 * \brief Add landmarks to the template in a grid layout
182 210 * \author Josh Klontz \cite jklontz
183 211 */
... ...
openbr/plugins/mask.cpp
... ... @@ -158,6 +158,9 @@ class LargestConvexAreaTransform : public UntrainableTransform
158 158 {
159 159 Q_OBJECT
160 160  
  161 + Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false)
  162 + BR_PROPERTY(QString, outputVariable, "Label")
  163 +
161 164 void project(const Template &src, Template &dst) const
162 165 {
163 166 std::vector< std::vector<Point> > contours;
... ... @@ -171,7 +174,7 @@ class LargestConvexAreaTransform : public UntrainableTransform
171 174 if (area / hullArea > 0.98)
172 175 maxArea = std::max(maxArea, area);
173 176 }
174   - dst.file.set("Label", maxArea);
  177 + dst.file.set(outputVariable, maxArea);
175 178 }
176 179 };
177 180  
... ...
openbr/plugins/meta.cpp
... ... @@ -105,6 +105,24 @@ class PipeTransform : public CompositeTransform
105 105 transforms[i]->train(copy);
106 106 }
107 107  
  108 + // if the transform is time varying, we can't project it in parallel
  109 + if (transforms[i]->timeVarying()) {
  110 + fprintf(stderr, "\n%s projecting...", qPrintable(transforms[i]->objectName()));
  111 + for (int j=0; j < singleItemLists.size();j++)
  112 + transforms[i]->projectUpdate(singleItemLists[j], singleItemLists[j]);
  113 +
  114 + // advance i since we already projected for this stage.
  115 + i++;
  116 +
  117 + // set up copy again
  118 + copy.clear();
  119 + for (int j=0; j < singleItemLists.size(); j++)
  120 + copy.append(singleItemLists[j]);
  121 +
  122 + // the next stage might be trainable, so continue to evaluate it.
  123 + continue;
  124 + }
  125 +
108 126 // We project through any subsequent untrainable transforms at once
109 127 // as a memory optimization in case any of these intermediate
110 128 // transforms allocate a lot of memory (like OpenTransform)
... ... @@ -112,7 +130,8 @@ class PipeTransform : public CompositeTransform
112 130 // by that transform at once if we can avoid it.
113 131 int nextTrainableTransform = i+1;
114 132 while ((nextTrainableTransform < transforms.size()) &&
115   - !transforms[nextTrainableTransform]->trainable)
  133 + !transforms[nextTrainableTransform]->trainable &&
  134 + !transforms[nextTrainableTransform]->timeVarying())
116 135 nextTrainableTransform++;
117 136  
118 137 fprintf(stderr, " projecting...");
... ... @@ -129,26 +148,6 @@ class PipeTransform : public CompositeTransform
129 148 }
130 149 }
131 150  
132   - void backProject(const Template &dst, Template &src) const
133   - {
134   - // Backprojecting a time-varying transform is probably not going to work.
135   - if (timeVarying()) qFatal("No backProject defined for time-varying transform");
136   -
137   - src = dst;
138   - // Reverse order in which transforms are processed
139   - int length = transforms.length();
140   - for (int i=length-1; i>=0; i--) {
141   - Transform *f = transforms.at(i);
142   - try {
143   - src >> *f;
144   - } catch (...) {
145   - qWarning("Exception triggered when processing %s with transform %s", qPrintable(dst.file.flat()), qPrintable(f->objectName()));
146   - src = Template(src.file);
147   - src.file.set("FTE", true);
148   - }
149   - }
150   - }
151   -
152 151 void projectUpdate(const Template &src, Template &dst)
153 152 {
154 153 dst = src;
... ... @@ -306,8 +305,6 @@ class ForkTransform : public CompositeTransform
306 305 futures.waitForFinished();
307 306 }
308 307  
309   - void backProject(const Template &dst, Template &src) const {Transform::backProject(dst, src);}
310   -
311 308 // same as _project, but calls projectUpdate on sub-transforms
312 309 void projectupdate(const Template & src, Template & dst)
313 310 {
... ... @@ -645,17 +642,28 @@ public:
645 642 QList<TemplateList> input_buffer;
646 643 input_buffer.reserve(src.size());
647 644  
  645 + QFutureSynchronizer<void> futures;
  646 +
648 647 for (int i =0; i < src.size();i++) {
649 648 input_buffer.append(TemplateList());
650 649 output_buffer.append(TemplateList());
651 650 }
652   -
653   - QFutureSynchronizer<void> futures;
  651 + QList<QFuture<void> > temp;
  652 + temp.reserve(src.size());
654 653 for (int i=0; i<src.size(); i++) {
655 654 input_buffer[i].append(src[i]);
656   - if (Globals->parallelism) futures.addFuture(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i]));
657   - else _projectList( transform, &input_buffer[i], &output_buffer[i]);
  655 +
  656 + if (Globals->parallelism > 1) temp.append(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i]));
  657 + else _projectList(transform, &input_buffer[i], &output_buffer[i]);
658 658 }
  659 + // We add the futures in reverse order, since in Qt 5.1 at least the
  660 + // waiting thread will wait on them in the order added (which for uniform priority
  661 + // threads is the order of execution), and we want the waiting thread to go in the opposite order
  662 + // so that it can steal runnables and do something besides wait.
  663 + for (int i = temp.size() - 1; i >= 0; i--) {
  664 + futures.addFuture(temp[i]);
  665 + }
  666 +
659 667 futures.waitForFinished();
660 668  
661 669 for (int i=0; i<src.size(); i++) dst.append(output_buffer[i]);
... ...
openbr/plugins/misc.cpp
... ... @@ -179,6 +179,10 @@ class FirstTransform : public UntrainableMetaTransform
179 179  
180 180 void project(const Template &src, Template &dst) const
181 181 {
  182 + // AggregateFrames will leave the Template empty
  183 + // if it hasn't filled up the buffer
  184 + // so we gotta anticipate an empty Template
  185 + if (src.empty()) return;
182 186 dst.file = src.file;
183 187 dst = src.m();
184 188 }
... ...
openbr/plugins/normalize.cpp
... ... @@ -97,6 +97,7 @@ class CenterTransform : public Transform
97 97 Q_OBJECT
98 98 Q_ENUMS(Method)
99 99 Q_PROPERTY(Method method READ get_method WRITE set_method RESET reset_method STORED false)
  100 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
100 101  
101 102 public:
102 103 /*!< */
... ... @@ -107,6 +108,7 @@ public:
107 108  
108 109 private:
109 110 BR_PROPERTY(Method, method, Mean)
  111 + BR_PROPERTY(QString, inputVariable, "Label")
110 112  
111 113 Mat a, b; // dst = (src - b) / a
112 114  
... ... @@ -127,7 +129,7 @@ private:
127 129 Mat m;
128 130 OpenCVUtils::toMat(data.data()).convertTo(m, CV_64F);
129 131  
130   - const QList<int> labels = data.indexProperty("Subject");
  132 + const QList<int> labels = data.indexProperty(inputVariable);
131 133 const int dims = m.cols;
132 134  
133 135 vector<Mat> mv, av, bv;
... ...
openbr/plugins/openbr_internal.h
... ... @@ -170,14 +170,8 @@ public:
170 170 virtual void project(const TemplateList &src, TemplateList &dst) const
171 171 {
172 172 if (timeVarying()) {
173   - if (!this->timeInvariantAlias) {
174   - QMutexLocker lock(&aliasLock);
175   - CompositeTransform * non_const = const_cast<CompositeTransform *>(this);
176   - non_const->timeInvariantAlias = non_const->smartCopy();
177   - non_const->timeInvariantAlias->setParent(non_const);
178   - lock.unlock();
179   - }
180   - timeInvariantAlias->projectUpdate(src,dst);
  173 + CompositeTransform * non_const = const_cast<CompositeTransform *>(this);
  174 + non_const->projectUpdate(src,dst);
181 175 return;
182 176 }
183 177 _project(src, dst);
... ... @@ -225,10 +219,6 @@ public:
225 219 }
226 220  
227 221 output->file = this->file;
228   - output->classes = classes;
229   - output->instances = instances;
230   - output->fraction = fraction;
231   -
232 222 output->init();
233 223  
234 224 return output;
... ... @@ -237,13 +227,10 @@ public:
237 227 protected:
238 228 bool isTimeVarying;
239 229  
240   - mutable QMutex aliasLock;
241   - Transform * timeInvariantAlias;
242   -
243 230 virtual void _project(const Template & src, Template & dst) const = 0;
244 231 virtual void _project(const TemplateList & src, TemplateList & dst) const = 0;
245 232  
246   - CompositeTransform() : TimeVaryingTransform(false) { timeInvariantAlias = NULL; }
  233 + CompositeTransform() : TimeVaryingTransform(false) {}
247 234 };
248 235  
249 236 }
... ...
openbr/plugins/opticalflow.cpp 0 โ†’ 100644
  1 +#include <opencv2/video/tracking.hpp>
  2 +#include "openbr_internal.h"
  3 +#include "openbr/core/opencvutils.h"
  4 +
  5 +using namespace cv;
  6 +
  7 +namespace br
  8 +{
  9 +
  10 +/*!
  11 + * \ingroup transforms
  12 + * \brief Gets a one-channel dense optical flow from two images
  13 + * \author Austin Blanton \cite imaus10
  14 + */
  15 +class OpticalFlowTransform : public UntrainableMetaTransform
  16 +{
  17 + Q_OBJECT
  18 + Q_PROPERTY(double pyr_scale READ get_pyr_scale WRITE set_pyr_scale RESET reset_pyr_scale STORED false)
  19 + Q_PROPERTY(int levels READ get_levels WRITE set_levels RESET reset_levels STORED false)
  20 + Q_PROPERTY(int winsize READ get_winsize WRITE set_winsize RESET reset_winsize STORED false)
  21 + Q_PROPERTY(int iterations READ get_iterations WRITE set_iterations RESET reset_iterations STORED false)
  22 + Q_PROPERTY(int poly_n READ get_poly_n WRITE set_poly_n RESET reset_poly_n STORED false)
  23 + Q_PROPERTY(double poly_sigma READ get_poly_sigma WRITE set_poly_sigma RESET reset_poly_sigma STORED false)
  24 + Q_PROPERTY(int flags READ get_flags WRITE set_flags RESET reset_flags STORED false)
  25 + // these defaults are optimized for KTH
  26 + BR_PROPERTY(double, pyr_scale, 0.1)
  27 + BR_PROPERTY(int, levels, 1)
  28 + BR_PROPERTY(int, winsize, 5)
  29 + BR_PROPERTY(int, iterations, 10)
  30 + BR_PROPERTY(int, poly_n, 7)
  31 + BR_PROPERTY(double, poly_sigma, 1.1)
  32 + BR_PROPERTY(int, flags, 0)
  33 +
  34 + void project(const Template &src, Template &dst) const
  35 + {
  36 + // get the two images put there by AggregateFrames
  37 + if (src.size() < 2) return;
  38 + Mat prevImg = src[0], nextImg = src[1], flow, flowOneCh;
  39 + if (src[0].channels() != 1) OpenCVUtils::cvtGray(src[0], prevImg);
  40 + if (src[1].channels() != 1) OpenCVUtils::cvtGray(src[1], nextImg);
  41 + calcOpticalFlowFarneback(prevImg, nextImg, flow, pyr_scale, levels, winsize, iterations, poly_n, poly_sigma, flags);
  42 +
  43 + // the result is two channels
  44 + std::vector<Mat> channels(2);
  45 + split(flow, channels);
  46 + magnitude(channels[0], channels[1], flowOneCh);
  47 +
  48 + dst += flowOneCh;
  49 + dst.file = src.file;
  50 + }
  51 +};
  52 +
  53 +BR_REGISTER(Transform, OpticalFlowTransform)
  54 +
  55 +} // namespace br
  56 +
  57 +#include "opticalflow.moc"
... ...
openbr/plugins/output.cpp
... ... @@ -146,8 +146,8 @@ class meltOutput : public MatrixOutput
146 146 QStringList lines;
147 147 if (file.baseName() != "terminal") lines.append(QString("Query,Target,Mask,Similarity%1").arg(keys));
148 148  
149   - QList<QString> queryLabels = File::get<QString>(queryFiles, "Subject");
150   - QList<QString> targetLabels = File::get<QString>(targetFiles, "Subject");
  149 + QList<QString> queryLabels = File::get<QString>(queryFiles, "Label");
  150 + QList<QString> targetLabels = File::get<QString>(targetFiles, "Label");
151 151  
152 152 for (int i=0; i<queryFiles.size(); i++) {
153 153 for (int j=(selfSimilar ? i+1 : 0); j<targetFiles.size(); j++) {
... ... @@ -300,7 +300,7 @@ class txtOutput : public MatrixOutput
300 300 if (file.isNull() || targetFiles.isEmpty() || queryFiles.isEmpty()) return;
301 301 QStringList lines;
302 302 foreach (const File &file, queryFiles)
303   - lines.append(file.name + " " + file.get<QString>("Subject"));
  303 + lines.append(file.name + " " + file.get<QString>("Label"));
304 304 QtUtils::writeFile(file, lines);
305 305 }
306 306 };
... ... @@ -433,7 +433,7 @@ class rankOutput : public MatrixOutput
433 433 foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) {
434 434 if (Globals->crossValidate > 0 ? (targetFiles[pair.second].get<int>("Partition",-1) == queryFiles[i].get<int>("Partition",-1)) : true) {
435 435 if (QString(targetFiles[pair.second]) != QString(queryFiles[i])) {
436   - if (targetFiles[pair.second].get<QString>("Subject") == queryFiles[i].get<QString>("Subject")) {
  436 + if (targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) {
437 437 ranks.append(rank);
438 438 positions.append(pair.second);
439 439 scores.append(pair.first);
... ...
openbr/plugins/quality.cpp
... ... @@ -19,17 +19,20 @@ class ImpostorUniquenessMeasureTransform : public Transform
19 19 Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false)
20 20 Q_PROPERTY(double mean READ get_mean WRITE set_mean RESET reset_mean)
21 21 Q_PROPERTY(double stddev READ get_stddev WRITE set_stddev RESET reset_stddev)
  22 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
22 23 BR_PROPERTY(br::Distance*, distance, Distance::make("Dist(L2)", this))
23 24 BR_PROPERTY(double, mean, 0)
24 25 BR_PROPERTY(double, stddev, 1)
  26 + BR_PROPERTY(QString, inputVariable, "Label")
  27 +
25 28 TemplateList impostors;
26 29  
27 30 float calculateIUM(const Template &probe, const TemplateList &gallery) const
28 31 {
29   - const QString probeLabel = probe.file.get<QString>("Subject");
  32 + const QString probeLabel = probe.file.get<QString>(inputVariable);
30 33 TemplateList subset = gallery;
31 34 for (int j=subset.size()-1; j>=0; j--)
32   - if (subset[j].file.get<QString>("Subject") == probeLabel)
  35 + if (subset[j].file.get<QString>(inputVariable) == probeLabel)
33 36 subset.removeAt(j);
34 37  
35 38 QList<float> scores = distance->compare(subset, probe);
... ... @@ -151,6 +154,7 @@ class MatchProbabilityDistance : public Distance
151 154 Q_PROPERTY(br::Distance* distance READ get_distance WRITE set_distance RESET reset_distance STORED false)
152 155 Q_PROPERTY(bool gaussian READ get_gaussian WRITE set_gaussian RESET reset_gaussian STORED false)
153 156 Q_PROPERTY(bool crossModality READ get_crossModality WRITE set_crossModality RESET reset_crossModality STORED false)
  157 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
154 158  
155 159 MP mp;
156 160  
... ... @@ -158,7 +162,7 @@ class MatchProbabilityDistance : public Distance
158 162 {
159 163 distance->train(src);
160 164  
161   - const QList<int> labels = src.indexProperty("Subject");
  165 + const QList<int> labels = src.indexProperty(inputVariable);
162 166 QScopedPointer<MatrixOutput> matrixOutput(MatrixOutput::make(FileList(src.size()), FileList(src.size())));
163 167 distance->compare(src, src, matrixOutput.data());
164 168  
... ... @@ -201,6 +205,7 @@ protected:
201 205 BR_PROPERTY(br::Distance*, distance, make("Dist(L2)"))
202 206 BR_PROPERTY(bool, gaussian, true)
203 207 BR_PROPERTY(bool, crossModality, false)
  208 + BR_PROPERTY(QString, inputVariable, "Label")
204 209 };
205 210  
206 211 BR_REGISTER(Distance, MatchProbabilityDistance)
... ... @@ -217,10 +222,12 @@ class HeatMapDistance : public Distance
217 222 Q_PROPERTY(bool gaussian READ get_gaussian WRITE set_gaussian RESET reset_gaussian STORED false)
218 223 Q_PROPERTY(bool crossModality READ get_crossModality WRITE set_crossModality RESET reset_crossModality STORED false)
219 224 Q_PROPERTY(int step READ get_step WRITE set_step RESET reset_step STORED false)
  225 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
220 226 BR_PROPERTY(br::Distance*, distance, make("Dist(L2)"))
221 227 BR_PROPERTY(bool, gaussian, true)
222 228 BR_PROPERTY(bool, crossModality, false)
223 229 BR_PROPERTY(int, step, 1)
  230 + BR_PROPERTY(QString, inputVariable, "Label")
224 231  
225 232 QList<MP> mp;
226 233  
... ... @@ -228,7 +235,7 @@ class HeatMapDistance : public Distance
228 235 {
229 236 distance->train(src);
230 237  
231   - const QList<int> labels = src.indexProperty("Subject");
  238 + const QList<int> labels = src.indexProperty(inputVariable);
232 239  
233 240 QList<TemplateList> patches;
234 241  
... ... @@ -307,14 +314,16 @@ class UnitDistance : public Distance
307 314 Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance)
308 315 Q_PROPERTY(float a READ get_a WRITE set_a RESET reset_a)
309 316 Q_PROPERTY(float b READ get_b WRITE set_b RESET reset_b)
  317 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
310 318 BR_PROPERTY(br::Distance*, distance, make("Dist(L2)"))
311 319 BR_PROPERTY(float, a, 1)
312 320 BR_PROPERTY(float, b, 0)
  321 + BR_PROPERTY(QString, inputVariable, "Label")
313 322  
314 323 void train(const TemplateList &templates)
315 324 {
316 325 const TemplateList samples = templates.mid(0, 2000);
317   - const QList<int> sampleLabels = samples.indexProperty("Subject");
  326 + const QList<int> sampleLabels = samples.indexProperty(inputVariable);
318 327 QScopedPointer<MatrixOutput> matrixOutput(MatrixOutput::make(FileList(samples.size()), FileList(samples.size())));
319 328 Distance::compare(samples, samples, matrixOutput.data());
320 329  
... ...
openbr/plugins/quantize.cpp
... ... @@ -120,6 +120,10 @@ BR_REGISTER(Transform, HistEqQuantizationTransform)
120 120 class BayesianQuantizationDistance : public Distance
121 121 {
122 122 Q_OBJECT
  123 +
  124 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
  125 + BR_PROPERTY(QString, inputVariable, "Label")
  126 +
123 127 QVector<float> loglikelihoods;
124 128  
125 129 static void computeLogLikelihood(const Mat &data, const QList<int> &labels, float *loglikelihood)
... ... @@ -150,7 +154,7 @@ class BayesianQuantizationDistance : public Distance
150 154 qFatal("Expected sigle matrix templates of type CV_8UC1!");
151 155  
152 156 const Mat data = OpenCVUtils::toMat(src.data());
153   - const QList<int> templateLabels = src.indexProperty("Subject");
  157 + const QList<int> templateLabels = src.indexProperty(inputVariable);
154 158 loglikelihoods = QVector<float>(data.cols*256, 0);
155 159  
156 160 QFutureSynchronizer<void> futures;
... ... @@ -343,9 +347,11 @@ class ProductQuantizationTransform : public Transform
343 347 Q_PROPERTY(int n READ get_n WRITE set_n RESET reset_n STORED false)
344 348 Q_PROPERTY(br::Distance *distance READ get_distance WRITE set_distance RESET reset_distance STORED false)
345 349 Q_PROPERTY(bool bayesian READ get_bayesian WRITE set_bayesian RESET reset_bayesian STORED false)
  350 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
346 351 BR_PROPERTY(int, n, 2)
347 352 BR_PROPERTY(br::Distance*, distance, Distance::make("L2", this))
348 353 BR_PROPERTY(bool, bayesian, false)
  354 + BR_PROPERTY(QString, inputVariable, "Label")
349 355  
350 356 quint16 index;
351 357 QList<Mat> centers;
... ... @@ -474,7 +480,7 @@ private:
474 480 Mat data = OpenCVUtils::toMat(src.data());
475 481 const int step = getStep(data.cols);
476 482  
477   - const QList<int> labels = src.indexProperty("Subject");
  483 + const QList<int> labels = src.indexProperty(inputVariable);
478 484  
479 485 Mat &lut = ProductQuantizationLUTs[index];
480 486 lut = Mat(getDims(data.cols), 256*(256+1)/2, CV_32FC1);
... ...
openbr/plugins/quantize2.cpp
... ... @@ -19,6 +19,10 @@ namespace br
19 19 class BayesianQuantizationTransform : public Transform
20 20 {
21 21 Q_OBJECT
  22 +
  23 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
  24 + BR_PROPERTY(QString, inputVariable, "Label")
  25 +
22 26 QVector<float> thresholds;
23 27  
24 28 static void computeThresholdsRecursive(const QVector<int> &cumulativeGenuines, const QVector<int> &cumulativeImpostors,
... ... @@ -77,7 +81,7 @@ class BayesianQuantizationTransform : public Transform
77 81 void train(const TemplateList &src)
78 82 {
79 83 const Mat data = OpenCVUtils::toMat(src.data());
80   - const QList<int> labels = src.indexProperty("Subject");
  84 + const QList<int> labels = src.indexProperty(inputVariable);
81 85  
82 86 thresholds = QVector<float>(256*data.cols);
83 87  
... ...
openbr/plugins/regions.cpp
... ... @@ -114,7 +114,7 @@ BR_REGISTER(Transform, CatTransform)
114 114 /*!
115 115 * \ingroup transforms
116 116 * \brief Concatenates all input matrices by row into a single matrix.
117   - * All matricies must have the same row counts.
  117 + * All matricies must have the same column counts.
118 118 * \author Josh Klontz \cite jklontz
119 119 */
120 120 class CatRowsTransform : public UntrainableMetaTransform
... ... @@ -131,6 +131,31 @@ BR_REGISTER(Transform, CatRowsTransform)
131 131  
132 132 /*!
133 133 * \ingroup transforms
  134 + * \brief Concatenates all input matrices by column into a single matrix.
  135 + * Use after a fork to concatenate two feature matrices by column.
  136 + * \author Austin Blanton \cite imaus10
  137 + */
  138 +class CatColsTransform : public UntrainableMetaTransform
  139 +{
  140 + Q_OBJECT
  141 +
  142 + void project(const Template &src, Template &dst) const
  143 + {
  144 + if (src.empty()) return;
  145 + dst.file = src.file;
  146 + Mat m = OpenCVUtils::toMatByRow(src);
  147 + // right now this just splits src in half and joins them horizontally
  148 + // TODO: add partitions parameter for more than a single split
  149 + Mat first = m.rowRange(Range(0, m.rows/2));
  150 + Mat second = m.rowRange(Range(m.rows/2, m.rows));
  151 + hconcat(first, second, dst);
  152 + }
  153 +};
  154 +
  155 +BR_REGISTER(Transform, CatColsTransform)
  156 +
  157 +/*!
  158 + * \ingroup transforms
134 159 * \brief Reshape the each matrix to the specified number of rows.
135 160 * \author Josh Klontz \cite jklontz
136 161 */
... ...
openbr/plugins/stream.cpp
... ... @@ -31,8 +31,10 @@ public:
31 31 virtual ~SharedBuffer() {}
32 32  
33 33 virtual void addItem(FrameData * input)=0;
  34 + virtual void reset()=0;
34 35  
35 36 virtual FrameData * tryGetItem()=0;
  37 + virtual int size()=0;
36 38 };
37 39  
38 40 // for n - 1 boundaries, multiple threads call addItem, the frames are
... ... @@ -74,6 +76,21 @@ public:
74 76 return output;
75 77 }
76 78  
  79 + virtual int size()
  80 + {
  81 + QMutexLocker lock(&bufferGuard);
  82 + return buffer.size();
  83 + }
  84 + virtual void reset()
  85 + {
  86 + if (size() != 0)
  87 + qDebug("Sequencing buffer has non-zero size during reset!");
  88 +
  89 + QMutexLocker lock(&bufferGuard);
  90 + next_target = 0;
  91 + }
  92 +
  93 +
77 94 private:
78 95 QMutex bufferGuard;
79 96 int next_target;
... ... @@ -95,6 +112,11 @@ public:
95 112 outputBuffer = &buffer2;
96 113 }
97 114  
  115 + int size()
  116 + {
  117 + QReadLocker readLock(&bufferGuard);
  118 + return inputBuffer->size() + outputBuffer->size();
  119 + }
98 120  
99 121 // called from the producer thread
100 122 void addItem(FrameData * input)
... ... @@ -133,6 +155,13 @@ public:
133 155 return output;
134 156 }
135 157  
  158 + virtual void reset()
  159 + {
  160 + if (this->size() != 0)
  161 + qDebug("Shared buffer has non-zero size during reset!");
  162 + }
  163 +
  164 +
136 165 private:
137 166 // The read-write lock. The thread adding to this buffer can add
138 167 // to the current input buffer if it has a read lock. The thread
... ... @@ -160,9 +189,8 @@ class DataSource
160 189 public:
161 190 DataSource(int maxFrames=500)
162 191 {
  192 + // The sequence number of the last frame
163 193 final_frame = -1;
164   - last_issued = -2;
165   - last_received = -3;
166 194 for (int i=0; i < maxFrames;i++)
167 195 {
168 196 allFrames.addItem(new FrameData());
... ... @@ -181,52 +209,67 @@ public:
181 209 }
182 210  
183 211 // non-blocking version of getFrame
184   - FrameData * tryGetFrame()
  212 + // Returns a NULL FrameData if too many frames are out, or the
  213 + // data source is broken. Sets last_frame to true iff the FrameData
  214 + // returned is the last valid frame, and the data source is now broken.
  215 + FrameData * tryGetFrame(bool & last_frame)
185 216 {
  217 + last_frame = false;
  218 +
  219 + if (is_broken) {
  220 + return NULL;
  221 + }
  222 +
  223 + // Try to get a FrameData from the pool, if we can't it means too many
  224 + // frames are already out, and we will return NULL to indicate failure
186 225 FrameData * aFrame = allFrames.tryGetItem();
187 226 if (aFrame == NULL)
188 227 return NULL;
189 228  
190   - aFrame->data.clear();
191   - aFrame->sequenceNumber = -1;
192   -
  229 + // Try to actually read a frame, if this returns false the data source is broken
193 230 bool res = getNext(*aFrame);
194 231  
195   - // The datasource broke.
196   - if (!res) {
197   - allFrames.addItem(aFrame);
198   -
  232 + // The datasource broke, update final_frame
  233 + if (!res)
  234 + {
199 235 QMutexLocker lock(&last_frame_update);
200   - // Did we already receive the last frame?
201   - final_frame = last_issued;
  236 + final_frame = lookAhead.back()->sequenceNumber;
  237 + allFrames.addItem(aFrame);
  238 + }
  239 + else {
  240 + lookAhead.push_back(aFrame);
  241 + }
202 242  
203   - // We got the last frame before the data source broke,
204   - // better pulse lastReturned
205   - if (final_frame == last_received) {
206   - lastReturned.wakeAll();
207   - }
208   - else if (final_frame < last_received)
209   - std::cout << "Bad last frame " << final_frame << " but received " << last_received << std::endl;
  243 + // we will return the first frame on the lookAhead buffer
  244 + FrameData * rVal = lookAhead.first();
  245 + lookAhead.pop_front();
210 246  
211   - return NULL;
  247 + // If this is the last frame, say so
  248 + if (rVal->sequenceNumber == final_frame) {
  249 + last_frame = true;
  250 + is_broken = true;
212 251 }
213   - last_issued = aFrame->sequenceNumber;
214   - return aFrame;
  252 +
  253 + return rVal;
215 254 }
216 255  
217   - // Returns true if the frame returned was the last
  256 + // Return a frame to the pool, returns true if the frame returned was the last
218 257 // frame issued, false otherwise
219 258 bool returnFrame(FrameData * inputFrame)
220 259 {
  260 + int frameNumber = inputFrame->sequenceNumber;
  261 +
  262 + inputFrame->data.clear();
  263 + inputFrame->sequenceNumber = -1;
221 264 allFrames.addItem(inputFrame);
222 265  
223 266 bool rval = false;
224 267  
225 268 QMutexLocker lock(&last_frame_update);
226   - last_received = inputFrame->sequenceNumber;
227 269  
228   - if (inputFrame->sequenceNumber == final_frame) {
  270 + if (frameNumber == final_frame) {
229 271 // We just received the last frame, better pulse
  272 + allReturned = true;
230 273 lastReturned.wakeAll();
231 274 rval = true;
232 275 }
... ... @@ -234,23 +277,88 @@ public:
234 277 return rval;
235 278 }
236 279  
237   - void waitLast()
  280 + bool waitLast()
238 281 {
239 282 QMutexLocker lock(&last_frame_update);
240   - lastReturned.wait(&last_frame_update);
  283 +
  284 + while (!allReturned)
  285 + {
  286 + // This would be a safer wait if we used a timeout, but
  287 + // theoretically that should never matter.
  288 + lastReturned.wait(&last_frame_update);
  289 + }
  290 + return true;
241 291 }
242 292  
243   - virtual void close() = 0;
244   - virtual bool open(Template & output, int start_index=0) = 0;
245   - virtual bool isOpen() = 0;
  293 + bool open(Template & output, int start_index = 0)
  294 + {
  295 + is_broken = false;
  296 + allReturned = false;
  297 +
  298 + // The last frame isn't initialized yet
  299 + final_frame = -1;
  300 + // Start our sequence numbers from the input index
  301 + next_sequence_number = start_index;
  302 +
  303 + // Actually open the data source
  304 + bool open_res = concreteOpen(output);
  305 +
  306 + // We couldn't open the data source
  307 + if (!open_res) {
  308 + is_broken = true;
  309 + return false;
  310 + }
  311 +
  312 + // Try to get a frame from the global pool
  313 + FrameData * firstFrame = allFrames.tryGetItem();
  314 +
  315 + // If this fails, things have gone pretty badly.
  316 + if (firstFrame == NULL) {
  317 + is_broken = true;
  318 + return false;
  319 + }
  320 +
  321 + // Read a frame from the video source
  322 + bool res = getNext(*firstFrame);
246 323  
  324 + // the data source broke already, we couldn't even get one frame
  325 + // from it even though it claimed to have opened successfully.
  326 + if (!res) {
  327 + is_broken = true;
  328 + return false;
  329 + }
  330 +
  331 + // We read one frame ahead of the last one returned, this allows
  332 + // us to know which frame is the final frame when we return it.
  333 + lookAhead.append(firstFrame);
  334 + return true;
  335 + }
  336 +
  337 + /*
  338 + * Pure virtual methods
  339 + */
  340 +
  341 + // isOpen doesn't appear to particularly work when used on opencv
  342 + // VideoCaptures, so we don't use it for anything important.
  343 + virtual bool isOpen()=0;
  344 + // Called from open, open the data source specified by the input
  345 + // template, don't worry about setting any of the state variables
  346 + // set in open.
  347 + virtual bool concreteOpen(Template & output) = 0;
  348 + // Get the next frame from the data source, store the results in
  349 + // FrameData (including the actual frame and appropriate sequence
  350 + // number).
247 351 virtual bool getNext(FrameData & input) = 0;
  352 + // close the currently open data source.
  353 + virtual void close() = 0;
248 354  
  355 + int next_sequence_number;
249 356 protected:
250 357 DoubleBuffer allFrames;
251 358 int final_frame;
252   - int last_issued;
253   - int last_received;
  359 + bool is_broken;
  360 + bool allReturned;
  361 + QList<FrameData *> lookAhead;
254 362  
255 363 QWaitCondition lastReturned;
256 364 QMutex last_frame_update;
... ... @@ -262,14 +370,14 @@ class VideoDataSource : public DataSource
262 370 public:
263 371 VideoDataSource(int maxFrames) : DataSource(maxFrames) {}
264 372  
265   - bool open(Template &input, int start_index=0)
  373 + bool concreteOpen(Template &input)
266 374 {
267   - final_frame = -1;
268   - last_issued = -2;
269   - last_received = -3;
270   -
271   - next_idx = start_index;
272 375 basis = input;
  376 +
  377 + // We can open either files (well actually this includes addresses of ip cameras
  378 + // through ffmpeg), or webcams. Webcam VideoCaptures are created through a separate
  379 + // overload of open that takes an integer, not a string.
  380 + // So, does this look like an integer?
273 381 bool is_int = false;
274 382 int anInt = input.file.name.toInt(&is_int);
275 383 if (is_int)
... ... @@ -287,7 +395,8 @@ public:
287 395 } else {
288 396 // Yes, we should specify absolute path:
289 397 // http://stackoverflow.com/questions/9396459/loading-a-video-in-opencv-in-python
290   - video.open(QFileInfo(input.file.name).absoluteFilePath().toStdString());
  398 + QString fileName = (Globals->path.isEmpty() ? "" : Globals->path + "/") + input.file.name;
  399 + video.open(QFileInfo(fileName).absoluteFilePath().toStdString());
291 400 }
292 401  
293 402 return video.isOpened();
... ... @@ -302,29 +411,38 @@ public:
302 411 private:
303 412 bool getNext(FrameData & output)
304 413 {
305   - if (!isOpen())
  414 + if (!isOpen()) {
  415 + qDebug("video source is not open");
306 416 return false;
  417 + }
307 418  
308 419 output.data.append(Template(basis.file));
309   - output.data.last().append(cv::Mat());
  420 + output.data.last().m() = cv::Mat();
310 421  
311   - output.sequenceNumber = next_idx;
312   - next_idx++;
  422 + output.sequenceNumber = next_sequence_number;
  423 + next_sequence_number++;
313 424  
314   - bool res = video.read(output.data.last().last());
315   - output.data.last().last() = output.data.last().last().clone();
  425 + cv::Mat temp;
  426 + bool res = video.read(temp);
316 427  
317 428 if (!res) {
  429 + // The video capture broke, return false.
  430 + output.data.last().m() = cv::Mat();
318 431 close();
319 432 return false;
320 433 }
  434 +
  435 + // This clone is critical, if we don't do it then the matrix will
  436 + // be an alias of an internal buffer of the video source, leading
  437 + // to various problems later.
  438 + output.data.last().m() = temp.clone();
  439 +
321 440 output.data.last().file.set("FrameNumber", output.sequenceNumber);
322 441 return true;
323 442 }
324 443  
325 444 cv::VideoCapture video;
326 445 Template basis;
327   - int next_idx;
328 446 };
329 447  
330 448 // Given a template as input, return its matrices one by one on subsequent calls
... ... @@ -334,21 +452,18 @@ class TemplateDataSource : public DataSource
334 452 public:
335 453 TemplateDataSource(int maxFrames) : DataSource(maxFrames)
336 454 {
337   - current_idx = INT_MAX;
  455 + current_matrix_idx = INT_MAX;
338 456 data_ok = false;
339 457 }
340   - bool data_ok;
341 458  
342   - bool open(Template &input, int start_index=0)
  459 + // To "open" it we just set appropriate indices, we assume that if this
  460 + // is an image, it is already loaded into memory.
  461 + bool concreteOpen(Template &input)
343 462 {
344 463 basis = input;
345   - current_idx = 0;
346   - next_sequence = start_index;
347   - final_frame = -1;
348   - last_issued = -2;
349   - last_received = -3;
  464 + current_matrix_idx = 0;
350 465  
351   - data_ok = current_idx < basis.size();
  466 + data_ok = current_matrix_idx < basis.size();
352 467 return data_ok;
353 468 }
354 469  
... ... @@ -358,39 +473,41 @@ public:
358 473  
359 474 void close()
360 475 {
361   - current_idx = INT_MAX;
  476 + current_matrix_idx = INT_MAX;
362 477 basis.clear();
363 478 }
364 479  
365 480 private:
366 481 bool getNext(FrameData & output)
367 482 {
368   - data_ok = current_idx < basis.size();
  483 + data_ok = current_matrix_idx < basis.size();
369 484 if (!data_ok)
370 485 return false;
371 486  
372   - output.data.append(basis[current_idx]);
373   - current_idx++;
  487 + output.data.append(basis[current_matrix_idx]);
  488 + current_matrix_idx++;
374 489  
375   - output.sequenceNumber = next_sequence;
376   - next_sequence++;
  490 + output.sequenceNumber = next_sequence_number;
  491 + next_sequence_number++;
377 492  
378 493 output.data.last().file.set("FrameNumber", output.sequenceNumber);
379 494 return true;
380 495 }
381 496  
382 497 Template basis;
383   - int current_idx;
384   - int next_sequence;
  498 + // Index of the next matrix to output from the template
  499 + int current_matrix_idx;
  500 +
  501 + // is current_matrix_idx in bounds?
  502 + bool data_ok;
385 503 };
386 504  
387   -// Given a template as input, create a VideoDataSource or a TemplateDataSource
388   -// depending on whether or not it looks like the input template has already
389   -// loaded frames into memory.
  505 +// Given a templatelist as input, create appropriate data source for each
  506 +// individual template
390 507 class DataSourceManager : public DataSource
391 508 {
392 509 public:
393   - DataSourceManager()
  510 + DataSourceManager(int activeFrames=100) : DataSource(activeFrames)
394 511 {
395 512 actualSource = NULL;
396 513 }
... ... @@ -400,6 +517,11 @@ public:
400 517 close();
401 518 }
402 519  
  520 + int size()
  521 + {
  522 + return this->allFrames.size();
  523 + }
  524 +
403 525 void close()
404 526 {
405 527 if (actualSource) {
... ... @@ -409,33 +531,40 @@ public:
409 531 }
410 532 }
411 533  
  534 + // We are used through a call to open(TemplateList)
412 535 bool open(TemplateList & input)
413 536 {
414   - currentIdx = 0;
  537 + // Set up variables specific to us
  538 + current_template_idx = 0;
415 539 templates = input;
416 540  
417   - return open(templates[currentIdx]);
  541 + // Call datasourece::open on the first template to set up
  542 + // state variables
  543 + return DataSource::open(templates[current_template_idx]);
418 544 }
419 545  
420   - bool open(Template & input, int start_index=0)
  546 + // Create an actual data source of appropriate type for this template
  547 + // (initially called via the call to DataSource::open, called later
  548 + // as we run out of frames on our templates).
  549 + bool concreteOpen(Template & input)
421 550 {
422 551 close();
423   - final_frame = -1;
424   - last_issued = -2;
425   - last_received = -3;
426   - next_frame = start_index;
427 552  
  553 + bool open_res = false;
428 554 // Input has no matrices? Its probably a video that hasn't been loaded yet
429 555 if (input.empty()) {
430 556 actualSource = new VideoDataSource(0);
431   - actualSource->open(input, next_frame);
  557 + open_res = actualSource->concreteOpen(input);
432 558 }
  559 + // If the input is not empty, we assume it is a set of frames already
  560 + // in memory.
433 561 else {
434   - // create frame dealer
435 562 actualSource = new TemplateDataSource(0);
436   - actualSource->open(input, next_frame);
  563 + open_res = actualSource->concreteOpen(input);
437 564 }
438   - if (!isOpen()) {
  565 +
  566 + // The data source failed to open
  567 + if (!open_res) {
439 568 delete actualSource;
440 569 actualSource = NULL;
441 570 return false;
... ... @@ -446,30 +575,55 @@ public:
446 575 bool isOpen() { return !actualSource ? false : actualSource->isOpen(); }
447 576  
448 577 protected:
449   - int currentIdx;
450   - int next_frame;
  578 + // Index of the template in the templatelist we are currently reading from
  579 + int current_template_idx;
  580 +
451 581 TemplateList templates;
452 582 DataSource * actualSource;
  583 + // Get the next frame, if we run out of frames on the current template
  584 + // move on to the next one.
453 585 bool getNext(FrameData & output)
454 586 {
455 587 bool res = actualSource->getNext(output);
  588 + output.sequenceNumber = next_sequence_number;
  589 +
  590 + // OK we got a frame
456 591 if (res) {
457   - next_frame = output.sequenceNumber+1;
  592 + // Override the sequence number set by actualSource
  593 + output.data.last().file.set("FrameNumber", output.sequenceNumber);
  594 + next_sequence_number++;
  595 + if (output.data.last().last().empty())
  596 + qDebug("broken matrix");
458 597 return true;
459 598 }
460 599  
  600 + // We didn't get a frame, try to move on to the next template.
461 601 while(!res) {
462   - currentIdx++;
  602 + output.data.clear();
  603 + current_template_idx++;
463 604  
464   - if (currentIdx >= templates.size())
  605 + // No more templates? We're done
  606 + if (current_template_idx >= templates.size())
465 607 return false;
466   - bool open_res = open(templates[currentIdx], next_frame);
  608 +
  609 + // open the next data source
  610 + bool open_res = concreteOpen(templates[current_template_idx]);
  611 + // We couldn't open it, give up? We could maybe continue here
  612 + // but don't currently.
467 613 if (!open_res)
468 614 return false;
  615 +
  616 + // get a frame from the newly opened data source, if that fails
  617 + // we continue to open the next one.
469 618 res = actualSource->getNext(output);
470 619 }
  620 + // Finally, set the sequence number for the frame we actually return.
  621 + output.sequenceNumber = next_sequence_number++;
  622 + output.data.last().file.set("FrameNumber", output.sequenceNumber);
  623 +
  624 + if (output.data.last().last().empty())
  625 + qDebug("broken matrix");
471 626  
472   - next_frame = output.sequenceNumber+1;
473 627 return res;
474 628 }
475 629  
... ... @@ -477,9 +631,14 @@ protected:
477 631  
478 632 class ProcessingStage;
479 633  
480   -class BasicLoop : public QRunnable
  634 +class BasicLoop : public QRunnable, public QFutureInterface<void>
481 635 {
482 636 public:
  637 + BasicLoop()
  638 + {
  639 + this->reportStarted();
  640 + }
  641 +
483 642 void run();
484 643  
485 644 QList<ProcessingStage *> * stages;
... ... @@ -506,12 +665,15 @@ public:
506 665  
507 666 virtual void reset()=0;
508 667  
  668 + virtual void status()=0;
  669 +
509 670 protected:
510 671 int thread_count;
511 672  
512 673 SharedBuffer * inputBuffer;
513 674 ProcessingStage * nextStage;
514 675 QList<ProcessingStage *> * stages;
  676 + QThreadPool * threads;
515 677 Transform * transform;
516 678  
517 679 };
... ... @@ -530,6 +692,7 @@ void BasicLoop::run()
530 692 current_idx++;
531 693 current_idx = current_idx % stages->size();
532 694 }
  695 + this->reportFinished();
533 696 }
534 697  
535 698 class MultiThreadStage : public ProcessingStage
... ... @@ -537,7 +700,8 @@ class MultiThreadStage : public ProcessingStage
537 700 public:
538 701 MultiThreadStage(int _input) : ProcessingStage(_input) {}
539 702  
540   -
  703 + // Not much to worry about here, we will project the input
  704 + // and try to continue to the next stage.
541 705 FrameData * run(FrameData * input, bool & should_continue)
542 706 {
543 707 if (input == NULL) {
... ... @@ -551,7 +715,8 @@ public:
551 715 return input;
552 716 }
553 717  
554   - // Called from a different thread than run
  718 + // Called from a different thread than run. Nothing to worry about
  719 + // we offer no restrictions on when loops may enter this stage.
555 720 virtual bool tryAcquireNextStage(FrameData *& input)
556 721 {
557 722 (void) input;
... ... @@ -562,9 +727,11 @@ public:
562 727 {
563 728 // nothing to do.
564 729 }
  730 + void status(){
  731 + qDebug("multi thread stage %d, nothing to worry about", this->stage_id);
  732 + }
565 733 };
566 734  
567   -
568 735 class SingleThreadStage : public ProcessingStage
569 736 {
570 737 public:
... ... @@ -572,13 +739,18 @@ public:
572 739 {
573 740 currentStatus = STOPPING;
574 741 next_target = 0;
  742 + // If the previous stage is single-threaded, queued inputs
  743 + // are stored in a double buffer
575 744 if (input_variance) {
576 745 this->inputBuffer = new DoubleBuffer();
577 746 }
  747 + // If it's multi-threaded we need to put the inputs back in order
  748 + // before we can use them, so we use a sequencing buffer.
578 749 else {
579 750 this->inputBuffer = new SequencingBuffer();
580 751 }
581 752 }
  753 +
582 754 ~SingleThreadStage()
583 755 {
584 756 delete inputBuffer;
... ... @@ -589,6 +761,7 @@ public:
589 761 QWriteLocker writeLock(&statusLock);
590 762 currentStatus = STOPPING;
591 763 next_target = 0;
  764 + inputBuffer->reset();
592 765 }
593 766  
594 767  
... ... @@ -627,18 +800,26 @@ public:
627 800 lock.unlock();
628 801  
629 802 if (newItem)
630   - {
631   - BasicLoop * next = new BasicLoop();
632   - next->stages = stages;
633   - next->start_idx = this->stage_id;
634   - next->startItem = newItem;
635   -
636   - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id);
637   - }
  803 + startThread(newItem);
638 804  
639 805 return input;
640 806 }
641 807  
  808 + void startThread(br::FrameData * newItem)
  809 + {
  810 + BasicLoop * next = new BasicLoop();
  811 + next->stages = stages;
  812 + next->start_idx = this->stage_id;
  813 + next->startItem = newItem;
  814 +
  815 + // We start threads with priority equal to their stage id
  816 + // This is intended to ensure progression, we do queued late stage
  817 + // jobs before queued early stage jobs, and so tend to finish frames
  818 + // rather than go stage by stage. In Qt 5.1, priorities are priorities
  819 + // so we use the stage_id directly.
  820 + this->threads->start(next, stage_id);
  821 + }
  822 +
642 823  
643 824 // Calledfrom a different thread than run.
644 825 bool tryAcquireNextStage(FrameData *& input)
... ... @@ -671,79 +852,109 @@ public:
671 852  
672 853 return true;
673 854 }
  855 +
  856 + void status(){
  857 + qDebug("single thread stage %d, status starting? %d, next %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->inputBuffer->size());
  858 + }
  859 +
674 860 };
675 861  
676   -// No input buffer, instead we draw templates from some data source
677   -// Will be operated by the main thread for the stream
  862 +// This stage reads new frames from the data source.
678 863 class FirstStage : public SingleThreadStage
679 864 {
680 865 public:
681   - FirstStage() : SingleThreadStage(true) {}
  866 + FirstStage(int activeFrames = 100) : SingleThreadStage(true), dataSource(activeFrames){ }
682 867  
683 868 DataSourceManager dataSource;
684 869  
  870 + void reset()
  871 + {
  872 + dataSource.close();
  873 + SingleThreadStage::reset();
  874 + }
  875 +
685 876 FrameData * run(FrameData * input, bool & should_continue)
686 877 {
687   - // Is there anything on our input buffer? If so we should start a thread with that.
688   - QWriteLocker lock(&statusLock);
689   - input = dataSource.tryGetFrame();
690   - // Datasource broke?
691   - if (!input)
692   - {
693   - currentStatus = STOPPING;
694   - should_continue = false;
695   - return NULL;
696   - }
697   - lock.unlock();
  878 + if (input == NULL)
  879 + qFatal("NULL frame in input stage");
698 880  
  881 + // Can we enter the next stage?
699 882 should_continue = nextStage->tryAcquireNextStage(input);
700 883  
701   - BasicLoop * next = new BasicLoop();
702   - next->stages = stages;
703   - next->start_idx = this->stage_id;
704   - next->startItem = NULL;
  884 + // Try to get a frame from the datasource, we keep working on
  885 + // the frame we have, but we will queue another job for the next
  886 + // frame if a frame is currently available.
  887 + QWriteLocker lock(&statusLock);
  888 + bool last_frame = false;
  889 + FrameData * newFrame = dataSource.tryGetFrame(last_frame);
705 890  
706   - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id);
  891 + // Were we able to get a frame?
  892 + if (newFrame) startThread(newFrame);
  893 + // If not this stage will enter a stopped state.
  894 + else {
  895 + currentStatus = STOPPING;
  896 + }
  897 +
  898 + lock.unlock();
707 899  
708 900 return input;
709 901 }
710 902  
711   - // Calledfrom a different thread than run.
  903 + // The last stage, trying to access the first stage
712 904 bool tryAcquireNextStage(FrameData *& input)
713 905 {
  906 + // Return the frame, was it the last one?
714 907 bool was_last = dataSource.returnFrame(input);
715 908 input = NULL;
  909 +
  910 + // OK we won't continue.
716 911 if (was_last) {
717 912 return false;
718 913 }
719 914  
720   - if (!dataSource.isOpen())
721   - return false;
722   -
723 915 QReadLocker lock(&statusLock);
724   - // Thread is already running, we should just return
  916 + // If the first stage is already active we will just end.
725 917 if (currentStatus == STARTING)
726 918 {
727 919 return false;
728 920 }
729   - // Have to change to a write lock to modify currentStatus
  921 +
  922 + // Otherwise we will try to continue, but to do so we have to
  923 + // escalate the lock, and sadly there is no way to do so without
  924 + // releasing the read-mode lock, and getting a new write-mode lock.
730 925 lock.unlock();
731 926  
732 927 QWriteLocker writeLock(&statusLock);
733   - // But someone else might have started a thread in the meantime
  928 + // currentStatus might have changed in the gap between releasing the read
  929 + // lock and getting the write lock.
734 930 if (currentStatus == STARTING)
735 931 {
736 932 return false;
737 933 }
738   - // Ok we'll start a thread
  934 +
  935 + bool last_frame = false;
  936 + // Try to get a frame from the data source, if we get one we will
  937 + // continue to the first stage.
  938 + input = dataSource.tryGetFrame(last_frame);
  939 +
  940 + if (!input) {
  941 + return false;
  942 + }
  943 +
739 944 currentStatus = STARTING;
740 945  
741   - // We always start a readstage thread with null input, so nothing to do here
742 946 return true;
743 947 }
744 948  
  949 + void status(){
  950 + qDebug("Read stage %d, status starting? %d, next frame %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->dataSource.size());
  951 + }
  952 +
  953 +
745 954 };
746 955  
  956 +// Appened to the end of a Stream's transform sequence. Collects the output
  957 +// from each frame on a single templatelist
747 958 class LastStage : public SingleThreadStage
748 959 {
749 960 public:
... ... @@ -756,6 +967,7 @@ public:
756 967 private:
757 968 TemplateList collectedOutput;
758 969 public:
  970 +
759 971 void reset()
760 972 {
761 973 collectedOutput.clear();
... ... @@ -774,11 +986,14 @@ public:
774 986 }
775 987 next_target = input->sequenceNumber + 1;
776 988  
  989 + // add the item to our output buffer
777 990 collectedOutput.append(input->data);
778 991  
  992 + // Can we enter the read stage?
779 993 should_continue = nextStage->tryAcquireNextStage(input);
780 994  
781   - // Is there anything on our input buffer? If so we should start a thread with that.
  995 + // Is there anything on our input buffer? If so we should start a thread
  996 + // in this stage to process that frame.
782 997 QWriteLocker lock(&statusLock);
783 998 FrameData * newItem = inputBuffer->tryGetItem();
784 999 if (!newItem)
... ... @@ -788,23 +1003,25 @@ public:
788 1003 lock.unlock();
789 1004  
790 1005 if (newItem)
791   - {
792   - BasicLoop * next = new BasicLoop();
793   - next->stages = stages;
794   - next->start_idx = this->stage_id;
795   - next->startItem = newItem;
796   -
797   - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id);
798   - }
  1006 + startThread(newItem);
799 1007  
800 1008 return input;
801 1009 }
  1010 +
  1011 + void status(){
  1012 + qDebug("Collection stage %d, status starting? %d, next %d buffer size %d", this->stage_id, this->currentStatus == SingleThreadStage::STARTING, this->next_target, this->inputBuffer->size());
  1013 + }
  1014 +
802 1015 };
803 1016  
  1017 +
804 1018 class StreamTransform : public CompositeTransform
805 1019 {
806 1020 Q_OBJECT
807 1021 public:
  1022 + Q_PROPERTY(int activeFrames READ get_activeFrames WRITE set_activeFrames RESET reset_activeFrames)
  1023 + BR_PROPERTY(int, activeFrames, 100)
  1024 +
808 1025 void train(const TemplateList & data)
809 1026 {
810 1027 foreach(Transform * transform, transforms) {
... ... @@ -826,7 +1043,8 @@ public:
826 1043 qFatal("whatever");
827 1044 }
828 1045  
829   - // start processing
  1046 + // start processing, consider all templates in src a continuous
  1047 + // 'video'
830 1048 void projectUpdate(const TemplateList & src, TemplateList & dst)
831 1049 {
832 1050 dst = src;
... ... @@ -834,21 +1052,27 @@ public:
834 1052 bool res = readStage->dataSource.open(dst);
835 1053 if (!res) return;
836 1054  
837   - QThreadPool::globalInstance()->releaseThread();
  1055 + // Start the first thread in the stream.
  1056 + QWriteLocker lock(&readStage->statusLock);
838 1057 readStage->currentStatus = SingleThreadStage::STARTING;
839 1058  
840   - BasicLoop loop;
841   - loop.stages = &this->processingStages;
842   - loop.start_idx = 0;
843   - loop.startItem = NULL;
844   - loop.setAutoDelete(false);
  1059 + // We have to get a frame before starting the thread
  1060 + bool last_frame = false;
  1061 + FrameData * firstFrame = readStage->dataSource.tryGetFrame(last_frame);
  1062 + if (firstFrame == NULL)
  1063 + qFatal("Failed to read first frame of video");
845 1064  
846   - QThreadPool::globalInstance()->start(&loop, processingStages.size() - processingStages[0]->stage_id);
  1065 + readStage->startThread(firstFrame);
  1066 + lock.unlock();
847 1067  
848   - // Wait for the end.
849   - readStage->dataSource.waitLast();
850   - QThreadPool::globalInstance()->reserveThread();
  1068 + // Wait for the stream to process the last frame available from
  1069 + // the data source.
  1070 + bool wait_res = false;
  1071 + wait_res = readStage->dataSource.waitLast();
851 1072  
  1073 + // Now that there are no more incoming frames, call finalize
  1074 + // on each transform in turn to collect any last templates
  1075 + // they wish to issue.
852 1076 TemplateList final_output;
853 1077  
854 1078 // Push finalize through the stages
... ... @@ -864,7 +1088,8 @@ public:
864 1088 final_output.append(output_set);
865 1089 }
866 1090  
867   - // dst is set to all output received by the final stage
  1091 + // dst is set to all output received by the final stage, along
  1092 + // with anything output via the calls to finalize.
868 1093 dst = collectionStage->getOutput();
869 1094 dst.append(final_output);
870 1095  
... ... @@ -876,7 +1101,8 @@ public:
876 1101 virtual void finalize(TemplateList & output)
877 1102 {
878 1103 (void) output;
879   - // Not handling this yet -cao
  1104 + // Nothing in particular to do here, stream calls finalize
  1105 + // on all child transforms as part of projectUpdate
880 1106 }
881 1107  
882 1108 // Create and link stages
... ... @@ -884,26 +1110,49 @@ public:
884 1110 {
885 1111 if (transforms.isEmpty()) return;
886 1112  
  1113 + // call CompositeTransform::init so that trainable is set
  1114 + // correctly.
  1115 + CompositeTransform::init();
  1116 +
  1117 + // We share a thread pool across streams attached to the same
  1118 + // parent tranform, retrieve or create a thread pool based
  1119 + // on our parent transform.
  1120 + QMutexLocker poolLock(&poolsAccess);
  1121 + QHash<QObject *, QThreadPool *>::Iterator it;
  1122 + if (!pools.contains(this->parent())) {
  1123 + it = pools.insert(this->parent(), new QThreadPool(this));
  1124 + it.value()->setMaxThreadCount(Globals->parallelism);
  1125 + }
  1126 + else it = pools.find(this->parent());
  1127 + threads = it.value();
  1128 + poolLock.unlock();
  1129 +
  1130 + // Are our children time varying or not? This decides whether
  1131 + // we run them in single threaded or multi threaded stages
887 1132 stage_variance.reserve(transforms.size());
888 1133 foreach (const br::Transform *transform, transforms) {
889 1134 stage_variance.append(transform->timeVarying());
890 1135 }
891 1136  
892   - readStage = new FirstStage();
  1137 + // Additionally, we have a separate stage responsible for reading
  1138 + // frames from the data source
  1139 + readStage = new FirstStage(activeFrames);
893 1140  
894 1141 processingStages.push_back(readStage);
895 1142 readStage->stage_id = 0;
896 1143 readStage->stages = &this->processingStages;
  1144 + readStage->threads = this->threads;
897 1145  
  1146 + // Initialize and link a processing stage for each of our child
  1147 + // transforms.
898 1148 int next_stage_id = 1;
899   -
900 1149 bool prev_stage_variance = true;
901 1150 for (int i =0; i < transforms.size(); i++)
902 1151 {
903 1152 if (stage_variance[i])
904   - {
  1153 + // Whether or not the previous stage is multi-threaded controls
  1154 + // the type of input buffer we need in a single threaded stage.
905 1155 processingStages.append(new SingleThreadStage(prev_stage_variance));
906   - }
907 1156 else
908 1157 processingStages.append(new MultiThreadStage(Globals->parallelism));
909 1158  
... ... @@ -914,24 +1163,31 @@ public:
914 1163 processingStages[i]->nextStage = processingStages[i+1];
915 1164  
916 1165 processingStages.last()->stages = &this->processingStages;
  1166 + processingStages.last()->threads = this->threads;
917 1167  
918 1168 processingStages.last()->transform = transforms[i];
919 1169 prev_stage_variance = stage_variance[i];
920 1170 }
921 1171  
  1172 + // We also have the last stage, which just puts the output of the
  1173 + // previous stages on a template list.
922 1174 collectionStage = new LastStage(prev_stage_variance);
923 1175 processingStages.append(collectionStage);
924 1176 collectionStage->stage_id = next_stage_id;
925 1177 collectionStage->stages = &this->processingStages;
  1178 + collectionStage->threads = this->threads;
926 1179  
  1180 + // the last transform stage points to collection stage
927 1181 processingStages[processingStages.size() - 2]->nextStage = collectionStage;
928 1182  
929   - // It's a ring buffer, get it?
  1183 + // And the collection stage points to the read stage, because this is
  1184 + // a ring buffer.
930 1185 collectionStage->nextStage = readStage;
931 1186 }
932 1187  
933 1188 ~StreamTransform()
934 1189 {
  1190 + // Delete all the stages
935 1191 for (int i = 0; i < processingStages.size(); i++) {
936 1192 delete processingStages[i];
937 1193 }
... ... @@ -945,6 +1201,25 @@ protected:
945 1201  
946 1202 QList<ProcessingStage *> processingStages;
947 1203  
  1204 + // This is a map from parent transforms (of Streams) to thread pools. Rather
  1205 + // than starting threads on the global thread pool, Stream uses separate thread pools
  1206 + // keyed on their parent transform. This is necessary because stream's project starts
  1207 + // threads, then enters an indefinite wait for them to finish. Since we are starting
  1208 + // threads using thread pools, threads themselves are a limited resource. Therefore,
  1209 + // the type of hold and wait done by stream project can lead to deadlock unless
  1210 + // resources are ordered in such a way that a circular wait will not occur. The points
  1211 + // of this hash is to introduce a resource ordering (on threads) that mirrors the structure
  1212 + // of the algorithm. So, as long as the structure of the algorithm is a DAG, the wait done
  1213 + // by stream project will not be circular, since every thread in stream project is waiting
  1214 + // for threads at a lower level to do the work.
  1215 + // This issue doesn't come up in distribute, since a thread waiting on a QFutureSynchronizer
  1216 + // will steal work from those jobs, so in that sense distribute isn't doing a hold and wait.
  1217 + // Waiting for a QFutureSynchronzier isn't really possible here since stream runs an indeteriminate
  1218 + // number of jobs.
  1219 + static QHash<QObject *, QThreadPool *> pools;
  1220 + static QMutex poolsAccess;
  1221 + QThreadPool * threads;
  1222 +
948 1223 void _project(const Template &src, Template &dst) const
949 1224 {
950 1225 (void) src; (void) dst;
... ... @@ -957,6 +1232,9 @@ protected:
957 1232 }
958 1233 };
959 1234  
  1235 +QHash<QObject *, QThreadPool *> StreamTransform::pools;
  1236 +QMutex StreamTransform::poolsAccess;
  1237 +
960 1238 BR_REGISTER(Transform, StreamTransform)
961 1239  
962 1240  
... ...
openbr/plugins/svm.cpp
... ... @@ -101,6 +101,8 @@ class SVMTransform : public Transform
101 101 Q_PROPERTY(Type type READ get_type WRITE set_type RESET reset_type STORED false)
102 102 Q_PROPERTY(float C READ get_C WRITE set_C RESET reset_C STORED false)
103 103 Q_PROPERTY(float gamma READ get_gamma WRITE set_gamma RESET reset_gamma STORED false)
  104 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
  105 + Q_PROPERTY(QString outputVariable READ get_outputVariable WRITE set_outputVariable RESET reset_outputVariable STORED false)
104 106  
105 107 public:
106 108 enum Kernel { Linear = CvSVM::LINEAR,
... ... @@ -119,6 +121,9 @@ private:
119 121 BR_PROPERTY(Type, type, C_SVC)
120 122 BR_PROPERTY(float, C, -1)
121 123 BR_PROPERTY(float, gamma, -1)
  124 + BR_PROPERTY(QString, inputVariable, "")
  125 + BR_PROPERTY(QString, outputVariable, "")
  126 +
122 127  
123 128 SVM svm;
124 129 QHash<QString, int> labelMap;
... ... @@ -128,14 +133,15 @@ private:
128 133 {
129 134 Mat data = OpenCVUtils::toMat(_data.data());
130 135 Mat lab;
131   - // If we are doing regression, assume subject has float values
  136 + // If we are doing regression, the input variable should have float
  137 + // values
132 138 if (type == EPS_SVR || type == NU_SVR) {
133   - lab = OpenCVUtils::toMat(File::get<float>(_data, "Subject"));
  139 + lab = OpenCVUtils::toMat(File::get<float>(_data, inputVariable));
134 140 }
135   - // If we are doing classification, assume subject has discrete values, map them
136   - // and store the mapping data
  141 + // If we are doing classification, we should be dealing with discrete
  142 + // values. Map them and store the mapping data
137 143 else {
138   - QList<int> dataLabels = _data.indexProperty("Subject", labelMap, reverseLookup);
  144 + QList<int> dataLabels = _data.indexProperty(inputVariable, labelMap, reverseLookup);
139 145 lab = OpenCVUtils::toMat(dataLabels);
140 146 }
141 147 trainSVM(svm, data, lab, kernel, type, C, gamma);
... ... @@ -146,9 +152,9 @@ private:
146 152 dst = src;
147 153 float prediction = svm.predict(src.m().reshape(1, 1));
148 154 if (type == EPS_SVR || type == NU_SVR)
149   - dst.file.set("Subject", prediction);
  155 + dst.file.set(outputVariable, prediction);
150 156 else
151   - dst.file.set("Subject", reverseLookup[prediction]);
  157 + dst.file.set(outputVariable, reverseLookup[prediction]);
152 158 }
153 159  
154 160 void store(QDataStream &stream) const
... ... @@ -162,6 +168,24 @@ private:
162 168 loadSVM(svm, stream);
163 169 stream >> labelMap >> reverseLookup;
164 170 }
  171 +
  172 + void init()
  173 + {
  174 + // Since SVM can do regression or classification, we have to check the problem type before
  175 + // specifying target variable names
  176 + if (inputVariable.isEmpty())
  177 + {
  178 + if (type == EPS_SVR || type == NU_SVR) {
  179 + inputVariable = "Regressor";
  180 + if (outputVariable.isEmpty())
  181 + outputVariable = "Regressand";
  182 + }
  183 + else
  184 + inputVariable = "Label";
  185 + }
  186 + if (outputVariable.isEmpty())
  187 + outputVariable = inputVariable;
  188 + }
165 189 };
166 190  
167 191 BR_REGISTER(Transform, SVMTransform)
... ... @@ -178,6 +202,8 @@ class SVMDistance : public Distance
178 202 Q_ENUMS(Type)
179 203 Q_PROPERTY(Kernel kernel READ get_kernel WRITE set_kernel RESET reset_kernel STORED false)
180 204 Q_PROPERTY(Type type READ get_type WRITE set_type RESET reset_type STORED false)
  205 + Q_PROPERTY(QString inputVariable READ get_inputVariable WRITE set_inputVariable RESET reset_inputVariable STORED false)
  206 +
181 207  
182 208 public:
183 209 enum Kernel { Linear = CvSVM::LINEAR,
... ... @@ -194,13 +220,14 @@ public:
194 220 private:
195 221 BR_PROPERTY(Kernel, kernel, Linear)
196 222 BR_PROPERTY(Type, type, EPS_SVR)
  223 + BR_PROPERTY(QString, inputVariable, "Label")
197 224  
198 225 SVM svm;
199 226  
200 227 void train(const TemplateList &src)
201 228 {
202 229 const Mat data = OpenCVUtils::toMat(src.data());
203   - const QList<int> lab = src.indexProperty("Subject");
  230 + const QList<int> lab = src.indexProperty(inputVariable);
204 231  
205 232 const int instances = data.rows * (data.rows+1) / 2;
206 233 Mat deltaData(instances, data.cols, data.type());
... ...
openbr/plugins/validate.cpp
... ... @@ -159,26 +159,29 @@ class MetadataDistance : public Distance
159 159 float compare(const Template &a, const Template &b) const
160 160 {
161 161 foreach (const QString &key, filters) {
  162 + QString aValue = a.file.get<QString>(key, QString());
  163 + QString bValue = b.file.get<QString>(key, QString());
162 164  
163   - const QString aValue = a.file.get<QString>(key, "");
164   - const QString bValue = b.file.get<QString>(key, "");
  165 + // The query value may be a range. Let's check.
  166 + if (bValue.isEmpty()) bValue = QtUtils::toString(b.file.get<QPointF>(key, QPointF()));
165 167  
166 168 if (aValue.isEmpty() || bValue.isEmpty()) continue;
167 169  
168 170 bool keep = false;
  171 + bool ok;
169 172  
170   - if (aValue[0] == '(') /* Range */ {
171   - QStringList values = aValue.split(',');
  173 + QPointF range = QtUtils::toPoint(bValue,&ok);
172 174  
173   - int value = values[0].mid(1).toInt();
174   - values[1].chop(1);
175   - int upperBound = values[1].toInt();
  175 + if (ok) /* Range */ {
  176 + int value = range.x();
  177 + int upperBound = range.y();
176 178  
177 179 while (value <= upperBound) {
178   - if (aValue == bValue) {
  180 + if (aValue == QString::number(value)) {
179 181 keep = true;
180 182 break;
181 183 }
  184 + value++;
182 185 }
183 186 }
184 187 else if (aValue == bValue) keep = true;
... ...
scripts/downloadDatasets.sh
... ... @@ -64,6 +64,5 @@ if [ ! -d ../data/KTH/vid ]; then
64 64 rm ${vidclass}.zip
65 65 done
66 66 # this file is corrupted
67   - chmod +w ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi
68   - rm ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi
  67 + rm -f ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi
69 68 fi
... ...
scripts/evalAgeRegression-PCSO.sh
... ... @@ -4,8 +4,12 @@ if [ ! -f evalAgeRegression-PCSO.sh ]; then
4 4 exit
5 5 fi
6 6  
  7 +export BR="../build/app/br/br -useGui 0"
  8 +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/
  9 +export ageAlg=AgeRegression
  10 +
7 11 # Create a file list by querying the database
8   -br -quiet -algorithm Identity -enroll "../data/PCSO/PCSO.db[query='SELECT File,Age,PersonID FROM PCSO WHERE Age >= 15 AND AGE <= 75', subset=1:200]" terminal.txt > Input.txt
  12 +$BR -quiet -algorithm Identity -enroll "$PCSO_DIR/PCSO.db[query='SELECT File,Age,PersonID FROM PCSO WHERE Age >= 17 AND AGE <= 68', subset=1:200]" terminal.txt > Input.txt
9 13  
10 14 # Enroll the file list and evaluate performance
11   -br -algorithm AgeRegression -path ../data/PCSO/img -enroll Input.txt Output.txt -evalRegression Output.txt Input.txt
  15 +$BR -algorithm $ageAlg -path $PCSO_DIR/Images -enroll Input.txt Output.txt -evalRegression Output.txt Input.txt Age
... ...
scripts/evalFaceRecognition-MEDS.sh
... ... @@ -20,11 +20,11 @@ if [ ! -e Algorithm_Dataset ]; then
20 20 fi
21 21  
22 22 if [ ! -e MEDS.mask ]; then
23   - br -makeMask ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml MEDS.mask
  23 + br -useGui 0 -makeMask ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml MEDS.mask
24 24 fi
25 25  
26 26 # Run Algorithm on MEDS
27   -br -algorithm ${ALGORITHM} -path ../data/MEDS/img -compare ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml ${ALGORITHM}_MEDS.mtx -eval ${ALGORITHM}_MEDS.mtx MEDS.mask Algorithm_Dataset/${ALGORITHM}_MEDS.csv
  27 +br -useGui 0 -algorithm ${ALGORITHM} -path ../data/MEDS/img -compare ../data/MEDS/sigset/MEDS_frontal_target.xml ../data/MEDS/sigset/MEDS_frontal_query.xml ${ALGORITHM}_MEDS.mtx -eval ${ALGORITHM}_MEDS.mtx MEDS.mask Algorithm_Dataset/${ALGORITHM}_MEDS.csv
28 28  
29 29 # Plot results
30   -br -plot Algorithm_Dataset/*_MEDS.csv MEDS
  30 +br -useGui 0 -plot Algorithm_Dataset/*_MEDS.csv MEDS
... ...
scripts/evalGenderClassification-PCSO.sh
... ... @@ -4,8 +4,13 @@ if [ ! -f evalGenderClassification-PCSO.sh ]; then
4 4 exit
5 5 fi
6 6  
  7 +export BR=../build/app/br/br
  8 +export genderAlg=GenderClassification
  9 +
  10 +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/
  11 +
7 12 # Create a file list by querying the database
8   -br -quiet -algorithm Identity -enroll "../data/PCSO/PCSO.db[query='SELECT File,Gender,PersonID FROM PCSO', subset=1:8000]" terminal.txt > Input.txt
  13 +$BR -useGui 0 -quiet -algorithm Identity -enroll "$PCSO_DIR/PCSO.db[query='SELECT File,Gender,PersonID FROM PCSO', subset=1:8000]" terminal.txt > Input.txt
9 14  
10 15 # Enroll the file list and evaluate performance
11   -br -algorithm GenderClassification -path ../data/PCSO/img -enroll Input.txt Output.txt -evalClassification Output.txt Input.txt
  16 +$BR -useGui 0 -algorithm $genderAlg -path $PCSO_DIR/Images -enroll Input.txt Output.txt -evalClassification Output.txt Input.txt Gender
12 17 \ No newline at end of file
... ...
scripts/trainAgeRegression-PCSO.sh
... ... @@ -6,6 +6,11 @@ fi
6 6  
7 7 #rm -f ../share/openbr/models/features/FaceClassificationRegistration
8 8 #rm -f ../share/openbr/models/features/FaceClassificationExtraction
9   -rm -f ../share/openbr/models/algorithms/AgeRegression
  9 +#rm -f ../share/openbr/models/algorithms/AgeRegression
10 10  
11   -br -algorithm AgeRegression -path ../data/PCSO/Images -train "../data/PCSO/PCSO.db[query='SELECT File,Age,PersonID FROM PCSO WHERE Age >= 15 AND AGE <= 75', subset=0:200]" ../share/openbr/models/algorithms/AgeRegression
  11 +export BR=../build/app/br/br
  12 +export ageAlg=AgeRegression
  13 +
  14 +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/
  15 +
  16 +$BR -useGui 0 -algorithm $ageAlg -path $PCSO_DIR/Images -train "$PCSO_DIR/PCSO.db[query='SELECT File,Age,PersonID FROM PCSO WHERE Age >= 17 AND AGE <= 68', subset=0:200]" ../share/openbr/models/algorithms/AgeRegression
... ...
scripts/trainFaceRecognition-PCSO.sh
... ... @@ -8,6 +8,13 @@ fi
8 8 #rm -f ../share/openbr/models/features/FaceRecognitionExtraction
9 9 #rm -f ../share/openbr/models/features/FaceRecognitionEmbedding
10 10 #rm -f ../share/openbr/models/features/FaceRecognitionQuantization
11   -rm -f ../share/openbr/models/algorithms/FaceRecognition
  11 +#rm -f ../share/openbr/models/algorithms/FaceRecognition
  12 +
  13 +export BR=../build/app/br/br
  14 +
  15 +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/
  16 +
  17 +
  18 +
  19 +$BR -useGui 0 -algorithm FaceRecognition -path "$PCSO_DIR/Images/" -train "$PCSO_DIR/PCSO.db[query='SELECT File,PersonID as Label,PersonID FROM PCSO', subset=0:5:6000]" ../share/openbr/models/algorithms/FaceRecognition
12 20  
13   -br -algorithm FaceRecognition -path ../data/PCSO/img -train "../data/PCSO/PCSO.db[query='SELECT File,'S'||PersonID,PersonID FROM PCSO', subset=0:5:6000]" ../share/openbr/models/algorithms/FaceRecognition
... ...
scripts/trainGenderClassification-PCSO.sh
... ... @@ -6,6 +6,11 @@ fi
6 6  
7 7 #rm -f ../share/openbr/models/features/FaceClassificationRegistration
8 8 #rm -f ../share/openbr/models/features/FaceClassificationExtraction
9   -rm -f ../share/openbr/models/algorithms/GenderClassification
  9 +#rm -f ../share/openbr/models/algorithms/GenderClassification
10 10  
11   -br -algorithm GenderClassification -path ../data/PCSO/Images -train "../data/PCSO/PCSO.db[query='SELECT File,Gender,PersonID FROM PCSO', subset=0:8000]" ../share/openbr/models/algorithms/GenderClassification
  11 +export BR=../build/app/br/br
  12 +export genderAlg=GenderClassification
  13 +
  14 +export PCSO_DIR=/user/pripshare/Databases/FaceDatabases/PCSO/PCSO/
  15 +
  16 +$BR -useGui 0 -algorithm $genderAlg -path $PCSO_DIR/Images -train "$PCSO_DIR/PCSO.db[query='SELECT File,Gender,PersonID FROM PCSO', subset=0:8000]" ../share/openbr/models/algorithms/GenderClassification
... ...
share/openbr/openbr.bib
... ... @@ -34,6 +34,11 @@
34 34 Howpublished = {https://github.com/lbestrowden},
35 35 Title = {bestrow1 at msu.edu}}
36 36  
  37 +@misc{imaus10,
  38 + Author = {Austin Van Blanton},
  39 + Howpublished = {https://github.com/imaus10},
  40 + Title = {imaus10 at gmail.com}}
  41 +
37 42 % Software
38 43 @misc{libface,
39 44 Howpublished = {http://libface.sourceforge.net/file/Home.html},
... ...