Commit 87021a620516d0dd6e0ceabc2fcc5d0c4ce977c5
Merge branch 'master' into mosift
Showing
22 changed files
with
411 additions
and
177 deletions
CHANGELOG.md
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<ResolvedDetection> &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 &files, const File &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 &files, const File &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 &src, TemplateList &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
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
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}, | ... | ... |