Commit 289023d827674ec228b6cdcbcb5e4b46cf7e1fff

Authored by Charles Otto
2 parents a2fc934a 0d7cfeef

Merge branch 'master' of https://github.com/biometrics/openbr into specificity

Resolved conflicts:
	app/br/br.cpp
	openbr/core/bee.cpp
	openbr/plugins/output.cpp
CHANGELOG.md
1 0.4.0 - ??/??/?? 1 0.4.0 - ??/??/??
2 ================ 2 ================
  3 +* Added -evalDetection and -plotDetection for evaluating and plotting object detection accuracy (#9)
3 4
4 0.3.0 - 5/22/13 5 0.3.0 - 5/22/13
5 =============== 6 ===============
app/br/br.cpp
@@ -140,6 +140,9 @@ public: @@ -140,6 +140,9 @@ public:
140 } else if (!strcmp(fun, "evalRegression")) { 140 } else if (!strcmp(fun, "evalRegression")) {
141 check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'."); 141 check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'.");
142 br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : ""); 142 br_eval_regression(parv[0], parv[1], parc >= 3 ? parv[2] : "", parc >= 4 ? parv[3] : "");
  143 + } else if (!strcmp(fun, "plotDetection")) {
  144 + check(parc >= 2, "Incorrect parameter count for 'plotDetection'.");
  145 + br_plot_detection(parc-1, parv, parv[parc-1], true);
143 } else if (!strcmp(fun, "plotMetadata")) { 146 } else if (!strcmp(fun, "plotMetadata")) {
144 check(parc >= 2, "Incorrect parameter count for 'plotMetadata'."); 147 check(parc >= 2, "Incorrect parameter count for 'plotMetadata'.");
145 br_plot_metadata(parc-1, parv, parv[parc-1], true); 148 br_plot_metadata(parc-1, parv, parv[parc-1], true);
@@ -216,6 +219,7 @@ private: @@ -216,6 +219,7 @@ private:
216 "-evalClustering <clusters> <gallery>\n" 219 "-evalClustering <clusters> <gallery>\n"
217 "-evalDetection <predicted_gallery> <truth_gallery> [{csv}]\n" 220 "-evalDetection <predicted_gallery> <truth_gallery> [{csv}]\n"
218 "-evalRegression <predicted_gallery> <truth_gallery>\n" 221 "-evalRegression <predicted_gallery> <truth_gallery>\n"
  222 + "-plotDetection <file> ... <file> {destination}\n"
219 "-plotMetadata <file> ... <file> <columns>\n" 223 "-plotMetadata <file> ... <file> <columns>\n"
220 "-getHeader <matrix>\n" 224 "-getHeader <matrix>\n"
221 "-setHeader {<matrix>} <target_gallery> <query_gallery>\n" 225 "-setHeader {<matrix>} <target_gallery> <query_gallery>\n"
openbr/core/bee.cpp
@@ -102,7 +102,7 @@ void BEE::writeSigset(const QString &amp;sigset, const br::FileList &amp;files, bool ign @@ -102,7 +102,7 @@ void BEE::writeSigset(const QString &amp;sigset, const br::FileList &amp;files, bool ign
102 if ((key == "Index") || (key == "Label")) continue; 102 if ((key == "Index") || (key == "Label")) continue;
103 metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\""); 103 metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\"");
104 } 104 }
105 - lines.append("\t<biometric-signature name=\"" + file.get<QString>("Label") +"\">"); 105 + lines.append("\t<biometric-signature name=\"" + file.get<QString>("Label",file.fileName()) +"\">");
106 lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>"); 106 lines.append("\t\t<presentation file-name=\"" + file.name + "\" " + metadata.join(" ") + "/>");
107 lines.append("\t</biometric-signature>"); 107 lines.append("\t</biometric-signature>");
108 } 108 }
openbr/core/eval.cpp
@@ -387,7 +387,7 @@ static QStringList computeDetectionResults(const QList&lt;ResolvedDetection&gt; &amp;detec @@ -387,7 +387,7 @@ static QStringList computeDetectionResults(const QList&lt;ResolvedDetection&gt; &amp;detec
387 for (int i=0; i<keep; i++) { 387 for (int i=0; i<keep; i++) {
388 const DetectionOperatingPoint &point = points[double(i) / double(keep-1) * double(points.size()-1)]; 388 const DetectionOperatingPoint &point = points[double(i) / double(keep-1) * double(points.size()-1)];
389 lines.append(QString("%1ROC, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.FalsePositives), QString::number(point.Recall))); 389 lines.append(QString("%1ROC, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.FalsePositives), QString::number(point.Recall)));
390 - lines.append(QString("%1PR, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.Precision), QString::number(point.Recall))); 390 + lines.append(QString("%1PR, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.Recall), QString::number(point.Precision)));
391 } 391 }
392 return lines; 392 return lines;
393 } 393 }
openbr/core/plot.cpp
@@ -79,7 +79,7 @@ struct RPlot @@ -79,7 +79,7 @@ struct RPlot
79 79
80 Pivot major, minor; 80 Pivot major, minor;
81 81
82 - RPlot(QStringList files, const br::File &destination, bool isEvalFormat = true) 82 + RPlot(QStringList files, const File &destination, bool isEvalFormat = true)
83 { 83 {
84 if (files.isEmpty()) qFatal("Empty file list."); 84 if (files.isEmpty()) qFatal("Empty file list.");
85 qSort(files.begin(), files.end(), sortFiles); 85 qSort(files.begin(), files.end(), sortFiles);
@@ -214,7 +214,7 @@ struct RPlot @@ -214,7 +214,7 @@ struct RPlot
214 }; 214 };
215 215
216 // Does not work if dataset folder starts with a number 216 // Does not work if dataset folder starts with a number
217 -bool Plot(const QStringList &files, const br::File &destination, bool show) 217 +bool Plot(const QStringList &files, const File &destination, bool show)
218 { 218 {
219 qDebug("Plotting %d file(s) to %s", files.size(), qPrintable(destination)); 219 qDebug("Plotting %d file(s) to %s", files.size(), qPrintable(destination));
220 220
@@ -238,7 +238,6 @@ bool Plot(const QStringList &amp;files, const br::File &amp;destination, bool show) @@ -238,7 +238,6 @@ bool Plot(const QStringList &amp;files, const br::File &amp;destination, bool show)
238 (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) + 238 (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
239 QString(" + scale_x_log10(labels=percent) + scale_y_log10(labels=percent) + annotation_logticks()\n\n"))); 239 QString(" + scale_x_log10(labels=percent) + scale_y_log10(labels=percent) + annotation_logticks()\n\n")));
240 240
241 -  
242 p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") + 241 p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") +
243 QString(", xlab=\"Score%1\"").arg((p.flip ? p.major.size : p.minor.size) > 1 ? " / " + (p.flip ? p.major.header : p.minor.header) : QString()) + 242 QString(", xlab=\"Score%1\"").arg((p.flip ? p.major.size : p.minor.size) > 1 ? " / " + (p.flip ? p.major.header : p.minor.header) : QString()) +
244 QString(", ylab=\"Frequency%1\"").arg((p.flip ? p.minor.size : p.major.size) > 1 ? " / " + (p.flip ? p.minor.header : p.major.header) : QString()) + 243 QString(", ylab=\"Frequency%1\"").arg((p.flip ? p.minor.size : p.major.size) > 1 ? " / " + (p.flip ? p.minor.header : p.major.header) : QString()) +
@@ -272,6 +271,48 @@ bool Plot(const QStringList &amp;files, const br::File &amp;destination, bool show) @@ -272,6 +271,48 @@ bool Plot(const QStringList &amp;files, const br::File &amp;destination, bool show)
272 return p.finalize(show); 271 return p.finalize(show);
273 } 272 }
274 273
  274 +bool PlotDetection(const QStringList &files, const File &destination, bool show)
  275 +{
  276 + qDebug("Plotting %d detection file(s) to %s", files.size(), qPrintable(destination));
  277 +
  278 + RPlot p(files, destination, false);
  279 +
  280 + p.file.write("# Split data into individual plots\n"
  281 + "plot_index = which(names(data)==\"Plot\")\n"
  282 + "DiscreteROC <- data[grep(\"DiscreteROC\",data$Plot),-c(1)]\n"
  283 + "ContinuousROC <- data[grep(\"ContinuousROC\",data$Plot),-c(1)]\n"
  284 + "DiscretePR <- data[grep(\"DiscretePR\",data$Plot),-c(1)]\n"
  285 + "ContinuousPR <- data[grep(\"ContinuousPR\",data$Plot),-c(1)]\n"
  286 + "Overlap <- data[grep(\"Overlap\",data$Plot),-c(1)]\n"
  287 + "rm(data)\n"
  288 + "\n");
  289 +
  290 + foreach (const QString &type, QStringList() << "Discrete" << "Continuous")
  291 + p.file.write(qPrintable(QString("qplot(X, Y, data=%1ROC%2").arg(type, (p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : ", geom=\"line\"") +
  292 + (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
  293 + (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
  294 + QString(", xlab=\"False Accepts\", ylab=\"True Accept Rate\") + theme_minimal()") +
  295 + (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
  296 + (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
  297 + QString(" + scale_x_log10() + scale_y_continuous(labels=percent) + annotation_logticks(sides=\"b\") + ggtitle(\"%1\")\n\n").arg(type)));
  298 +
  299 + foreach (const QString &type, QStringList() << "Discrete" << "Continuous")
  300 + p.file.write(qPrintable(QString("qplot(X, Y, data=%1PR%2").arg(type, (p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : ", geom=\"line\"") +
  301 + (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
  302 + (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
  303 + QString(", xlab=\"Recall\", ylab=\"Precision\") + theme_minimal()") +
  304 + (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
  305 + (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
  306 + QString(" + scale_x_continuous(labels=percent) + scale_y_continuous(labels=percent) + ggtitle(\"%1\")\n\n").arg(type)));
  307 +
  308 + p.file.write(qPrintable(QString("qplot(X, data=Overlap, geom=\"histogram\", position=\"identity\", xlab=\"Overlap\", ylab=\"Frequency\")") +
  309 + QString(" + theme_minimal() + scale_x_continuous(minor_breaks=NULL) + scale_y_continuous(minor_breaks=NULL) + theme(axis.text.y=element_blank(), axis.ticks=element_blank(), axis.text.x=element_text(angle=-90, hjust=0))") +
  310 + (p.major.size > 1 ? (p.minor.size > 1 ? QString(" + facet_grid(%2 ~ %1, scales=\"free\")").arg(p.minor.header, p.major.header) : QString(" + facet_wrap(~ %1, scales = \"free\")").arg(p.major.header)) : QString()) +
  311 + QString(" + theme(aspect.ratio=1)\n\n")));
  312 +
  313 + return p.finalize(show);
  314 +}
  315 +
275 bool PlotMetadata(const QStringList &files, const QString &columns, bool show) 316 bool PlotMetadata(const QStringList &files, const QString &columns, bool show)
276 { 317 {
277 qDebug("Plotting %d metadata file(s) for columns %s", files.size(), qPrintable(columns)); 318 qDebug("Plotting %d metadata file(s) for columns %s", files.size(), qPrintable(columns));
openbr/core/plot.h
@@ -24,7 +24,8 @@ @@ -24,7 +24,8 @@
24 24
25 namespace br 25 namespace br
26 { 26 {
27 - bool Plot(const QStringList &files, const br::File &destination, bool show = false); 27 + bool Plot(const QStringList &files, const File &destination, bool show = false);
  28 + bool PlotDetection(const QStringList &files, const File &destination, bool show = false);
28 bool PlotMetadata(const QStringList &files, const QString &destination, bool show = false); 29 bool PlotMetadata(const QStringList &files, const QString &destination, bool show = false);
29 } 30 }
30 31
openbr/core/resource.h
@@ -24,6 +24,7 @@ @@ -24,6 +24,7 @@
24 #include <QSharedPointer> 24 #include <QSharedPointer>
25 #include <QString> 25 #include <QString>
26 #include <QThread> 26 #include <QThread>
  27 +#include <openbr/openbr_plugin.h>
27 28
28 template <typename T> 29 template <typename T>
29 class ResourceMaker 30 class ResourceMaker
@@ -52,7 +53,7 @@ public: @@ -52,7 +53,7 @@ public:
52 : resourceMaker(rm) 53 : resourceMaker(rm)
53 , availableResources(new QList<T*>()) 54 , availableResources(new QList<T*>())
54 , lock(new QMutex()) 55 , lock(new QMutex())
55 - , totalResources(new QSemaphore(QThread::idealThreadCount())) 56 + , totalResources(new QSemaphore(br::Globals->parallelism))
56 {} 57 {}
57 58
58 ~Resource() 59 ~Resource()
openbr/openbr.cpp
@@ -172,6 +172,11 @@ bool br_plot(int num_files, const char *files[], const char *destination, bool s @@ -172,6 +172,11 @@ bool br_plot(int num_files, const char *files[], const char *destination, bool s
172 return Plot(QtUtils::toStringList(num_files, files), destination, show); 172 return Plot(QtUtils::toStringList(num_files, files), destination, show);
173 } 173 }
174 174
  175 +bool br_plot_detection(int num_files, const char *files[], const char *destination, bool show)
  176 +{
  177 + return PlotDetection(QtUtils::toStringList(num_files, files), destination, show);
  178 +}
  179 +
175 bool br_plot_metadata(int num_files, const char *files[], const char *columns, bool show) 180 bool br_plot_metadata(int num_files, const char *files[], const char *columns, bool show)
176 { 181 {
177 return PlotMetadata(QtUtils::toStringList(num_files, files), columns, show); 182 return PlotMetadata(QtUtils::toStringList(num_files, files), columns, show);
openbr/openbr.h
@@ -240,7 +240,7 @@ BR_EXPORT const char *br_most_recent_message(); @@ -240,7 +240,7 @@ BR_EXPORT const char *br_most_recent_message();
240 BR_EXPORT const char *br_objects(const char *abstractions = ".*", const char *implementations = ".*", bool parameters = true); 240 BR_EXPORT const char *br_objects(const char *abstractions = ".*", const char *implementations = ".*", bool parameters = true);
241 241
242 /*! 242 /*!
243 - * \brief Renders performance figures for a set of <tt>.csv</tt> files. 243 + * \brief Renders recognition performance figures for a set of <tt>.csv</tt> files created by \ref br_eval.
244 * 244 *
245 * In order of their output, the figures are: 245 * In order of their output, the figures are:
246 * -# Metadata table 246 * -# Metadata table
@@ -262,11 +262,29 @@ BR_EXPORT const char *br_objects(const char *abstractions = &quot;.*&quot;, const char *im @@ -262,11 +262,29 @@ BR_EXPORT const char *br_objects(const char *abstractions = &quot;.*&quot;, const char *im
262 * \return Returns \c true on success. Returns false on a failure to compile the figures due to a missing, out of date, or incomplete \c R installation. 262 * \return Returns \c true on success. Returns false on a failure to compile the figures due to a missing, out of date, or incomplete \c R installation.
263 * \note This function requires a current <a href="http://www.r-project.org/">R</a> installation with the following packages: 263 * \note This function requires a current <a href="http://www.r-project.org/">R</a> installation with the following packages:
264 * \code install.packages(c("ggplot2", "gplots", "reshape", "scales")) \endcode 264 * \code install.packages(c("ggplot2", "gplots", "reshape", "scales")) \endcode
265 - * \see br_plot_metadata 265 + * \see br_eval
266 */ 266 */
267 BR_EXPORT bool br_plot(int num_files, const char *files[], const char *destination, bool show = false); 267 BR_EXPORT bool br_plot(int num_files, const char *files[], const char *destination, bool show = false);
268 268
269 /*! 269 /*!
  270 + * \brief Renders detection performance figures for a set of <tt>.csv</tt> files created by \ref br_eval_detection.
  271 + *
  272 + * In order of their output, the figures are:
  273 + * -# Discrete Receiver Operating Characteristic (DiscreteROC)
  274 + * -# Continuous Receiver Operating Characteristic (ContinuousROC)
  275 + * -# Discrete Precision Recall (DiscretePR)
  276 + * -# Continuous Precision Recall (ContinuousPR)
  277 + * -# Bounding Box Overlap Histogram (Overlap)
  278 + *
  279 + * Detection accuracy is measured with <i>overlap fraction = bounding box intersection / union</i>.
  280 + * When computing <i>discrete</i> curves, an overlap >= 0.5 is considered a true positive, otherwise it is considered a false negative.
  281 + * When computing <i>continuous</i> curves, true positives and false negatives are measured fractionally as <i>overlap</i> and <i>1-overlap</i> respectively.
  282 + *
  283 + * \see br_plot
  284 + */
  285 +BR_EXPORT bool br_plot_detection(int num_files, const char *files[], const char *destination, bool show = false);
  286 +
  287 +/*!
270 * \brief Renders metadata figures for a set of <tt>.csv</tt> files with specified columns. 288 * \brief Renders metadata figures for a set of <tt>.csv</tt> files with specified columns.
271 * 289 *
272 * Several files will be created: 290 * Several files will be created:
openbr/openbr_plugin.cpp
@@ -813,7 +813,7 @@ float br::Context::progress() const @@ -813,7 +813,7 @@ float br::Context::progress() const
813 813
814 void br::Context::setProperty(const QString &key, const QString &value) 814 void br::Context::setProperty(const QString &key, const QString &value)
815 { 815 {
816 - Object::setProperty(key, value); 816 + Object::setProperty(key, value.isEmpty() ? QVariant() : value);
817 qDebug("Set %s%s", qPrintable(key), value.isEmpty() ? "" : qPrintable(" to " + value)); 817 qDebug("Set %s%s", qPrintable(key), value.isEmpty() ? "" : qPrintable(" to " + value));
818 818
819 if (key == "parallelism") { 819 if (key == "parallelism") {
@@ -1163,7 +1163,8 @@ void Transform::project(const TemplateList &amp;src, TemplateList &amp;dst) const @@ -1163,7 +1163,8 @@ void Transform::project(const TemplateList &amp;src, TemplateList &amp;dst) const
1163 dst.append(Template()); 1163 dst.append(Template());
1164 QFutureSynchronizer<void> futures; 1164 QFutureSynchronizer<void> futures;
1165 for (int i=0; i<dst.size(); i++) 1165 for (int i=0; i<dst.size(); i++)
1166 - futures.addFuture(QtConcurrent::run(_project, this, &src[i], &dst[i])); 1166 + if (Globals->parallelism > 1) futures.addFuture(QtConcurrent::run(_project, this, &src[i], &dst[i]));
  1167 + else _project(this, &src[i], &dst[i]);
1167 futures.waitForFinished(); 1168 futures.waitForFinished();
1168 } 1169 }
1169 1170
openbr/plugins/cascade.cpp
@@ -49,19 +49,20 @@ private: @@ -49,19 +49,20 @@ private:
49 } 49 }
50 }; 50 };
51 51
52 -  
53 /*! 52 /*!
54 * \ingroup transforms 53 * \ingroup transforms
55 * \brief Wraps OpenCV cascade classifier 54 * \brief Wraps OpenCV cascade classifier
56 * \author Josh Klontz \cite jklontz 55 * \author Josh Klontz \cite jklontz
57 */ 56 */
58 -class CascadeTransform : public UntrainableTransform 57 +class CascadeTransform : public UntrainableMetaTransform
59 { 58 {
60 Q_OBJECT 59 Q_OBJECT
61 Q_PROPERTY(QString model READ get_model WRITE set_model RESET reset_model STORED false) 60 Q_PROPERTY(QString model READ get_model WRITE set_model RESET reset_model STORED false)
62 Q_PROPERTY(int minSize READ get_minSize WRITE set_minSize RESET reset_minSize STORED false) 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 BR_PROPERTY(QString, model, "FrontalFace") 63 BR_PROPERTY(QString, model, "FrontalFace")
64 BR_PROPERTY(int, minSize, 64) 64 BR_PROPERTY(int, minSize, 64)
  65 + BR_PROPERTY(bool, ROCMode, false)
65 66
66 Resource<CascadeClassifier> cascadeResource; 67 Resource<CascadeClassifier> cascadeResource;
67 68
@@ -72,18 +73,54 @@ class CascadeTransform : public UntrainableTransform @@ -72,18 +73,54 @@ class CascadeTransform : public UntrainableTransform
72 73
73 void project(const Template &src, Template &dst) const 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 CascadeClassifier *cascade = cascadeResource.acquire(); 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]*1000.0 + levelWeights[j]*1.0);
  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 cascadeResource.release(cascade); 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/eyes.cpp
@@ -186,7 +186,6 @@ private: @@ -186,7 +186,6 @@ private:
186 dst.file.appendPoint(QPointF(second_eye_x, second_eye_y)); 186 dst.file.appendPoint(QPointF(second_eye_x, second_eye_y));
187 dst.file.set("First_Eye", QPointF(first_eye_x, first_eye_y)); 187 dst.file.set("First_Eye", QPointF(first_eye_x, first_eye_y));
188 dst.file.set("Second_Eye", QPointF(second_eye_x, second_eye_y)); 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/frames.cpp 0 โ†’ 100644
  1 +#include "openbr_internal.h"
  2 +
  3 +namespace br
  4 +{
  5 +
  6 +/*!
  7 + * \ingroup transforms
  8 + * \brief Passes along n sequential frames to the next transform.
  9 + * \author Josh Klontz \cite jklontz
  10 + *
  11 + * For a video with m frames, AggregateFrames would create a total of m-n+1 sequences ([0,n] ... [m-n+1, m]).
  12 + */
  13 +class AggregateFrames : public TimeVaryingTransform
  14 +{
  15 + Q_OBJECT
  16 + Q_PROPERTY(int n READ get_n WRITE set_n RESET reset_n STORED false)
  17 + BR_PROPERTY(int, n, 1)
  18 +
  19 + TemplateList buffer;
  20 +
  21 +public:
  22 + AggregateFrames() : TimeVaryingTransform(false) {}
  23 +
  24 +private:
  25 + void train(const TemplateList &data)
  26 + {
  27 + (void) data;
  28 + }
  29 +
  30 + void projectUpdate(const Template &src, Template &dst)
  31 + {
  32 + buffer.append(src);
  33 + if (buffer.size() < n) return;
  34 + foreach (const Template &t, buffer) dst.append(t);
  35 + dst.file = buffer.takeFirst().file;
  36 + }
  37 +
  38 + void store(QDataStream &stream) const
  39 + {
  40 + (void) stream;
  41 + }
  42 +
  43 + void load(QDataStream &stream)
  44 + {
  45 + (void) stream;
  46 + }
  47 +};
  48 +
  49 +BR_REGISTER(Transform, AggregateFrames)
  50 +
  51 +} // namespace br
  52 +
  53 +#include "frames.moc"
openbr/plugins/gallery.cpp
@@ -165,10 +165,13 @@ class EmptyGallery : public Gallery @@ -165,10 +165,13 @@ class EmptyGallery : public Gallery
165 templates.append(File(fileName, dir.dirName())); 165 templates.append(File(fileName, dir.dirName()));
166 166
167 if (!regexp.isEmpty()) { 167 if (!regexp.isEmpty()) {
168 - const QRegularExpression re(regexp);  
169 - for (int i=templates.size()-1; i>=0; i--)  
170 - if (!re.match(templates[i].file.suffix()).hasMatch()) 168 + QRegExp re(regexp);
  169 + re.setPatternSyntax(QRegExp::Wildcard);
  170 + for (int i=templates.size()-1; i>=0; i--) {
  171 + if (!re.exactMatch(templates[i].file.fileName())) {
171 templates.removeAt(i); 172 templates.removeAt(i);
  173 + }
  174 + }
172 } 175 }
173 176
174 return templates; 177 return templates;
@@ -269,67 +272,6 @@ class matrixGallery : public Gallery @@ -269,67 +272,6 @@ class matrixGallery : public Gallery
269 BR_REGISTER(Gallery, matrixGallery) 272 BR_REGISTER(Gallery, matrixGallery)
270 273
271 /*! 274 /*!
272 - * \ingroup galleries  
273 - * \brief Treat a video as a gallery, making a single template from each frame  
274 - * \author Charles Otto \cite caotto  
275 - */  
276 -class aviGallery : public Gallery  
277 -{  
278 - Q_OBJECT  
279 -  
280 - TemplateList output_set;  
281 - QScopedPointer<cv::VideoWriter> videoOut;  
282 -  
283 - ~aviGallery()  
284 - {  
285 - if (videoOut && videoOut->isOpened()) videoOut->release();  
286 - }  
287 -  
288 - TemplateList readBlock(bool * done)  
289 - {  
290 - std::string fname = file.name.toStdString();  
291 - *done = true;  
292 -  
293 - TemplateList output;  
294 - if (!file.exists())  
295 - return output;  
296 -  
297 - cv::VideoCapture videoReader(file.name.toStdString());  
298 -  
299 - bool open = videoReader.isOpened();  
300 -  
301 - while (open) {  
302 - cv::Mat frame;  
303 -  
304 - open = videoReader.read(frame);  
305 - if (!open) break;  
306 - output.append(Template());  
307 - output.back() = frame.clone();  
308 - }  
309 -  
310 - return TemplateList();  
311 - }  
312 -  
313 - void write(const Template & t)  
314 - {  
315 - if (videoOut.isNull() || !videoOut->isOpened()) {  
316 - int fourcc = OpenCVUtils::getFourcc();  
317 - videoOut.reset(new cv::VideoWriter(qPrintable(file.name), fourcc, 30, t.m().size()));  
318 - }  
319 -  
320 - if (!videoOut->isOpened()) {  
321 - qWarning("Failed to open %s for writing\n", qPrintable(file.name));  
322 - return;  
323 - }  
324 -  
325 - foreach(const cv::Mat & m, t) {  
326 - videoOut->write(m);  
327 - }  
328 - }  
329 -};  
330 -BR_REGISTER(Gallery, aviGallery)  
331 -  
332 -/*!  
333 * \ingroup initializers 275 * \ingroup initializers
334 * \brief Initialization support for memGallery. 276 * \brief Initialization support for memGallery.
335 * \author Josh Klontz \cite jklontz 277 * \author Josh Klontz \cite jklontz
openbr/plugins/gui.cpp
@@ -212,8 +212,11 @@ public: @@ -212,8 +212,11 @@ public:
212 foreach (const Template & t, src) { 212 foreach (const Template & t, src) {
213 // build label 213 // build label
214 QString newTitle; 214 QString newTitle;
  215 +
215 foreach (const QString & s, keys) { 216 foreach (const QString & s, keys) {
216 - if (t.file.contains(s)) { 217 + if (s.compare("name", Qt::CaseInsensitive) == 0) {
  218 + newTitle = newTitle + s + ": " + t.file.fileName() + " ";
  219 + } else if (t.file.contains(s)) {
217 QString out = t.file.get<QString>(s); 220 QString out = t.file.get<QString>(s);
218 newTitle = newTitle + s + ": " + out + " "; 221 newTitle = newTitle + s + ": " + out + " ";
219 } 222 }
openbr/plugins/independent.cpp
@@ -129,6 +129,8 @@ class IndependentTransform : public MetaTransform @@ -129,6 +129,8 @@ class IndependentTransform : public MetaTransform
129 return independentTransform; 129 return independentTransform;
130 } 130 }
131 131
  132 + bool timeVarying() const { return transform->timeVarying(); }
  133 +
132 static void _train(Transform *transform, const TemplateList *data) 134 static void _train(Transform *transform, const TemplateList *data)
133 { 135 {
134 transform->train(*data); 136 transform->train(*data);
@@ -170,6 +172,27 @@ class IndependentTransform : public MetaTransform @@ -170,6 +172,27 @@ class IndependentTransform : public MetaTransform
170 dst.append(mats); 172 dst.append(mats);
171 } 173 }
172 174
  175 + void projectUpdate(const Template &src, Template &dst)
  176 + {
  177 + dst.file = src.file;
  178 + QList<Mat> mats;
  179 + for (int i=0; i<src.size(); i++) {
  180 + transforms[i%transforms.size()]->projectUpdate(Template(src.file, src[i]), dst);
  181 + mats.append(dst);
  182 + dst.clear();
  183 + }
  184 + dst.append(mats);
  185 + }
  186 +
  187 + void projectUpdate(const TemplateList &src, TemplateList &dst)
  188 + {
  189 + dst.reserve(src.size());
  190 + foreach (const Template &t, src) {
  191 + dst.append(Template());
  192 + projectUpdate(t, dst.last());
  193 + }
  194 + }
  195 +
173 void store(QDataStream &stream) const 196 void store(QDataStream &stream) const
174 { 197 {
175 const int size = transforms.size(); 198 const int size = transforms.size();
openbr/plugins/landmarks.cpp
@@ -230,6 +230,8 @@ class DelaunayTransform : public UntrainableTransform @@ -230,6 +230,8 @@ class DelaunayTransform : public UntrainableTransform
230 230
231 QList<Point2f> mappedPoints; 231 QList<Point2f> mappedPoints;
232 232
  233 + dst.file = src.file;
  234 +
233 for (int i = 0; i < validTriangles.size(); i++) { 235 for (int i = 0; i < validTriangles.size(); i++) {
234 Eigen::MatrixXf srcMat(validTriangles[i].size(), 2); 236 Eigen::MatrixXf srcMat(validTriangles[i].size(), 2);
235 237
@@ -272,8 +274,8 @@ class DelaunayTransform : public UntrainableTransform @@ -272,8 +274,8 @@ class DelaunayTransform : public UntrainableTransform
272 bitwise_and(dst.m(),mask,overlap); 274 bitwise_and(dst.m(),mask,overlap);
273 for (int j = 0; j < overlap.rows; j++) { 275 for (int j = 0; j < overlap.rows; j++) {
274 for (int k = 0; k < overlap.cols; k++) { 276 for (int k = 0; k < overlap.cols; k++) {
275 - if (overlap.at<uchar>(k,j) != 0) {  
276 - mask.at<uchar>(k,j) = 0; 277 + if (overlap.at<uchar>(j,k) != 0) {
  278 + mask.at<uchar>(j,k) = 0;
277 } 279 }
278 } 280 }
279 } 281 }
@@ -281,11 +283,13 @@ class DelaunayTransform : public UntrainableTransform @@ -281,11 +283,13 @@ class DelaunayTransform : public UntrainableTransform
281 283
282 bitwise_and(buffer,mask,output); 284 bitwise_and(buffer,mask,output);
283 285
  286 +
284 dst.m() += output; 287 dst.m() += output;
285 } 288 }
286 289
  290 + // Overwrite any rects
287 Rect boundingBox = boundingRect(mappedPoints.toVector().toStdVector()); 291 Rect boundingBox = boundingRect(mappedPoints.toVector().toStdVector());
288 - dst.file.appendRect(OpenCVUtils::fromRect(boundingBox)); 292 + dst.file.setRects(QList<QRectF>() << OpenCVUtils::fromRect(boundingBox));
289 } 293 }
290 } 294 }
291 295
openbr/plugins/meta.cpp
@@ -645,17 +645,28 @@ public: @@ -645,17 +645,28 @@ public:
645 QList<TemplateList> input_buffer; 645 QList<TemplateList> input_buffer;
646 input_buffer.reserve(src.size()); 646 input_buffer.reserve(src.size());
647 647
  648 + QFutureSynchronizer<void> futures;
  649 +
648 for (int i =0; i < src.size();i++) { 650 for (int i =0; i < src.size();i++) {
649 input_buffer.append(TemplateList()); 651 input_buffer.append(TemplateList());
650 output_buffer.append(TemplateList()); 652 output_buffer.append(TemplateList());
651 } 653 }
652 -  
653 - QFutureSynchronizer<void> futures; 654 + QList<QFuture<void> > temp;
  655 + temp.reserve(src.size());
654 for (int i=0; i<src.size(); i++) { 656 for (int i=0; i<src.size(); i++) {
655 input_buffer[i].append(src[i]); 657 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]); 658 +
  659 + if (Globals->parallelism > 1) temp.append(QtConcurrent::run(_projectList, transform, &input_buffer[i], &output_buffer[i]));
  660 + else _projectList(transform, &input_buffer[i], &output_buffer[i]);
  661 + }
  662 + // We add the futures in reverse order, since in Qt 5.1 at least the
  663 + // waiting thread will wait on them in the order added (which for uniform priority
  664 + // threads is the order of execution), and we want the waiting thread to go in the opposite order
  665 + // so that it can steal runnables and do something besides wait.
  666 + for (int i = temp.size() - 1; i > 0; i--) {
  667 + futures.addFuture(temp[i]);
658 } 668 }
  669 +
659 futures.waitForFinished(); 670 futures.waitForFinished();
660 671
661 for (int i=0; i<src.size(); i++) dst.append(output_buffer[i]); 672 for (int i=0; i<src.size(); i++) dst.append(output_buffer[i]);
openbr/plugins/output.cpp
@@ -98,7 +98,7 @@ class heatOutput : public MatrixOutput @@ -98,7 +98,7 @@ class heatOutput : public MatrixOutput
98 { 98 {
99 Q_OBJECT 99 Q_OBJECT
100 Q_PROPERTY(int patches READ get_patches WRITE set_patches RESET reset_patches STORED false) 100 Q_PROPERTY(int patches READ get_patches WRITE set_patches RESET reset_patches STORED false)
101 - BR_PROPERTY(int, patches, -1); 101 + BR_PROPERTY(int, patches, -1)
102 102
103 ~heatOutput() 103 ~heatOutput()
104 { 104 {
@@ -426,20 +426,26 @@ class rankOutput : public MatrixOutput @@ -426,20 +426,26 @@ class rankOutput : public MatrixOutput
426 typedef QPair<float,int> Pair; 426 typedef QPair<float,int> Pair;
427 int rank = 1; 427 int rank = 1;
428 foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) { 428 foreach (const Pair &pair, Common::Sort(OpenCVUtils::matrixToVector<float>(data.row(i)), true)) {
429 - if (targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) {  
430 - ranks.append(rank);  
431 - positions.append(pair.second);  
432 - scores.append(pair.first);  
433 - break; 429 + if (Globals->crossValidate > 0 ? (targetFiles[pair.second].get<int>("Partition",-1) == queryFiles[i].get<int>("Partition",-1)) : true) {
  430 + if (QString(targetFiles[pair.second]) != QString(queryFiles[i])) {
  431 + if (targetFiles[pair.second].get<QString>("Label") == queryFiles[i].get<QString>("Label")) {
  432 + ranks.append(rank);
  433 + positions.append(pair.second);
  434 + scores.append(pair.first);
  435 + break;
  436 + }
  437 + rank++;
  438 + }
434 } 439 }
435 - rank++;  
436 } 440 }
437 } 441 }
438 442
439 typedef QPair<int,int> RankPair; 443 typedef QPair<int,int> RankPair;
440 foreach (const RankPair &pair, Common::Sort(ranks, false)) 444 foreach (const RankPair &pair, Common::Sort(ranks, false))
  445 + // pair.first == rank retrieved, pair.second == original position
441 lines.append(queryFiles[pair.second].name + " " + QString::number(pair.first) + " " + QString::number(scores[pair.second]) + " " + targetFiles[positions[pair.second]].name); 446 lines.append(queryFiles[pair.second].name + " " + QString::number(pair.first) + " " + QString::number(scores[pair.second]) + " " + targetFiles[positions[pair.second]].name);
442 447
  448 +
443 QtUtils::writeFile(file, lines); 449 QtUtils::writeFile(file, lines);
444 } 450 }
445 }; 451 };
openbr/plugins/regions.cpp
@@ -252,16 +252,12 @@ class CropRectTransform : public UntrainableTransform @@ -252,16 +252,12 @@ class CropRectTransform : public UntrainableTransform
252 void project(const Template &src, Template &dst) const 252 void project(const Template &src, Template &dst) const
253 { 253 {
254 dst = src; 254 dst = src;
255 - QList<QRectF> rects = dst.file.rects(); 255 + QList<QRectF> rects = src.file.rects();
256 for (int i=0;i < rects.size(); i++) { 256 for (int i=0;i < rects.size(); i++) {
257 - QRectF rect = rects[i];  
258 -  
259 - rect.setX(rect.x() + rect.width() * QtUtils::toPoint(widthCrop).x());  
260 - rect.setY(rect.y() + rect.height() * QtUtils::toPoint(heightCrop).x());  
261 - rect.setWidth(rect.width() * (1-QtUtils::toPoint(widthCrop).y()));  
262 - rect.setHeight(rect.height() * (1-QtUtils::toPoint(heightCrop).y()));  
263 -  
264 - dst.m() = Mat(dst.m(), OpenCVUtils::toRect(rect)); 257 + rects[i].setX(rects[i].x() + rects[i].width() * QtUtils::toPoint(widthCrop).x());
  258 + rects[i].setY(rects[i].y() + rects[i].height() * QtUtils::toPoint(heightCrop).x());
  259 + rects[i].setWidth(rects[i].width() * (1-QtUtils::toPoint(widthCrop).y()));
  260 + rects[i].setHeight(rects[i].height() * (1-QtUtils::toPoint(heightCrop).y()));
265 } 261 }
266 dst.file.setRects(rects); 262 dst.file.setRects(rects);
267 } 263 }
openbr/plugins/stream.cpp
@@ -160,9 +160,8 @@ class DataSource @@ -160,9 +160,8 @@ class DataSource
160 public: 160 public:
161 DataSource(int maxFrames=500) 161 DataSource(int maxFrames=500)
162 { 162 {
  163 + // The sequence number of the last frame
163 final_frame = -1; 164 final_frame = -1;
164 - last_issued = -2;  
165 - last_received = -3;  
166 for (int i=0; i < maxFrames;i++) 165 for (int i=0; i < maxFrames;i++)
167 { 166 {
168 allFrames.addItem(new FrameData()); 167 allFrames.addItem(new FrameData());
@@ -181,51 +180,64 @@ public: @@ -181,51 +180,64 @@ public:
181 } 180 }
182 181
183 // non-blocking version of getFrame 182 // non-blocking version of getFrame
184 - FrameData * tryGetFrame() 183 + // Returns a NULL FrameData if too many frames are out, or the
  184 + // data source is broken. Sets last_frame to true iff the FrameData
  185 + // returned is the last valid frame, and the data source is now broken.
  186 + FrameData * tryGetFrame(bool & last_frame)
185 { 187 {
  188 + last_frame = false;
  189 +
  190 + if (is_broken) {
  191 + return NULL;
  192 + }
  193 +
  194 + // Try to get a FrameData from the pool, if we can't it means too many
  195 + // frames are already out, and we will return NULL to indicate failure
186 FrameData * aFrame = allFrames.tryGetItem(); 196 FrameData * aFrame = allFrames.tryGetItem();
187 - if (aFrame == NULL) 197 + if (aFrame == NULL) {
188 return NULL; 198 return NULL;
  199 + }
189 200
190 aFrame->data.clear(); 201 aFrame->data.clear();
191 aFrame->sequenceNumber = -1; 202 aFrame->sequenceNumber = -1;
192 203
  204 + // Try to read a frame, if this returns false the data source is broken
193 bool res = getNext(*aFrame); 205 bool res = getNext(*aFrame);
194 206
195 - // The datasource broke.  
196 - if (!res) { 207 + // The datasource broke, update final_frame
  208 + if (!res)
  209 + {
  210 + QMutexLocker lock(&last_frame_update);
  211 + final_frame = lookAhead.back()->sequenceNumber;
197 allFrames.addItem(aFrame); 212 allFrames.addItem(aFrame);
  213 + }
  214 + else lookAhead.push_back(aFrame);
198 215
199 - QMutexLocker lock(&last_frame_update);  
200 - // Did we already receive the last frame?  
201 - final_frame = last_issued; 216 + FrameData * rVal = lookAhead.first();
  217 + lookAhead.pop_front();
202 218
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;  
210 219
211 - return NULL; 220 + if (rVal->sequenceNumber == final_frame) {
  221 + last_frame = true;
  222 + is_broken = true;
212 } 223 }
213 - last_issued = aFrame->sequenceNumber;  
214 - return aFrame; 224 +
  225 + return rVal;
215 } 226 }
216 227
217 - // Returns true if the frame returned was the last 228 + // Return a frame to the pool, returns true if the frame returned was the last
218 // frame issued, false otherwise 229 // frame issued, false otherwise
219 bool returnFrame(FrameData * inputFrame) 230 bool returnFrame(FrameData * inputFrame)
220 { 231 {
  232 + int frameNumber = inputFrame->sequenceNumber;
  233 +
221 allFrames.addItem(inputFrame); 234 allFrames.addItem(inputFrame);
222 235
223 bool rval = false; 236 bool rval = false;
224 237
225 QMutexLocker lock(&last_frame_update); 238 QMutexLocker lock(&last_frame_update);
226 - last_received = inputFrame->sequenceNumber;  
227 239
228 - if (inputFrame->sequenceNumber == final_frame) { 240 + if (frameNumber == final_frame) {
229 // We just received the last frame, better pulse 241 // We just received the last frame, better pulse
230 lastReturned.wakeAll(); 242 lastReturned.wakeAll();
231 rval = true; 243 rval = true;
@@ -240,17 +252,57 @@ public: @@ -240,17 +252,57 @@ public:
240 lastReturned.wait(&last_frame_update); 252 lastReturned.wait(&last_frame_update);
241 } 253 }
242 254
243 - virtual void close() = 0;  
244 - virtual bool open(Template & output, int start_index=0) = 0;  
245 - virtual bool isOpen() = 0; 255 + bool open(Template & output, int start_index = 0)
  256 + {
  257 + is_broken = false;
  258 + // The last frame isn't initialized yet
  259 + final_frame = -1;
  260 + // Start our sequence numbers from the input index
  261 + next_sequence_number = start_index;
  262 +
  263 + // Actually open the data source
  264 + bool open_res = concreteOpen(output);
  265 +
  266 + // We couldn't open the data source
  267 + if (!open_res) {
  268 + is_broken = true;
  269 + return false;
  270 + }
  271 +
  272 + // Try to get a frame from the global pool
  273 + FrameData * firstFrame = allFrames.tryGetItem();
  274 +
  275 + // If this fails, things have gone pretty badly.
  276 + if (firstFrame == NULL) {
  277 + is_broken = true;
  278 + return false;
  279 + }
  280 +
  281 + // Read a frame from the video source
  282 + bool res = getNext(*firstFrame);
  283 +
  284 + // the data source broke already, we couldn't even get one frame
  285 + // from it.
  286 + if (!res) {
  287 + is_broken = true;
  288 + return false;
  289 + }
246 290
  291 + lookAhead.append(firstFrame);
  292 + return true;
  293 + }
  294 +
  295 + virtual bool isOpen()=0;
  296 + virtual bool concreteOpen(Template & output) = 0;
247 virtual bool getNext(FrameData & input) = 0; 297 virtual bool getNext(FrameData & input) = 0;
  298 + virtual void close() = 0;
248 299
  300 + int next_sequence_number;
249 protected: 301 protected:
250 DoubleBuffer allFrames; 302 DoubleBuffer allFrames;
251 int final_frame; 303 int final_frame;
252 - int last_issued;  
253 - int last_received; 304 + bool is_broken;
  305 + QList<FrameData *> lookAhead;
254 306
255 QWaitCondition lastReturned; 307 QWaitCondition lastReturned;
256 QMutex last_frame_update; 308 QMutex last_frame_update;
@@ -262,13 +314,8 @@ class VideoDataSource : public DataSource @@ -262,13 +314,8 @@ class VideoDataSource : public DataSource
262 public: 314 public:
263 VideoDataSource(int maxFrames) : DataSource(maxFrames) {} 315 VideoDataSource(int maxFrames) : DataSource(maxFrames) {}
264 316
265 - bool open(Template &input, int start_index=0) 317 + bool concreteOpen(Template &input)
266 { 318 {
267 - final_frame = -1;  
268 - last_issued = -2;  
269 - last_received = -3;  
270 -  
271 - next_idx = start_index;  
272 basis = input; 319 basis = input;
273 bool is_int = false; 320 bool is_int = false;
274 int anInt = input.file.name.toInt(&is_int); 321 int anInt = input.file.name.toInt(&is_int);
@@ -284,8 +331,11 @@ public: @@ -284,8 +331,11 @@ public:
284 { 331 {
285 qDebug("Video not open!"); 332 qDebug("Video not open!");
286 } 333 }
  334 + } else {
  335 + // Yes, we should specify absolute path:
  336 + // http://stackoverflow.com/questions/9396459/loading-a-video-in-opencv-in-python
  337 + video.open(QFileInfo(input.file.name).absoluteFilePath().toStdString());
287 } 338 }
288 - else video.open(input.file.name.toStdString());  
289 339
290 return video.isOpened(); 340 return video.isOpened();
291 } 341 }
@@ -303,25 +353,31 @@ private: @@ -303,25 +353,31 @@ private:
303 return false; 353 return false;
304 354
305 output.data.append(Template(basis.file)); 355 output.data.append(Template(basis.file));
306 - output.data.last().append(cv::Mat()); 356 + output.data.last().m() = cv::Mat();
307 357
308 - output.sequenceNumber = next_idx;  
309 - next_idx++; 358 + output.sequenceNumber = next_sequence_number;
  359 + next_sequence_number++;
310 360
311 - bool res = video.read(output.data.last().last());  
312 - output.data.last().last() = output.data.last().last().clone(); 361 + cv::Mat temp;
  362 + bool res = video.read(temp);
313 363
314 if (!res) { 364 if (!res) {
  365 + output.data.last().m() = cv::Mat();
315 close(); 366 close();
316 return false; 367 return false;
317 } 368 }
  369 +
  370 + // This clone is critical, if we don't do it then the matrix will
  371 + // be an alias of an internal buffer of the video source, leading
  372 + // to various problems later.
  373 + output.data.last().m() = temp.clone();
  374 +
318 output.data.last().file.set("FrameNumber", output.sequenceNumber); 375 output.data.last().file.set("FrameNumber", output.sequenceNumber);
319 return true; 376 return true;
320 } 377 }
321 378
322 cv::VideoCapture video; 379 cv::VideoCapture video;
323 Template basis; 380 Template basis;
324 - int next_idx;  
325 }; 381 };
326 382
327 // Given a template as input, return its matrices one by one on subsequent calls 383 // Given a template as input, return its matrices one by one on subsequent calls
@@ -331,21 +387,16 @@ class TemplateDataSource : public DataSource @@ -331,21 +387,16 @@ class TemplateDataSource : public DataSource
331 public: 387 public:
332 TemplateDataSource(int maxFrames) : DataSource(maxFrames) 388 TemplateDataSource(int maxFrames) : DataSource(maxFrames)
333 { 389 {
334 - current_idx = INT_MAX; 390 + current_matrix_idx = INT_MAX;
335 data_ok = false; 391 data_ok = false;
336 } 392 }
337 - bool data_ok;  
338 393
339 - bool open(Template &input, int start_index=0) 394 + bool concreteOpen(Template &input)
340 { 395 {
341 basis = input; 396 basis = input;
342 - current_idx = 0;  
343 - next_sequence = start_index;  
344 - final_frame = -1;  
345 - last_issued = -2;  
346 - last_received = -3; 397 + current_matrix_idx = 0;
347 398
348 - data_ok = current_idx < basis.size(); 399 + data_ok = current_matrix_idx < basis.size();
349 return data_ok; 400 return data_ok;
350 } 401 }
351 402
@@ -355,39 +406,41 @@ public: @@ -355,39 +406,41 @@ public:
355 406
356 void close() 407 void close()
357 { 408 {
358 - current_idx = INT_MAX; 409 + current_matrix_idx = INT_MAX;
359 basis.clear(); 410 basis.clear();
360 } 411 }
361 412
362 private: 413 private:
363 bool getNext(FrameData & output) 414 bool getNext(FrameData & output)
364 { 415 {
365 - data_ok = current_idx < basis.size(); 416 + data_ok = current_matrix_idx < basis.size();
366 if (!data_ok) 417 if (!data_ok)
367 return false; 418 return false;
368 419
369 - output.data.append(basis[current_idx]);  
370 - current_idx++; 420 + output.data.append(basis[current_matrix_idx]);
  421 + current_matrix_idx++;
371 422
372 - output.sequenceNumber = next_sequence;  
373 - next_sequence++; 423 + output.sequenceNumber = next_sequence_number;
  424 + next_sequence_number++;
374 425
375 output.data.last().file.set("FrameNumber", output.sequenceNumber); 426 output.data.last().file.set("FrameNumber", output.sequenceNumber);
376 return true; 427 return true;
377 } 428 }
378 429
379 Template basis; 430 Template basis;
380 - int current_idx;  
381 - int next_sequence; 431 + // Index of the next matrix to output from the template
  432 + int current_matrix_idx;
  433 +
  434 + // is current_matrix_idx in bounds?
  435 + bool data_ok;
382 }; 436 };
383 437
384 -// Given a template as input, create a VideoDataSource or a TemplateDataSource  
385 -// depending on whether or not it looks like the input template has already  
386 -// loaded frames into memory. 438 +// Given a templatelist as input, create appropriate data source for each
  439 +// individual template
387 class DataSourceManager : public DataSource 440 class DataSourceManager : public DataSource
388 { 441 {
389 public: 442 public:
390 - DataSourceManager() 443 + DataSourceManager() : DataSource(500)
391 { 444 {
392 actualSource = NULL; 445 actualSource = NULL;
393 } 446 }
@@ -408,29 +461,25 @@ public: @@ -408,29 +461,25 @@ public:
408 461
409 bool open(TemplateList & input) 462 bool open(TemplateList & input)
410 { 463 {
411 - currentIdx = 0; 464 + current_template_idx = 0;
412 templates = input; 465 templates = input;
413 466
414 - return open(templates[currentIdx]); 467 + return DataSource::open(templates[current_template_idx]);
415 } 468 }
416 469
417 - bool open(Template & input, int start_index=0) 470 + bool concreteOpen(Template & input)
418 { 471 {
419 close(); 472 close();
420 - final_frame = -1;  
421 - last_issued = -2;  
422 - last_received = -3;  
423 - next_frame = start_index;  
424 473
425 // Input has no matrices? Its probably a video that hasn't been loaded yet 474 // Input has no matrices? Its probably a video that hasn't been loaded yet
426 if (input.empty()) { 475 if (input.empty()) {
427 actualSource = new VideoDataSource(0); 476 actualSource = new VideoDataSource(0);
428 - actualSource->open(input, next_frame); 477 + actualSource->concreteOpen(input);
429 } 478 }
430 else { 479 else {
431 // create frame dealer 480 // create frame dealer
432 actualSource = new TemplateDataSource(0); 481 actualSource = new TemplateDataSource(0);
433 - actualSource->open(input, next_frame); 482 + actualSource->concreteOpen(input);
434 } 483 }
435 if (!isOpen()) { 484 if (!isOpen()) {
436 delete actualSource; 485 delete actualSource;
@@ -443,30 +492,47 @@ public: @@ -443,30 +492,47 @@ public:
443 bool isOpen() { return !actualSource ? false : actualSource->isOpen(); } 492 bool isOpen() { return !actualSource ? false : actualSource->isOpen(); }
444 493
445 protected: 494 protected:
446 - int currentIdx;  
447 - int next_frame; 495 + // Index of the template in the templatelist we are currently reading from
  496 + int current_template_idx;
  497 +
448 TemplateList templates; 498 TemplateList templates;
449 DataSource * actualSource; 499 DataSource * actualSource;
450 bool getNext(FrameData & output) 500 bool getNext(FrameData & output)
451 { 501 {
452 bool res = actualSource->getNext(output); 502 bool res = actualSource->getNext(output);
  503 + output.sequenceNumber = next_sequence_number;
  504 +
453 if (res) { 505 if (res) {
454 - next_frame = output.sequenceNumber+1; 506 + output.data.last().file.set("FrameNumber", output.sequenceNumber);
  507 + next_sequence_number++;
  508 + if (output.data.last().last().empty())
  509 + qDebug("broken matrix");
455 return true; 510 return true;
456 } 511 }
457 512
  513 +
458 while(!res) { 514 while(!res) {
459 - currentIdx++; 515 + output.data.clear();
  516 + current_template_idx++;
460 517
461 - if (currentIdx >= templates.size()) 518 + // No more templates? We're done
  519 + if (current_template_idx >= templates.size())
462 return false; 520 return false;
463 - bool open_res = open(templates[currentIdx], next_frame); 521 +
  522 + // open the next data source
  523 + bool open_res = concreteOpen(templates[current_template_idx]);
464 if (!open_res) 524 if (!open_res)
465 return false; 525 return false;
  526 +
  527 + // get a frame from it
466 res = actualSource->getNext(output); 528 res = actualSource->getNext(output);
467 } 529 }
  530 + output.sequenceNumber = next_sequence_number++;
  531 + output.data.last().file.set("FrameNumber", output.sequenceNumber);
  532 +
  533 + if (output.data.last().last().empty())
  534 + qDebug("broken matrix");
468 535
469 - next_frame = output.sequenceNumber+1;  
470 return res; 536 return res;
471 } 537 }
472 538
@@ -474,9 +540,14 @@ protected: @@ -474,9 +540,14 @@ protected:
474 540
475 class ProcessingStage; 541 class ProcessingStage;
476 542
477 -class BasicLoop : public QRunnable 543 +class BasicLoop : public QRunnable, public QFutureInterface<void>
478 { 544 {
479 public: 545 public:
  546 + BasicLoop()
  547 + {
  548 + this->reportStarted();
  549 + }
  550 +
480 void run(); 551 void run();
481 552
482 QList<ProcessingStage *> * stages; 553 QList<ProcessingStage *> * stages;
@@ -502,13 +573,13 @@ public: @@ -502,13 +573,13 @@ public:
502 int stage_id; 573 int stage_id;
503 574
504 virtual void reset()=0; 575 virtual void reset()=0;
505 -  
506 protected: 576 protected:
507 int thread_count; 577 int thread_count;
508 578
509 SharedBuffer * inputBuffer; 579 SharedBuffer * inputBuffer;
510 ProcessingStage * nextStage; 580 ProcessingStage * nextStage;
511 QList<ProcessingStage *> * stages; 581 QList<ProcessingStage *> * stages;
  582 + QThreadPool * threads;
512 Transform * transform; 583 Transform * transform;
513 584
514 }; 585 };
@@ -527,6 +598,7 @@ void BasicLoop::run() @@ -527,6 +598,7 @@ void BasicLoop::run()
527 current_idx++; 598 current_idx++;
528 current_idx = current_idx % stages->size(); 599 current_idx = current_idx % stages->size();
529 } 600 }
  601 + this->reportFinished();
530 } 602 }
531 603
532 class MultiThreadStage : public ProcessingStage 604 class MultiThreadStage : public ProcessingStage
@@ -561,7 +633,6 @@ public: @@ -561,7 +633,6 @@ public:
561 } 633 }
562 }; 634 };
563 635
564 -  
565 class SingleThreadStage : public ProcessingStage 636 class SingleThreadStage : public ProcessingStage
566 { 637 {
567 public: 638 public:
@@ -624,18 +695,20 @@ public: @@ -624,18 +695,20 @@ public:
624 lock.unlock(); 695 lock.unlock();
625 696
626 if (newItem) 697 if (newItem)
627 - {  
628 - BasicLoop * next = new BasicLoop();  
629 - next->stages = stages;  
630 - next->start_idx = this->stage_id;  
631 - next->startItem = newItem;  
632 -  
633 - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id);  
634 - } 698 + startThread(newItem);
635 699
636 return input; 700 return input;
637 } 701 }
638 702
  703 + void startThread(br::FrameData * newItem)
  704 + {
  705 + BasicLoop * next = new BasicLoop();
  706 + next->stages = stages;
  707 + next->start_idx = this->stage_id;
  708 + next->startItem = newItem;
  709 + this->threads->start(next, stages->size() - stage_id);
  710 + }
  711 +
639 712
640 // Calledfrom a different thread than run. 713 // Calledfrom a different thread than run.
641 bool tryAcquireNextStage(FrameData *& input) 714 bool tryAcquireNextStage(FrameData *& input)
@@ -671,7 +744,7 @@ public: @@ -671,7 +744,7 @@ public:
671 }; 744 };
672 745
673 // No input buffer, instead we draw templates from some data source 746 // No input buffer, instead we draw templates from some data source
674 -// Will be operated by the main thread for the stream 747 +// Will be operated by the main thread for the stream. starts threads
675 class FirstStage : public SingleThreadStage 748 class FirstStage : public SingleThreadStage
676 { 749 {
677 public: 750 public:
@@ -681,44 +754,51 @@ public: @@ -681,44 +754,51 @@ public:
681 754
682 FrameData * run(FrameData * input, bool & should_continue) 755 FrameData * run(FrameData * input, bool & should_continue)
683 { 756 {
684 - // Is there anything on our input buffer? If so we should start a thread with that. 757 + // Try to get a frame from the datasource
685 QWriteLocker lock(&statusLock); 758 QWriteLocker lock(&statusLock);
686 - input = dataSource.tryGetFrame();  
687 - // Datasource broke?  
688 - if (!input) 759 + bool last_frame = false;
  760 + input = dataSource.tryGetFrame(last_frame);
  761 +
  762 + // Datasource broke, or is currently out of frames?
  763 + if (!input || last_frame)
689 { 764 {
  765 + // We will just stop and not continue.
690 currentStatus = STOPPING; 766 currentStatus = STOPPING;
691 - should_continue = false;  
692 - return NULL; 767 + if (!input) {
  768 + should_continue = false;
  769 + return NULL;
  770 + }
693 } 771 }
694 lock.unlock(); 772 lock.unlock();
695 - 773 + // Can we enter the next stage?
696 should_continue = nextStage->tryAcquireNextStage(input); 774 should_continue = nextStage->tryAcquireNextStage(input);
697 775
698 - BasicLoop * next = new BasicLoop();  
699 - next->stages = stages;  
700 - next->start_idx = this->stage_id;  
701 - next->startItem = NULL;  
702 -  
703 - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id); 776 + // We are exiting leaving this stage, should we start another
  777 + // thread here? Normally we will always re-queue a thread on
  778 + // the first stage, but if we received the last frame there is
  779 + // no need to.
  780 + if (!last_frame) {
  781 + startThread(NULL);
  782 + }
704 783
705 return input; 784 return input;
706 } 785 }
707 786
708 - // Calledfrom a different thread than run. 787 + // The last stage, trying to access the first stage
709 bool tryAcquireNextStage(FrameData *& input) 788 bool tryAcquireNextStage(FrameData *& input)
710 { 789 {
  790 + // Return the frame, was it the last one?
711 bool was_last = dataSource.returnFrame(input); 791 bool was_last = dataSource.returnFrame(input);
712 input = NULL; 792 input = NULL;
  793 +
  794 + // OK we won't continue.
713 if (was_last) { 795 if (was_last) {
714 return false; 796 return false;
715 } 797 }
716 798
717 - if (!dataSource.isOpen())  
718 - return false;  
719 -  
720 QReadLocker lock(&statusLock); 799 QReadLocker lock(&statusLock);
721 - // Thread is already running, we should just return 800 + // A thread is already in the first stage,
  801 + // we should just return
722 if (currentStatus == STARTING) 802 if (currentStatus == STARTING)
723 { 803 {
724 return false; 804 return false;
@@ -741,6 +821,7 @@ public: @@ -741,6 +821,7 @@ public:
741 821
742 }; 822 };
743 823
  824 +// starts threads
744 class LastStage : public SingleThreadStage 825 class LastStage : public SingleThreadStage
745 { 826 {
746 public: 827 public:
@@ -771,11 +852,14 @@ public: @@ -771,11 +852,14 @@ public:
771 } 852 }
772 next_target = input->sequenceNumber + 1; 853 next_target = input->sequenceNumber + 1;
773 854
  855 + // add the item to our output buffer
774 collectedOutput.append(input->data); 856 collectedOutput.append(input->data);
775 857
  858 + // Can we enter the read stage?
776 should_continue = nextStage->tryAcquireNextStage(input); 859 should_continue = nextStage->tryAcquireNextStage(input);
777 860
778 - // Is there anything on our input buffer? If so we should start a thread with that. 861 + // Is there anything on our input buffer? If so we should start a thread
  862 + // in this stage to process that frame.
779 QWriteLocker lock(&statusLock); 863 QWriteLocker lock(&statusLock);
780 FrameData * newItem = inputBuffer->tryGetItem(); 864 FrameData * newItem = inputBuffer->tryGetItem();
781 if (!newItem) 865 if (!newItem)
@@ -785,23 +869,18 @@ public: @@ -785,23 +869,18 @@ public:
785 lock.unlock(); 869 lock.unlock();
786 870
787 if (newItem) 871 if (newItem)
788 - {  
789 - BasicLoop * next = new BasicLoop();  
790 - next->stages = stages;  
791 - next->start_idx = this->stage_id;  
792 - next->startItem = newItem;  
793 -  
794 - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id);  
795 - } 872 + startThread(newItem);
796 873
797 return input; 874 return input;
798 } 875 }
799 }; 876 };
800 877
  878 +
801 class StreamTransform : public CompositeTransform 879 class StreamTransform : public CompositeTransform
802 { 880 {
803 Q_OBJECT 881 Q_OBJECT
804 public: 882 public:
  883 +
805 void train(const TemplateList & data) 884 void train(const TemplateList & data)
806 { 885 {
807 foreach(Transform * transform, transforms) { 886 foreach(Transform * transform, transforms) {
@@ -831,21 +910,17 @@ public: @@ -831,21 +910,17 @@ public:
831 bool res = readStage->dataSource.open(dst); 910 bool res = readStage->dataSource.open(dst);
832 if (!res) return; 911 if (!res) return;
833 912
834 - QThreadPool::globalInstance()->releaseThread(); 913 + // Start the first thread in the stream.
835 readStage->currentStatus = SingleThreadStage::STARTING; 914 readStage->currentStatus = SingleThreadStage::STARTING;
  915 + readStage->startThread(NULL);
836 916
837 - BasicLoop loop;  
838 - loop.stages = &this->processingStages;  
839 - loop.start_idx = 0;  
840 - loop.startItem = NULL;  
841 - loop.setAutoDelete(false);  
842 -  
843 - QThreadPool::globalInstance()->start(&loop, processingStages.size() - processingStages[0]->stage_id);  
844 -  
845 - // Wait for the end. 917 + // Wait for the stream to reach the last frame available from
  918 + // the data source.
846 readStage->dataSource.waitLast(); 919 readStage->dataSource.waitLast();
847 - QThreadPool::globalInstance()->reserveThread();  
848 920
  921 + // Now that there are no more incoming frames, call finalize
  922 + // on each transform in turn to collect any last templates
  923 + // they wish to issue.
849 TemplateList final_output; 924 TemplateList final_output;
850 925
851 // Push finalize through the stages 926 // Push finalize through the stages
@@ -861,7 +936,8 @@ public: @@ -861,7 +936,8 @@ public:
861 final_output.append(output_set); 936 final_output.append(output_set);
862 } 937 }
863 938
864 - // dst is set to all output received by the final stage 939 + // dst is set to all output received by the final stage, along
  940 + // with anything output via the calls to finalize.
865 dst = collectionStage->getOutput(); 941 dst = collectionStage->getOutput();
866 dst.append(final_output); 942 dst.append(final_output);
867 943
@@ -873,7 +949,8 @@ public: @@ -873,7 +949,8 @@ public:
873 virtual void finalize(TemplateList & output) 949 virtual void finalize(TemplateList & output)
874 { 950 {
875 (void) output; 951 (void) output;
876 - // Not handling this yet -cao 952 + // Nothing in particular to do here, stream calls finalize
  953 + // on all child transforms as part of projectUpdate
877 } 954 }
878 955
879 // Create and link stages 956 // Create and link stages
@@ -881,6 +958,19 @@ public: @@ -881,6 +958,19 @@ public:
881 { 958 {
882 if (transforms.isEmpty()) return; 959 if (transforms.isEmpty()) return;
883 960
  961 + // We share a thread pool across streams attached to the same
  962 + // parent tranform, retrieve or create a thread pool based
  963 + // on our parent transform.
  964 + QMutexLocker poolLock(&poolsAccess);
  965 + QHash<QObject *, QThreadPool *>::Iterator it;
  966 + if (!pools.contains(this->parent())) {
  967 + it = pools.insert(this->parent(), new QThreadPool(this));
  968 + it.value()->setMaxThreadCount(Globals->parallelism);
  969 + }
  970 + else it = pools.find(this->parent());
  971 + threads = it.value();
  972 + poolLock.unlock();
  973 +
884 stage_variance.reserve(transforms.size()); 974 stage_variance.reserve(transforms.size());
885 foreach (const br::Transform *transform, transforms) { 975 foreach (const br::Transform *transform, transforms) {
886 stage_variance.append(transform->timeVarying()); 976 stage_variance.append(transform->timeVarying());
@@ -891,6 +981,7 @@ public: @@ -891,6 +981,7 @@ public:
891 processingStages.push_back(readStage); 981 processingStages.push_back(readStage);
892 readStage->stage_id = 0; 982 readStage->stage_id = 0;
893 readStage->stages = &this->processingStages; 983 readStage->stages = &this->processingStages;
  984 + readStage->threads = this->threads;
894 985
895 int next_stage_id = 1; 986 int next_stage_id = 1;
896 987
@@ -898,9 +989,7 @@ public: @@ -898,9 +989,7 @@ public:
898 for (int i =0; i < transforms.size(); i++) 989 for (int i =0; i < transforms.size(); i++)
899 { 990 {
900 if (stage_variance[i]) 991 if (stage_variance[i])
901 - {  
902 processingStages.append(new SingleThreadStage(prev_stage_variance)); 992 processingStages.append(new SingleThreadStage(prev_stage_variance));
903 - }  
904 else 993 else
905 processingStages.append(new MultiThreadStage(Globals->parallelism)); 994 processingStages.append(new MultiThreadStage(Globals->parallelism));
906 995
@@ -911,6 +1000,7 @@ public: @@ -911,6 +1000,7 @@ public:
911 processingStages[i]->nextStage = processingStages[i+1]; 1000 processingStages[i]->nextStage = processingStages[i+1];
912 1001
913 processingStages.last()->stages = &this->processingStages; 1002 processingStages.last()->stages = &this->processingStages;
  1003 + processingStages.last()->threads = this->threads;
914 1004
915 processingStages.last()->transform = transforms[i]; 1005 processingStages.last()->transform = transforms[i];
916 prev_stage_variance = stage_variance[i]; 1006 prev_stage_variance = stage_variance[i];
@@ -920,6 +1010,7 @@ public: @@ -920,6 +1010,7 @@ public:
920 processingStages.append(collectionStage); 1010 processingStages.append(collectionStage);
921 collectionStage->stage_id = next_stage_id; 1011 collectionStage->stage_id = next_stage_id;
922 collectionStage->stages = &this->processingStages; 1012 collectionStage->stages = &this->processingStages;
  1013 + collectionStage->threads = this->threads;
923 1014
924 processingStages[processingStages.size() - 2]->nextStage = collectionStage; 1015 processingStages[processingStages.size() - 2]->nextStage = collectionStage;
925 1016
@@ -942,6 +1033,10 @@ protected: @@ -942,6 +1033,10 @@ protected:
942 1033
943 QList<ProcessingStage *> processingStages; 1034 QList<ProcessingStage *> processingStages;
944 1035
  1036 + static QHash<QObject *, QThreadPool *> pools;
  1037 + static QMutex poolsAccess;
  1038 + QThreadPool * threads;
  1039 +
945 void _project(const Template &src, Template &dst) const 1040 void _project(const Template &src, Template &dst) const
946 { 1041 {
947 (void) src; (void) dst; 1042 (void) src; (void) dst;
@@ -954,6 +1049,9 @@ protected: @@ -954,6 +1049,9 @@ protected:
954 } 1049 }
955 }; 1050 };
956 1051
  1052 +QHash<QObject *, QThreadPool *> StreamTransform::pools;
  1053 +QMutex StreamTransform::poolsAccess;
  1054 +
957 BR_REGISTER(Transform, StreamTransform) 1055 BR_REGISTER(Transform, StreamTransform)
958 1056
959 1057
openbr/plugins/validate.cpp
@@ -116,7 +116,8 @@ class FilterDistance : public Distance @@ -116,7 +116,8 @@ class FilterDistance : public Distance
116 foreach (const QString &key, Globals->filters.keys()) { 116 foreach (const QString &key, Globals->filters.keys()) {
117 bool keep = false; 117 bool keep = false;
118 const QString metadata = a.file.get<QString>(key, ""); 118 const QString metadata = a.file.get<QString>(key, "");
119 - if (metadata.isEmpty() || Globals->filters[key].isEmpty()) continue; 119 + if (Globals->filters[key].isEmpty()) continue;
  120 + if (metadata.isEmpty()) return -std::numeric_limits<float>::max();
120 foreach (const QString &value, Globals->filters[key]) { 121 foreach (const QString &value, Globals->filters[key]) {
121 if (metadata == value) { 122 if (metadata == value) {
122 keep = true; 123 keep = true;
scripts/downloadDatasets.sh
@@ -63,4 +63,6 @@ if [ ! -d ../data/KTH/vid ]; then @@ -63,4 +63,6 @@ if [ ! -d ../data/KTH/vid ]; then
63 unzip ${vidclass}.zip -d ../data/KTH/vid/${vidclass} 63 unzip ${vidclass}.zip -d ../data/KTH/vid/${vidclass}
64 rm ${vidclass}.zip 64 rm ${vidclass}.zip
65 done 65 done
  66 + # this file is corrupted
  67 + rm -f ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi
66 fi 68 fi
share/openbr/openbr.bib
@@ -34,6 +34,11 @@ @@ -34,6 +34,11 @@
34 Howpublished = {https://github.com/lbestrowden}, 34 Howpublished = {https://github.com/lbestrowden},
35 Title = {bestrow1 at msu.edu}} 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 % Software 42 % Software
38 @misc{libface, 43 @misc{libface,
39 Howpublished = {http://libface.sourceforge.net/file/Home.html}, 44 Howpublished = {http://libface.sourceforge.net/file/Home.html},