Commit 87021a620516d0dd6e0ceabc2fcc5d0c4ce977c5

Authored by Austin Van Blanton
2 parents 848463f8 950ba90b

Merge branch 'master' into mosift

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 ===============
... ...
openbr/core/bee.cpp
... ... @@ -102,7 +102,7 @@ void BEE::writeSigset(const QString &sigset, const br::FileList &files, bool ign
102 102 if ((key == "Index") || (key == "Subject")) continue;
103 103 metadata.append(key+"=\""+QtUtils::toString(file.value(key))+"\"");
104 104 }
105   - lines.append("\t<biometric-signature name=\"" + file.get<QString>("Subject") +"\">");
  105 + lines.append("\t<biometric-signature name=\"" + file.get<QString>("Subject",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
... ... @@ -377,7 +377,7 @@ static QStringList computeDetectionResults(const QList&lt;ResolvedDetection&gt; &amp;detec
377 377 for (int i=0; i<keep; i++) {
378 378 const DetectionOperatingPoint &point = points[double(i) / double(keep-1) * double(points.size()-1)];
379 379 lines.append(QString("%1ROC, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.FalsePositives), QString::number(point.Recall)));
380   - lines.append(QString("%1PR, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.Precision), QString::number(point.Recall)));
  380 + lines.append(QString("%1PR, %2, %3").arg(discrete ? "Discrete" : "Continuous", QString::number(point.Recall), QString::number(point.Precision)));
381 381 }
382 382 return lines;
383 383 }
... ...
openbr/core/plot.cpp
... ... @@ -238,7 +238,6 @@ bool Plot(const QStringList &amp;files, const 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()) +
... ... @@ -278,6 +277,39 @@ bool PlotDetection(const QStringList &amp;files, const File &amp;destination, bool show)
278 277  
279 278 RPlot p(files, destination, false);
280 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 +
281 313 return p.finalize(show);
282 314 }
283 315  
... ...
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.h
... ... @@ -268,6 +268,18 @@ BR_EXPORT bool br_plot(int num_files, const char *files[], const char *destinati
268 268  
269 269 /*!
270 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 + *
271 283 * \see br_plot
272 284 */
273 285 BR_EXPORT bool br_plot_detection(int num_files, const char *files[], const char *destination, bool show = false);
... ...
openbr/openbr_plugin.cpp
... ... @@ -812,7 +812,7 @@ float br::Context::progress() const
812 812  
813 813 void br::Context::setProperty(const QString &key, const QString &value)
814 814 {
815   - Object::setProperty(key, value);
  815 + Object::setProperty(key, value.isEmpty() ? QVariant() : value);
816 816 qDebug("Set %s%s", qPrintable(key), value.isEmpty() ? "" : qPrintable(" to " + value));
817 817  
818 818 if (key == "parallelism") {
... ... @@ -1166,7 +1166,8 @@ void Transform::project(const TemplateList &amp;src, TemplateList &amp;dst) const
1166 1166 dst.append(Template());
1167 1167 QFutureSynchronizer<void> futures;
1168 1168 for (int i=0; i<dst.size(); i++)
1169   - futures.addFuture(QtConcurrent::run(_project, this, &src[i], &dst[i]));
  1169 + if (Globals->parallelism > 1) futures.addFuture(QtConcurrent::run(_project, this, &src[i], &dst[i]));
  1170 + else _project(this, &src[i], &dst[i]);
1170 1171 futures.waitForFinished();
1171 1172 }
1172 1173  
... ...
openbr/plugins/algorithms.cpp
... ... @@ -46,7 +46,7 @@ class AlgorithmsInitializer : public Initializer
46 46 Globals->abbreviations.insert("CropFace", "Open+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+Affine(128,128,0.25,0.35)");
47 47  
48 48 // Video
49   - Globals->abbreviations.insert("DisplayVideo", "Stream([Show(false,[FrameNumber])+Discard])");
  49 + Globals->abbreviations.insert("DisplayVideo", "Stream([FPSLimit(30)+Show(false,[FrameNumber])+Discard])");
50 50 Globals->abbreviations.insert("PerFrameDetection", "Stream([SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+ASEFEyes+RestoreMat(original)+Draw(inPlace=true),Show(false,[FrameNumber])+Discard])");
51 51 Globals->abbreviations.insert("AgeGenderDemo", "Stream([SaveMat(original)+Cvt(Gray)+Cascade(FrontalFace)+Expand+<FaceClassificationRegistration>+<FaceClassificationExtraction>+(<AgeRegressor>+Rename(Subject,Age)+Discard)/(<GenderClassifier>+Rename(Subject,Gender)+Discard)+RestoreMat(original)+Draw(inPlace=true)+DrawPropertiesPoint([Age,Gender],Affine_0,inPlace=true)+SaveMat(original)+Discard+Contract,RestoreMat(original)+FPSCalc+Show(false,[AvgFPS,Age,Gender])+Discard])");
52 52 Globals->abbreviations.insert("BoVW", "Flatten+CatRows+KMeans(500)+Hist(500)");
... ...
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
... ... @@ -18,6 +18,10 @@ class AggregateFrames : public TimeVaryingTransform
18 18  
19 19 TemplateList buffer;
20 20  
  21 +public:
  22 + AggregateFrames() : TimeVaryingTransform(false) {}
  23 +
  24 +private:
21 25 void train(const TemplateList &data)
22 26 {
23 27 (void) data;
... ...
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;
... ...
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
... ... @@ -99,6 +99,8 @@ class IndependentTransform : public MetaTransform
99 99 return independentTransform;
100 100 }
101 101  
  102 + bool timeVarying() const { return transform->timeVarying(); }
  103 +
102 104 static void _train(Transform *transform, const TemplateList *data)
103 105 {
104 106 transform->train(*data);
... ... @@ -143,6 +145,27 @@ class IndependentTransform : public MetaTransform
143 145 dst.append(mats);
144 146 }
145 147  
  148 + void projectUpdate(const Template &src, Template &dst)
  149 + {
  150 + dst.file = src.file;
  151 + QList<Mat> mats;
  152 + for (int i=0; i<src.size(); i++) {
  153 + transforms[i%transforms.size()]->projectUpdate(Template(src.file, src[i]), dst);
  154 + mats.append(dst);
  155 + dst.clear();
  156 + }
  157 + dst.append(mats);
  158 + }
  159 +
  160 + void projectUpdate(const TemplateList &src, TemplateList &dst)
  161 + {
  162 + dst.reserve(src.size());
  163 + foreach (const Template &t, src) {
  164 + dst.append(Template());
  165 + projectUpdate(t, dst.last());
  166 + }
  167 + }
  168 +
146 169 void store(QDataStream &stream) const
147 170 {
148 171 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>("Subject") == queryFiles[i].get<QString>("Subject")) {
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>("Subject") == queryFiles[i].get<QString>("Subject")) {
  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);
... ... @@ -306,25 +353,31 @@ private:
306 353 return false;
307 354  
308 355 output.data.append(Template(basis.file));
309   - output.data.last().append(cv::Mat());
  356 + output.data.last().m() = cv::Mat();
310 357  
311   - output.sequenceNumber = next_idx;
312   - next_idx++;
  358 + output.sequenceNumber = next_sequence_number;
  359 + next_sequence_number++;
313 360  
314   - bool res = video.read(output.data.last().last());
315   - output.data.last().last() = output.data.last().last().clone();
  361 + cv::Mat temp;
  362 + bool res = video.read(temp);
316 363  
317 364 if (!res) {
  365 + output.data.last().m() = cv::Mat();
318 366 close();
319 367 return false;
320 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 +
321 375 output.data.last().file.set("FrameNumber", output.sequenceNumber);
322 376 return true;
323 377 }
324 378  
325 379 cv::VideoCapture video;
326 380 Template basis;
327   - int next_idx;
328 381 };
329 382  
330 383 // Given a template as input, return its matrices one by one on subsequent calls
... ... @@ -334,21 +387,16 @@ class TemplateDataSource : public DataSource
334 387 public:
335 388 TemplateDataSource(int maxFrames) : DataSource(maxFrames)
336 389 {
337   - current_idx = INT_MAX;
  390 + current_matrix_idx = INT_MAX;
338 391 data_ok = false;
339 392 }
340   - bool data_ok;
341 393  
342   - bool open(Template &input, int start_index=0)
  394 + bool concreteOpen(Template &input)
343 395 {
344 396 basis = input;
345   - current_idx = 0;
346   - next_sequence = start_index;
347   - final_frame = -1;
348   - last_issued = -2;
349   - last_received = -3;
  397 + current_matrix_idx = 0;
350 398  
351   - data_ok = current_idx < basis.size();
  399 + data_ok = current_matrix_idx < basis.size();
352 400 return data_ok;
353 401 }
354 402  
... ... @@ -358,39 +406,41 @@ public:
358 406  
359 407 void close()
360 408 {
361   - current_idx = INT_MAX;
  409 + current_matrix_idx = INT_MAX;
362 410 basis.clear();
363 411 }
364 412  
365 413 private:
366 414 bool getNext(FrameData & output)
367 415 {
368   - data_ok = current_idx < basis.size();
  416 + data_ok = current_matrix_idx < basis.size();
369 417 if (!data_ok)
370 418 return false;
371 419  
372   - output.data.append(basis[current_idx]);
373   - current_idx++;
  420 + output.data.append(basis[current_matrix_idx]);
  421 + current_matrix_idx++;
374 422  
375   - output.sequenceNumber = next_sequence;
376   - next_sequence++;
  423 + output.sequenceNumber = next_sequence_number;
  424 + next_sequence_number++;
377 425  
378 426 output.data.last().file.set("FrameNumber", output.sequenceNumber);
379 427 return true;
380 428 }
381 429  
382 430 Template basis;
383   - int current_idx;
384   - 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;
385 436 };
386 437  
387   -// Given a template as input, create a VideoDataSource or a TemplateDataSource
388   -// depending on whether or not it looks like the input template has already
389   -// loaded frames into memory.
  438 +// Given a templatelist as input, create appropriate data source for each
  439 +// individual template
390 440 class DataSourceManager : public DataSource
391 441 {
392 442 public:
393   - DataSourceManager()
  443 + DataSourceManager() : DataSource(500)
394 444 {
395 445 actualSource = NULL;
396 446 }
... ... @@ -411,29 +461,25 @@ public:
411 461  
412 462 bool open(TemplateList & input)
413 463 {
414   - currentIdx = 0;
  464 + current_template_idx = 0;
415 465 templates = input;
416 466  
417   - return open(templates[currentIdx]);
  467 + return DataSource::open(templates[current_template_idx]);
418 468 }
419 469  
420   - bool open(Template & input, int start_index=0)
  470 + bool concreteOpen(Template & input)
421 471 {
422 472 close();
423   - final_frame = -1;
424   - last_issued = -2;
425   - last_received = -3;
426   - next_frame = start_index;
427 473  
428 474 // Input has no matrices? Its probably a video that hasn't been loaded yet
429 475 if (input.empty()) {
430 476 actualSource = new VideoDataSource(0);
431   - actualSource->open(input, next_frame);
  477 + actualSource->concreteOpen(input);
432 478 }
433 479 else {
434 480 // create frame dealer
435 481 actualSource = new TemplateDataSource(0);
436   - actualSource->open(input, next_frame);
  482 + actualSource->concreteOpen(input);
437 483 }
438 484 if (!isOpen()) {
439 485 delete actualSource;
... ... @@ -446,30 +492,47 @@ public:
446 492 bool isOpen() { return !actualSource ? false : actualSource->isOpen(); }
447 493  
448 494 protected:
449   - int currentIdx;
450   - int next_frame;
  495 + // Index of the template in the templatelist we are currently reading from
  496 + int current_template_idx;
  497 +
451 498 TemplateList templates;
452 499 DataSource * actualSource;
453 500 bool getNext(FrameData & output)
454 501 {
455 502 bool res = actualSource->getNext(output);
  503 + output.sequenceNumber = next_sequence_number;
  504 +
456 505 if (res) {
457   - 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");
458 510 return true;
459 511 }
460 512  
  513 +
461 514 while(!res) {
462   - currentIdx++;
  515 + output.data.clear();
  516 + current_template_idx++;
463 517  
464   - if (currentIdx >= templates.size())
  518 + // No more templates? We're done
  519 + if (current_template_idx >= templates.size())
465 520 return false;
466   - bool open_res = open(templates[currentIdx], next_frame);
  521 +
  522 + // open the next data source
  523 + bool open_res = concreteOpen(templates[current_template_idx]);
467 524 if (!open_res)
468 525 return false;
  526 +
  527 + // get a frame from it
469 528 res = actualSource->getNext(output);
470 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");
471 535  
472   - next_frame = output.sequenceNumber+1;
473 536 return res;
474 537 }
475 538  
... ... @@ -477,9 +540,14 @@ protected:
477 540  
478 541 class ProcessingStage;
479 542  
480   -class BasicLoop : public QRunnable
  543 +class BasicLoop : public QRunnable, public QFutureInterface<void>
481 544 {
482 545 public:
  546 + BasicLoop()
  547 + {
  548 + this->reportStarted();
  549 + }
  550 +
483 551 void run();
484 552  
485 553 QList<ProcessingStage *> * stages;
... ... @@ -505,13 +573,13 @@ public:
505 573 int stage_id;
506 574  
507 575 virtual void reset()=0;
508   -
509 576 protected:
510 577 int thread_count;
511 578  
512 579 SharedBuffer * inputBuffer;
513 580 ProcessingStage * nextStage;
514 581 QList<ProcessingStage *> * stages;
  582 + QThreadPool * threads;
515 583 Transform * transform;
516 584  
517 585 };
... ... @@ -530,6 +598,7 @@ void BasicLoop::run()
530 598 current_idx++;
531 599 current_idx = current_idx % stages->size();
532 600 }
  601 + this->reportFinished();
533 602 }
534 603  
535 604 class MultiThreadStage : public ProcessingStage
... ... @@ -564,7 +633,6 @@ public:
564 633 }
565 634 };
566 635  
567   -
568 636 class SingleThreadStage : public ProcessingStage
569 637 {
570 638 public:
... ... @@ -627,18 +695,20 @@ public:
627 695 lock.unlock();
628 696  
629 697 if (newItem)
630   - {
631   - BasicLoop * next = new BasicLoop();
632   - next->stages = stages;
633   - next->start_idx = this->stage_id;
634   - next->startItem = newItem;
635   -
636   - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id);
637   - }
  698 + startThread(newItem);
638 699  
639 700 return input;
640 701 }
641 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 +
642 712  
643 713 // Calledfrom a different thread than run.
644 714 bool tryAcquireNextStage(FrameData *& input)
... ... @@ -674,7 +744,7 @@ public:
674 744 };
675 745  
676 746 // No input buffer, instead we draw templates from some data source
677   -// Will be operated by the main thread for the stream
  747 +// Will be operated by the main thread for the stream. starts threads
678 748 class FirstStage : public SingleThreadStage
679 749 {
680 750 public:
... ... @@ -684,44 +754,51 @@ public:
684 754  
685 755 FrameData * run(FrameData * input, bool & should_continue)
686 756 {
687   - // 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
688 758 QWriteLocker lock(&statusLock);
689   - input = dataSource.tryGetFrame();
690   - // Datasource broke?
691   - 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)
692 764 {
  765 + // We will just stop and not continue.
693 766 currentStatus = STOPPING;
694   - should_continue = false;
695   - return NULL;
  767 + if (!input) {
  768 + should_continue = false;
  769 + return NULL;
  770 + }
696 771 }
697 772 lock.unlock();
698   -
  773 + // Can we enter the next stage?
699 774 should_continue = nextStage->tryAcquireNextStage(input);
700 775  
701   - BasicLoop * next = new BasicLoop();
702   - next->stages = stages;
703   - next->start_idx = this->stage_id;
704   - next->startItem = NULL;
705   -
706   - 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 + }
707 783  
708 784 return input;
709 785 }
710 786  
711   - // Calledfrom a different thread than run.
  787 + // The last stage, trying to access the first stage
712 788 bool tryAcquireNextStage(FrameData *& input)
713 789 {
  790 + // Return the frame, was it the last one?
714 791 bool was_last = dataSource.returnFrame(input);
715 792 input = NULL;
  793 +
  794 + // OK we won't continue.
716 795 if (was_last) {
717 796 return false;
718 797 }
719 798  
720   - if (!dataSource.isOpen())
721   - return false;
722   -
723 799 QReadLocker lock(&statusLock);
724   - // Thread is already running, we should just return
  800 + // A thread is already in the first stage,
  801 + // we should just return
725 802 if (currentStatus == STARTING)
726 803 {
727 804 return false;
... ... @@ -744,6 +821,7 @@ public:
744 821  
745 822 };
746 823  
  824 +// starts threads
747 825 class LastStage : public SingleThreadStage
748 826 {
749 827 public:
... ... @@ -774,11 +852,14 @@ public:
774 852 }
775 853 next_target = input->sequenceNumber + 1;
776 854  
  855 + // add the item to our output buffer
777 856 collectedOutput.append(input->data);
778 857  
  858 + // Can we enter the read stage?
779 859 should_continue = nextStage->tryAcquireNextStage(input);
780 860  
781   - // 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.
782 863 QWriteLocker lock(&statusLock);
783 864 FrameData * newItem = inputBuffer->tryGetItem();
784 865 if (!newItem)
... ... @@ -788,23 +869,18 @@ public:
788 869 lock.unlock();
789 870  
790 871 if (newItem)
791   - {
792   - BasicLoop * next = new BasicLoop();
793   - next->stages = stages;
794   - next->start_idx = this->stage_id;
795   - next->startItem = newItem;
796   -
797   - QThreadPool::globalInstance()->start(next, stages->size() - this->stage_id);
798   - }
  872 + startThread(newItem);
799 873  
800 874 return input;
801 875 }
802 876 };
803 877  
  878 +
804 879 class StreamTransform : public CompositeTransform
805 880 {
806 881 Q_OBJECT
807 882 public:
  883 +
808 884 void train(const TemplateList & data)
809 885 {
810 886 foreach(Transform * transform, transforms) {
... ... @@ -834,21 +910,17 @@ public:
834 910 bool res = readStage->dataSource.open(dst);
835 911 if (!res) return;
836 912  
837   - QThreadPool::globalInstance()->releaseThread();
  913 + // Start the first thread in the stream.
838 914 readStage->currentStatus = SingleThreadStage::STARTING;
  915 + readStage->startThread(NULL);
839 916  
840   - BasicLoop loop;
841   - loop.stages = &this->processingStages;
842   - loop.start_idx = 0;
843   - loop.startItem = NULL;
844   - loop.setAutoDelete(false);
845   -
846   - QThreadPool::globalInstance()->start(&loop, processingStages.size() - processingStages[0]->stage_id);
847   -
848   - // Wait for the end.
  917 + // Wait for the stream to reach the last frame available from
  918 + // the data source.
849 919 readStage->dataSource.waitLast();
850   - QThreadPool::globalInstance()->reserveThread();
851 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.
852 924 TemplateList final_output;
853 925  
854 926 // Push finalize through the stages
... ... @@ -864,7 +936,8 @@ public:
864 936 final_output.append(output_set);
865 937 }
866 938  
867   - // 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.
868 941 dst = collectionStage->getOutput();
869 942 dst.append(final_output);
870 943  
... ... @@ -876,7 +949,8 @@ public:
876 949 virtual void finalize(TemplateList & output)
877 950 {
878 951 (void) output;
879   - // Not handling this yet -cao
  952 + // Nothing in particular to do here, stream calls finalize
  953 + // on all child transforms as part of projectUpdate
880 954 }
881 955  
882 956 // Create and link stages
... ... @@ -884,6 +958,19 @@ public:
884 958 {
885 959 if (transforms.isEmpty()) return;
886 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 +
887 974 stage_variance.reserve(transforms.size());
888 975 foreach (const br::Transform *transform, transforms) {
889 976 stage_variance.append(transform->timeVarying());
... ... @@ -894,6 +981,7 @@ public:
894 981 processingStages.push_back(readStage);
895 982 readStage->stage_id = 0;
896 983 readStage->stages = &this->processingStages;
  984 + readStage->threads = this->threads;
897 985  
898 986 int next_stage_id = 1;
899 987  
... ... @@ -901,9 +989,7 @@ public:
901 989 for (int i =0; i < transforms.size(); i++)
902 990 {
903 991 if (stage_variance[i])
904   - {
905 992 processingStages.append(new SingleThreadStage(prev_stage_variance));
906   - }
907 993 else
908 994 processingStages.append(new MultiThreadStage(Globals->parallelism));
909 995  
... ... @@ -914,6 +1000,7 @@ public:
914 1000 processingStages[i]->nextStage = processingStages[i+1];
915 1001  
916 1002 processingStages.last()->stages = &this->processingStages;
  1003 + processingStages.last()->threads = this->threads;
917 1004  
918 1005 processingStages.last()->transform = transforms[i];
919 1006 prev_stage_variance = stage_variance[i];
... ... @@ -923,6 +1010,7 @@ public:
923 1010 processingStages.append(collectionStage);
924 1011 collectionStage->stage_id = next_stage_id;
925 1012 collectionStage->stages = &this->processingStages;
  1013 + collectionStage->threads = this->threads;
926 1014  
927 1015 processingStages[processingStages.size() - 2]->nextStage = collectionStage;
928 1016  
... ... @@ -945,6 +1033,10 @@ protected:
945 1033  
946 1034 QList<ProcessingStage *> processingStages;
947 1035  
  1036 + static QHash<QObject *, QThreadPool *> pools;
  1037 + static QMutex poolsAccess;
  1038 + QThreadPool * threads;
  1039 +
948 1040 void _project(const Template &src, Template &dst) const
949 1041 {
950 1042 (void) src; (void) dst;
... ... @@ -957,6 +1049,9 @@ protected:
957 1049 }
958 1050 };
959 1051  
  1052 +QHash<QObject *, QThreadPool *> StreamTransform::pools;
  1053 +QMutex StreamTransform::poolsAccess;
  1054 +
960 1055 BR_REGISTER(Transform, StreamTransform)
961 1056  
962 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
... ... @@ -64,5 +64,5 @@ if [ ! -d ../data/KTH/vid ]; then
64 64 rm ${vidclass}.zip
65 65 done
66 66 # this file is corrupted
67   - rm ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi
  67 + rm -f ../data/KTH/vid/boxing/person01_boxing_d4_uncomp.avi
68 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},
... ...