diff --git a/CHANGELOG.md b/CHANGELOG.md index c56439e..6445816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ 0.4.0 - ??/??/?? ================ +* Added -evalLandmarking and -plotLandmarking for evaluating and plotting landmarking accuracy (#9) * Added -evalDetection and -plotDetection for evaluating and plotting object detection accuracy (#9) * Deprecated Transform::backProject diff --git a/openbr/core/plot.cpp b/openbr/core/plot.cpp index 67d1224..021fc5d 100644 --- a/openbr/core/plot.cpp +++ b/openbr/core/plot.cpp @@ -228,7 +228,7 @@ bool Plot(const QStringList &files, const File &destination, bool show) QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()") + (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) + (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) + - QString(" + scale_x_log10(labels=percent) + scale_y_continuous(labels=percent) + annotation_logticks(sides=\"b\")\n\n"))); + QString(" + scale_x_log10(labels=percent, limits=c(min(DET$X),1)) + scale_y_continuous(labels=percent) + annotation_logticks(sides=\"b\")\n\n"))); p.file.write(qPrintable(QString("qplot(X, Y, data=DET%1").arg((p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : ", geom=\"line\"") + (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) + @@ -236,7 +236,7 @@ bool Plot(const QStringList &files, const File &destination, bool show) QString(", xlab=\"False Accept Rate\", ylab=\"False Reject Rate\") + geom_abline(alpha=0.5, colour=\"grey\", linetype=\"dashed\") + theme_minimal()") + (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) + (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) + - QString(" + scale_x_log10(labels=percent) + scale_y_log10(labels=percent) + annotation_logticks()\n\n"))); + QString(" + scale_x_log10(labels=percent, limits=c(min(DET$X),1)) + scale_y_log10(labels=percent) + annotation_logticks()\n\n"))); p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") + QString(", xlab=\"Score%1\"").arg((p.flip ? p.major.size : p.minor.size) > 1 ? " / " + (p.flip ? p.major.header : p.minor.header) : QString()) + @@ -355,7 +355,12 @@ bool PlotLandmarking(const QStringList &files, const File &destination, bool sho "rm(data)\n" "\n"); - p.file.write("ggplot(Box, aes(factor(X),Y)) + geom_boxplot() + geom_jitter(size=1.33,alpha=0.66) + scale_x_discrete(\"Landmark\") + scale_y_log10(\"Normalized Error\", breaks=c(0.01,0.1,1,10)) + annotation_logticks(sides=\"l\")\n\n"); + p.file.write(qPrintable(QString("ggplot(Box, aes(Y,%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + + QString(" + annotation_logticks(sides=\"b\") + stat_ecdf() + scale_x_log10(\"Normalized Error\", breaks=c(0.001,0.01,0.1,1,10)) + scale_y_continuous(\"Cumulative Density\", label=percent) + theme_minimal()\n\n"))); + p.file.write(qPrintable(QString("ggplot(Box, aes(factor(X), Y%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + + QString("+ annotation_logticks(sides=\"l\") + geom_boxplot(alpha=0.5) + geom_jitter(size=1, alpha=0.5) + scale_x_discrete(\"Landmark\") + scale_y_log10(\"Normalized Error\", breaks=c(0.01,0.1,1,10)) + theme_minimal()\n\n"))); + p.file.write(qPrintable(QString("ggplot(Box, aes(factor(X), Y%1%2))").arg(p.major.size > 1 ? QString(", colour=%1").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=%1").arg(p.minor.header) : QString()) + + QString("+ annotation_logticks(sides=\"l\") + geom_violin(alpha=0.5) + scale_x_discrete(\"Landmark\") + scale_y_log10(\"Normalized Error\", breaks=c(0.001,0.01,0.1,1,10))\n\n"))); return p.finalize(show); } diff --git a/openbr/core/resource.h b/openbr/core/resource.h index a620969..d418e78 100644 --- a/openbr/core/resource.h +++ b/openbr/core/resource.h @@ -40,6 +40,9 @@ class DefaultResourceMaker : public ResourceMaker T *make() const { return new T(); } }; +// Manage multiple copies of a limited resource in a thread-safe manner. +// TimeVaryingTransform makes a strong assumption that ResourceMaker::Make +// is only called in acquire, not in the constructor. template class Resource { diff --git a/openbr/openbr.h b/openbr/openbr.h index 9215e7c..c999b86 100644 --- a/openbr/openbr.h +++ b/openbr/openbr.h @@ -265,6 +265,12 @@ BR_EXPORT const char *br_objects(const char *abstractions = ".*", const char *im * - destination.R which is the auto-generated R script used to render the figures. * - destination.pdf which has all of the figures in one file multi-page file. * + * OpenBR uses file and folder names to automatically determine the plot legend. + * For example, let's consider the case where three algorithms (A, B, & C) were each evaluated on two datasets (Y & Z). + * The suggested way to plot these experiments on the same graph is to create a folder named Algorithm_Dataset that contains the six .csv files produced by \ref br_eval: A_Y.csv, A_Z.csv, B_Y.csv, B_Z.csv, C_Y.csv, & C_Z.csv. + * The '_' character plays a special role in determining the legend title(s) and value(s). + * In this case, A, B, & C will be identified as different values of type Algorithm, and each will be assigned its own color; Y & Z will be identified as different values of type Dataset, and each will be assigned its own line style. + * * \param num_files Number of .csv files. * \param files .csv files created using \ref br_eval. * \param destination Basename for the resulting figures. @@ -298,7 +304,9 @@ BR_EXPORT bool br_plot_detection(int num_files, const char *files[], const char * \brief Renders landmarking performance figures for a set of .csv files created by \ref br_eval_landmarking. * * In order of their output, the figures are: - * -# Normalized error box plots (Box) + * -# Cumulative landmarks less than normalized error (CD) + * -# Normalized error box and whisker plots (Box) + * -# Normalized error violin plots (Violin) * * Landmarking error is normalized against the distance between two predifined points, usually inter-ocular distance (IOD). * diff --git a/openbr/plugins/frames.cpp b/openbr/plugins/frames.cpp index 586bcae..fb7f507 100644 --- a/openbr/plugins/frames.cpp +++ b/openbr/plugins/frames.cpp @@ -52,11 +52,6 @@ private: { (void) stream; } - - void init() - { - TimeVaryingTransform::init(); - } }; BR_REGISTER(Transform, AggregateFrames) diff --git a/openbr/plugins/gui.cpp b/openbr/plugins/gui.cpp index 96f1c94..d48797b 100644 --- a/openbr/plugins/gui.cpp +++ b/openbr/plugins/gui.cpp @@ -434,8 +434,6 @@ public: if (!Globals->useGui) return; - TimeVaryingTransform::init(); - if (displayBuffer) delete displayBuffer; displayBuffer = new QPixmap(); @@ -714,7 +712,6 @@ public: target_wait = 1000.0 / targetFPS; timer.start(); last_time = timer.elapsed(); - TimeVaryingTransform::init(); } protected: @@ -768,7 +765,6 @@ public: { initialized = false; framesSeen = 0; - TimeVaryingTransform::init(); } protected: diff --git a/openbr/plugins/openbr_internal.h b/openbr/plugins/openbr_internal.h index 5eeb31f..84ff5ee 100644 --- a/openbr/plugins/openbr_internal.h +++ b/openbr/plugins/openbr_internal.h @@ -110,12 +110,12 @@ public: virtual void project(const Template &src, Template &dst) const { - timeInvariantAlias->project(src,dst); + timeInvariantAlias.project(src,dst); } virtual void project(const TemplateList &src, TemplateList &dst) const { - timeInvariantAlias->project(src,dst); + timeInvariantAlias.project(src,dst); } // Get a compile failure if this isn't here to go along with the other @@ -144,21 +144,13 @@ public: return this->clone(); } - void init() - { - delete timeInvariantAlias; - timeInvariantAlias = new TimeInvariantWrapperTransform(this); - } - protected: - Transform * timeInvariantAlias; - TimeVaryingTransform(bool independent = true, bool trainable = true) : Transform(independent, trainable) + // Since copies aren't actually made until project is called, we can set up + // timeInvariantAlias in the constructor. + TimeInvariantWrapperTransform timeInvariantAlias; + TimeVaryingTransform(bool independent = true, bool trainable = true) : Transform(independent, trainable), timeInvariantAlias(this) { - timeInvariantAlias = NULL; - } - ~TimeVaryingTransform() - { - delete timeInvariantAlias; + // } }; @@ -177,7 +169,7 @@ public: virtual void project(const Template &src, Template &dst) const { if (timeVarying()) { - timeInvariantAlias->project(src,dst); + timeInvariantAlias.project(src,dst); return; } _project(src, dst); @@ -186,7 +178,7 @@ public: virtual void project(const TemplateList &src, TemplateList &dst) const { if (timeVarying()) { - timeInvariantAlias->project(src,dst); + timeInvariantAlias.project(src,dst); return; } _project(src, dst); @@ -203,10 +195,6 @@ public: isTimeVarying = isTimeVarying || transform->timeVarying(); trainable = trainable || transform->trainable; } - - // If we are time varying, set up timeInvariantAlias - if (this->timeVarying()) - TimeVaryingTransform::init(); } /*! diff --git a/openbr/plugins/stasm4.cpp b/openbr/plugins/stasm4.cpp index fa7f271..c8195b1 100644 --- a/openbr/plugins/stasm4.cpp +++ b/openbr/plugins/stasm4.cpp @@ -54,6 +54,9 @@ class StasmTransform : public UntrainableTransform { Q_OBJECT + Q_PROPERTY(bool stasm3Format READ get_stasm3Format WRITE set_stasm3Format RESET reset_stasm3Format STORED false) + BR_PROPERTY(bool, stasm3Format, false) + Resource stasmCascadeResource; void init() @@ -69,14 +72,20 @@ class StasmTransform : public UntrainableTransform StasmCascadeClassifier *stasmCascade = stasmCascadeResource.acquire(); int foundface; + int nLandmarks = stasm_NLANDMARKS; float landmarks[2 * stasm_NLANDMARKS]; stasm_search_single(&foundface, landmarks, reinterpret_cast(src.m().data), src.m().cols, src.m().rows, *stasmCascade, NULL, NULL); + if (stasm3Format) { + nLandmarks = 76; + stasm_convert_shape(landmarks, nLandmarks); + } + stasmCascadeResource.release(stasmCascade); if (!foundface) qWarning("No face found in %s", qPrintable(src.file.fileName())); else { - for (int i = 0; i < stasm_NLANDMARKS; i++) + for (int i = 0; i < nLandmarks; i++) dst.file.appendPoint(QPointF(landmarks[2 * i], landmarks[2 * i + 1])); } diff --git a/openbr/plugins/stream.cpp b/openbr/plugins/stream.cpp index 898b089..15944be 100644 --- a/openbr/plugins/stream.cpp +++ b/openbr/plugins/stream.cpp @@ -1300,11 +1300,6 @@ public: if (!transform) return; - // Set up timeInvariantAlias - // this is only safe because copies are actually made in project - // calls, not during init. - TimeVaryingTransform::init(); - trainable = transform->trainable; basis.setParent(this->parent());