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