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 1 0.4.0 - ??/??/??
2 2 ================
  3 +* Added -evalDetection and -plotDetection for evaluating and plotting object detection accuracy (#9)
3 4  
4 5 0.3.0 - 5/22/13
5 6 ===============
... ...
app/br/br.cpp
... ... @@ -140,6 +140,9 @@ public:
140 140 } else if (!strcmp(fun, "evalRegression")) {
141 141 check(parc >= 2 && parc <= 4, "Incorrect parameter count for 'evalRegression'.");
142 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 146 } else if (!strcmp(fun, "plotMetadata")) {
144 147 check(parc >= 2, "Incorrect parameter count for 'plotMetadata'.");
145 148 br_plot_metadata(parc-1, parv, parv[parc-1], true);
... ... @@ -216,6 +219,7 @@ private:
216 219 "-evalClustering <clusters> <gallery>\n"
217 220 "-evalDetection <predicted_gallery> <truth_gallery> [{csv}]\n"
218 221 "-evalRegression <predicted_gallery> <truth_gallery>\n"
  222 + "-plotDetection <file> ... <file> {destination}\n"
219 223 "-plotMetadata <file> ... <file> <columns>\n"
220 224 "-getHeader <matrix>\n"
221 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 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>("Label") +"\">");
  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 }
... ...
openbr/core/eval.cpp
... ... @@ -387,7 +387,7 @@ static QStringList computeDetectionResults(const QList&lt;ResolvedDetection&gt; &amp;detec
387 387 for (int i=0; i<keep; i++) {
388 388 const DetectionOperatingPoint &point = points[double(i) / double(keep-1) * double(points.size()-1)];
389 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 392 return lines;
393 393 }
... ...
openbr/core/plot.cpp
... ... @@ -79,7 +79,7 @@ struct RPlot
79 79  
80 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 84 if (files.isEmpty()) qFatal("Empty file list.");
85 85 qSort(files.begin(), files.end(), sortFiles);
... ... @@ -214,7 +214,7 @@ struct RPlot
214 214 };
215 215  
216 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 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 238 (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
239 239 QString(" + scale_x_log10(labels=percent) + scale_y_log10(labels=percent) + annotation_logticks()\n\n")));
240 240  
241   -
242 241 p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") +
243 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 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 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 316 bool PlotMetadata(const QStringList &files, const QString &columns, bool show)
276 317 {
277 318 qDebug("Plotting %d metadata file(s) for columns %s", files.size(), qPrintable(columns));
... ...
openbr/core/plot.h
... ... @@ -24,7 +24,8 @@
24 24  
25 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 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 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/openbr.cpp
... ... @@ -172,6 +172,11 @@ bool br_plot(int num_files, const char *files[], const char *destination, bool s
172 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 180 bool br_plot_metadata(int num_files, const char *files[], const char *columns, bool show)
176 181 {
177 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 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 245 * In order of their output, the figures are:
246 246 * -# Metadata table
... ... @@ -262,11 +262,29 @@ BR_EXPORT const char *br_objects(const char *abstractions = &quot;.*&quot;, const char *im
262 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 263 * \note This function requires a current <a href="http://www.r-project.org/">R</a> installation with the following packages:
264 264 * \code install.packages(c("ggplot2", "gplots", "reshape", "scales")) \endcode
265   - * \see br_plot_metadata
  265 + * \see br_eval
266 266 */
267 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 288 * \brief Renders metadata figures for a set of <tt>.csv</tt> files with specified columns.
271 289 *
272 290 * Several files will be created:
... ...
openbr/openbr_plugin.cpp
... ... @@ -813,7 +813,7 @@ float br::Context::progress() const
813 813  
814 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 817 qDebug("Set %s%s", qPrintable(key), value.isEmpty() ? "" : qPrintable(" to " + value));
818 818  
819 819 if (key == "parallelism") {
... ... @@ -1163,7 +1163,8 @@ void Transform::project(const TemplateList &amp;src, TemplateList &amp;dst) const
1163 1163 dst.append(Template());
1164 1164 QFutureSynchronizer<void> futures;
1165 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 1168 futures.waitForFinished();
1168 1169 }
1169 1170  
... ...
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]*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 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 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/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 165 templates.append(File(fileName, dir.dirName()));
166 166  
167 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 172 templates.removeAt(i);
  173 + }
  174 + }
172 175 }
173 176  
174 177 return templates;
... ... @@ -269,67 +272,6 @@ class matrixGallery : public Gallery
269 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 275 * \ingroup initializers
334 276 * \brief Initialization support for memGallery.
335 277 * \author Josh Klontz \cite jklontz
... ...
openbr/plugins/gui.cpp
... ... @@ -212,8 +212,11 @@ public:
212 212 foreach (const Template & t, src) {
213 213 // build label
214 214 QString newTitle;
  215 +
215 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 220 QString out = t.file.get<QString>(s);
218 221 newTitle = newTitle + s + ": " + out + " ";
219 222 }
... ...
openbr/plugins/independent.cpp
... ... @@ -129,6 +129,8 @@ class IndependentTransform : public MetaTransform
129 129 return independentTransform;
130 130 }
131 131  
  132 + bool timeVarying() const { return transform->timeVarying(); }
  133 +
132 134 static void _train(Transform *transform, const TemplateList *data)
133 135 {
134 136 transform->train(*data);
... ... @@ -170,6 +172,27 @@ class IndependentTransform : public MetaTransform
170 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 196 void store(QDataStream &stream) const
174 197 {
175 198 const int size = transforms.size();
... ...
openbr/plugins/landmarks.cpp
... ... @@ -230,6 +230,8 @@ class DelaunayTransform : public UntrainableTransform
230 230  
231 231 QList<Point2f> mappedPoints;
232 232  
  233 + dst.file = src.file;
  234 +
233 235 for (int i = 0; i < validTriangles.size(); i++) {
234 236 Eigen::MatrixXf srcMat(validTriangles[i].size(), 2);
235 237  
... ... @@ -272,8 +274,8 @@ class DelaunayTransform : public UntrainableTransform
272 274 bitwise_and(dst.m(),mask,overlap);
273 275 for (int j = 0; j < overlap.rows; j++) {
274 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 283  
282 284 bitwise_and(buffer,mask,output);
283 285  
  286 +
284 287 dst.m() += output;
285 288 }
286 289  
  290 + // Overwrite any rects
287 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 645 QList<TemplateList> input_buffer;
646 646 input_buffer.reserve(src.size());
647 647  
  648 + QFutureSynchronizer<void> futures;
  649 +
648 650 for (int i =0; i < src.size();i++) {
649 651 input_buffer.append(TemplateList());
650 652 output_buffer.append(TemplateList());
651 653 }
652   -
653   - QFutureSynchronizer<void> futures;
  654 + QList<QFuture<void> > temp;
  655 + temp.reserve(src.size());
654 656 for (int i=0; i<src.size(); i++) {
655 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 670 futures.waitForFinished();
660 671  
661 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 98 {
99 99 Q_OBJECT
100 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 103 ~heatOutput()
104 104 {
... ... @@ -426,20 +426,26 @@ class rankOutput : public MatrixOutput
426 426 typedef QPair<float,int> Pair;
427 427 int rank = 1;
428 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 443 typedef QPair<int,int> RankPair;
440 444 foreach (const RankPair &pair, Common::Sort(ranks, false))
  445 + // pair.first == rank retrieved, pair.second == original position
441 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 449 QtUtils::writeFile(file, lines);
444 450 }
445 451 };
... ...
openbr/plugins/regions.cpp
... ... @@ -252,16 +252,12 @@ class CropRectTransform : public UntrainableTransform
252 252 void project(const Template &src, Template &dst) const
253 253 {
254 254 dst = src;
255   - QList<QRectF> rects = dst.file.rects();
  255 + QList<QRectF> rects = src.file.rects();
256 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 262 dst.file.setRects(rects);
267 263 }
... ...
openbr/plugins/stream.cpp
... ... @@ -160,9 +160,8 @@ class DataSource
160 160 public:
161 161 DataSource(int maxFrames=500)
162 162 {
  163 + // The sequence number of the last frame
163 164 final_frame = -1;
164   - last_issued = -2;
165   - last_received = -3;
166 165 for (int i=0; i < maxFrames;i++)
167 166 {
168 167 allFrames.addItem(new FrameData());
... ... @@ -181,51 +180,64 @@ public:
181 180 }
182 181  
183 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 196 FrameData * aFrame = allFrames.tryGetItem();
187   - if (aFrame == NULL)
  197 + if (aFrame == NULL) {
188 198 return NULL;
  199 + }
189 200  
190 201 aFrame->data.clear();
191 202 aFrame->sequenceNumber = -1;
192 203  
  204 + // Try to read a frame, if this returns false the data source is broken
193 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 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 229 // frame issued, false otherwise
219 230 bool returnFrame(FrameData * inputFrame)
220 231 {
  232 + int frameNumber = inputFrame->sequenceNumber;
  233 +
221 234 allFrames.addItem(inputFrame);
222 235  
223 236 bool rval = false;
224 237  
225 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 241 // We just received the last frame, better pulse
230 242 lastReturned.wakeAll();
231 243 rval = true;
... ... @@ -240,17 +252,57 @@ public:
240 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 297 virtual bool getNext(FrameData & input) = 0;
  298 + virtual void close() = 0;
248 299  
  300 + int next_sequence_number;
249 301 protected:
250 302 DoubleBuffer allFrames;
251 303 int final_frame;
252   - int last_issued;
253   - int last_received;
  304 + bool is_broken;
  305 + QList<FrameData *> lookAhead;
254 306  
255 307 QWaitCondition lastReturned;
256 308 QMutex last_frame_update;
... ... @@ -262,13 +314,8 @@ class VideoDataSource : public DataSource
262 314 public:
263 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 319 basis = input;
273 320 bool is_int = false;
274 321 int anInt = input.file.name.toInt(&is_int);
... ... @@ -284,8 +331,11 @@ public:
284 331 {
285 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 340 return video.isOpened();
291 341 }
... ... @@ -303,25 +353,31 @@ private:
303 353 return false;
304 354  
305 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 364 if (!res) {
  365 + output.data.last().m() = cv::Mat();
315 366 close();
316 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 375 output.data.last().file.set("FrameNumber", output.sequenceNumber);
319 376 return true;
320 377 }
321 378  
322 379 cv::VideoCapture video;
323 380 Template basis;
324   - int next_idx;
325 381 };
326 382  
327 383 // Given a template as input, return its matrices one by one on subsequent calls
... ... @@ -331,21 +387,16 @@ class TemplateDataSource : public DataSource
331 387 public:
332 388 TemplateDataSource(int maxFrames) : DataSource(maxFrames)
333 389 {
334   - current_idx = INT_MAX;
  390 + current_matrix_idx = INT_MAX;
335 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 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 400 return data_ok;
350 401 }
351 402  
... ... @@ -355,39 +406,41 @@ public:
355 406  
356 407 void close()
357 408 {
358   - current_idx = INT_MAX;
  409 + current_matrix_idx = INT_MAX;
359 410 basis.clear();
360 411 }
361 412  
362 413 private:
363 414 bool getNext(FrameData & output)
364 415 {
365   - data_ok = current_idx < basis.size();
  416 + data_ok = current_matrix_idx < basis.size();
366 417 if (!data_ok)
367 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 426 output.data.last().file.set("FrameNumber", output.sequenceNumber);
376 427 return true;
377 428 }
378 429  
379 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 440 class DataSourceManager : public DataSource
388 441 {
389 442 public:
390   - DataSourceManager()
  443 + DataSourceManager() : DataSource(500)
391 444 {
392 445 actualSource = NULL;
393 446 }
... ... @@ -408,29 +461,25 @@ public:
408 461  
409 462 bool open(TemplateList & input)
410 463 {
411   - currentIdx = 0;
  464 + current_template_idx = 0;
412 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 472 close();
420   - final_frame = -1;
421   - last_issued = -2;
422   - last_received = -3;
423   - next_frame = start_index;
424 473  
425 474 // Input has no matrices? Its probably a video that hasn't been loaded yet
426 475 if (input.empty()) {
427 476 actualSource = new VideoDataSource(0);
428   - actualSource->open(input, next_frame);
  477 + actualSource->concreteOpen(input);
429 478 }
430 479 else {
431 480 // create frame dealer
432 481 actualSource = new TemplateDataSource(0);
433   - actualSource->open(input, next_frame);
  482 + actualSource->concreteOpen(input);
434 483 }
435 484 if (!isOpen()) {
436 485 delete actualSource;
... ... @@ -443,30 +492,47 @@ public:
443 492 bool isOpen() { return !actualSource ? false : actualSource->isOpen(); }
444 493  
445 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 498 TemplateList templates;
449 499 DataSource * actualSource;
450 500 bool getNext(FrameData & output)
451 501 {
452 502 bool res = actualSource->getNext(output);
  503 + output.sequenceNumber = next_sequence_number;
  504 +
453 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 510 return true;
456 511 }
457 512  
  513 +
458 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 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 524 if (!open_res)
465 525 return false;
  526 +
  527 + // get a frame from it
466 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 536 return res;
471 537 }
472 538  
... ... @@ -474,9 +540,14 @@ protected:
474 540  
475 541 class ProcessingStage;
476 542  
477   -class BasicLoop : public QRunnable
  543 +class BasicLoop : public QRunnable, public QFutureInterface<void>
478 544 {
479 545 public:
  546 + BasicLoop()
  547 + {
  548 + this->reportStarted();
  549 + }
  550 +
480 551 void run();
481 552  
482 553 QList<ProcessingStage *> * stages;
... ... @@ -502,13 +573,13 @@ public:
502 573 int stage_id;
503 574  
504 575 virtual void reset()=0;
505   -
506 576 protected:
507 577 int thread_count;
508 578  
509 579 SharedBuffer * inputBuffer;
510 580 ProcessingStage * nextStage;
511 581 QList<ProcessingStage *> * stages;
  582 + QThreadPool * threads;
512 583 Transform * transform;
513 584  
514 585 };
... ... @@ -527,6 +598,7 @@ void BasicLoop::run()
527 598 current_idx++;
528 599 current_idx = current_idx % stages->size();
529 600 }
  601 + this->reportFinished();
530 602 }
531 603  
532 604 class MultiThreadStage : public ProcessingStage
... ... @@ -561,7 +633,6 @@ public:
561 633 }
562 634 };
563 635  
564   -
565 636 class SingleThreadStage : public ProcessingStage
566 637 {
567 638 public:
... ... @@ -624,18 +695,20 @@ public:
624 695 lock.unlock();
625 696  
626 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 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 713 // Calledfrom a different thread than run.
641 714 bool tryAcquireNextStage(FrameData *& input)
... ... @@ -671,7 +744,7 @@ public:
671 744 };
672 745  
673 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 748 class FirstStage : public SingleThreadStage
676 749 {
677 750 public:
... ... @@ -681,44 +754,51 @@ public:
681 754  
682 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 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 766 currentStatus = STOPPING;
691   - should_continue = false;
692   - return NULL;
  767 + if (!input) {
  768 + should_continue = false;
  769 + return NULL;
  770 + }
693 771 }
694 772 lock.unlock();
695   -
  773 + // Can we enter the next stage?
696 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 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 788 bool tryAcquireNextStage(FrameData *& input)
710 789 {
  790 + // Return the frame, was it the last one?
711 791 bool was_last = dataSource.returnFrame(input);
712 792 input = NULL;
  793 +
  794 + // OK we won't continue.
713 795 if (was_last) {
714 796 return false;
715 797 }
716 798  
717   - if (!dataSource.isOpen())
718   - return false;
719   -
720 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 802 if (currentStatus == STARTING)
723 803 {
724 804 return false;
... ... @@ -741,6 +821,7 @@ public:
741 821  
742 822 };
743 823  
  824 +// starts threads
744 825 class LastStage : public SingleThreadStage
745 826 {
746 827 public:
... ... @@ -771,11 +852,14 @@ public:
771 852 }
772 853 next_target = input->sequenceNumber + 1;
773 854  
  855 + // add the item to our output buffer
774 856 collectedOutput.append(input->data);
775 857  
  858 + // Can we enter the read stage?
776 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 863 QWriteLocker lock(&statusLock);
780 864 FrameData * newItem = inputBuffer->tryGetItem();
781 865 if (!newItem)
... ... @@ -785,23 +869,18 @@ public:
785 869 lock.unlock();
786 870  
787 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 874 return input;
798 875 }
799 876 };
800 877  
  878 +
801 879 class StreamTransform : public CompositeTransform
802 880 {
803 881 Q_OBJECT
804 882 public:
  883 +
805 884 void train(const TemplateList & data)
806 885 {
807 886 foreach(Transform * transform, transforms) {
... ... @@ -831,21 +910,17 @@ public:
831 910 bool res = readStage->dataSource.open(dst);
832 911 if (!res) return;
833 912  
834   - QThreadPool::globalInstance()->releaseThread();
  913 + // Start the first thread in the stream.
835 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 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 924 TemplateList final_output;
850 925  
851 926 // Push finalize through the stages
... ... @@ -861,7 +936,8 @@ public:
861 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 941 dst = collectionStage->getOutput();
866 942 dst.append(final_output);
867 943  
... ... @@ -873,7 +949,8 @@ public:
873 949 virtual void finalize(TemplateList & output)
874 950 {
875 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 956 // Create and link stages
... ... @@ -881,6 +958,19 @@ public:
881 958 {
882 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 974 stage_variance.reserve(transforms.size());
885 975 foreach (const br::Transform *transform, transforms) {
886 976 stage_variance.append(transform->timeVarying());
... ... @@ -891,6 +981,7 @@ public:
891 981 processingStages.push_back(readStage);
892 982 readStage->stage_id = 0;
893 983 readStage->stages = &this->processingStages;
  984 + readStage->threads = this->threads;
894 985  
895 986 int next_stage_id = 1;
896 987  
... ... @@ -898,9 +989,7 @@ public:
898 989 for (int i =0; i < transforms.size(); i++)
899 990 {
900 991 if (stage_variance[i])
901   - {
902 992 processingStages.append(new SingleThreadStage(prev_stage_variance));
903   - }
904 993 else
905 994 processingStages.append(new MultiThreadStage(Globals->parallelism));
906 995  
... ... @@ -911,6 +1000,7 @@ public:
911 1000 processingStages[i]->nextStage = processingStages[i+1];
912 1001  
913 1002 processingStages.last()->stages = &this->processingStages;
  1003 + processingStages.last()->threads = this->threads;
914 1004  
915 1005 processingStages.last()->transform = transforms[i];
916 1006 prev_stage_variance = stage_variance[i];
... ... @@ -920,6 +1010,7 @@ public:
920 1010 processingStages.append(collectionStage);
921 1011 collectionStage->stage_id = next_stage_id;
922 1012 collectionStage->stages = &this->processingStages;
  1013 + collectionStage->threads = this->threads;
923 1014  
924 1015 processingStages[processingStages.size() - 2]->nextStage = collectionStage;
925 1016  
... ... @@ -942,6 +1033,10 @@ protected:
942 1033  
943 1034 QList<ProcessingStage *> processingStages;
944 1035  
  1036 + static QHash<QObject *, QThreadPool *> pools;
  1037 + static QMutex poolsAccess;
  1038 + QThreadPool * threads;
  1039 +
945 1040 void _project(const Template &src, Template &dst) const
946 1041 {
947 1042 (void) src; (void) dst;
... ... @@ -954,6 +1049,9 @@ protected:
954 1049 }
955 1050 };
956 1051  
  1052 +QHash<QObject *, QThreadPool *> StreamTransform::pools;
  1053 +QMutex StreamTransform::poolsAccess;
  1054 +
957 1055 BR_REGISTER(Transform, StreamTransform)
958 1056  
959 1057  
... ...
openbr/plugins/validate.cpp
... ... @@ -116,7 +116,8 @@ class FilterDistance : public Distance
116 116 foreach (const QString &key, Globals->filters.keys()) {
117 117 bool keep = false;
118 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 121 foreach (const QString &value, Globals->filters[key]) {
121 122 if (metadata == value) {
122 123 keep = true;
... ...
scripts/downloadDatasets.sh
... ... @@ -63,4 +63,6 @@ if [ ! -d ../data/KTH/vid ]; then
63 63 unzip ${vidclass}.zip -d ../data/KTH/vid/${vidclass}
64 64 rm ${vidclass}.zip
65 65 done
  66 + # this file is corrupted
  67 + rm -f ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi
66 68 fi
... ...
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},
... ...