diff --git a/docs/docs/api_docs/c_api/functions.md b/docs/docs/api_docs/c_api/functions.md
index 4c54895..d9f241b 100644
--- a/docs/docs/api_docs/c_api/functions.md
+++ b/docs/docs/api_docs/c_api/functions.md
@@ -595,14 +595,15 @@ In order of their output, the figures are:
1. Metadata table
2. Receiver Operating Characteristic (ROC)
3. Detection Error Tradeoff (DET)
-4. Score Distribution (SD) histogram
-5. True Accept Rate Bar Chart (BC)
-6. Cumulative Match Characteristic (CMC)
-7. Error Rate (ERR) curve
+4. Identification Error Tradeoff (IET)
+5. Cumulative Match Characteristic (CMC)
+6. Score Distribution (SD) histogram
+7. True Accept Rate Bar Chart (BC)
+8. Error Rate (ERR) curve
Two files will be created:
* **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.
+ * **destination.pdf** which has all of the figures in one file multi-page file (note that **destination%1d.png** will output each figure to a separate 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).
@@ -611,6 +612,37 @@ The '_' character plays a special role in determining the legend title(
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.
Matches around the EER will be displayed if the matches parameter is set in [br_eval](#br_eval).
+It is possible to customize some aspects of your plots using the [File](../cpp_api/file/file.md) key/value metadata convention; possible keys are described below.
+
+
+Key | Value | Description
+--- | ---- | -----------
+smooth | [QString] | The file pivot to average across evaluation splits. Typically "Dataset" if using the folder name from above.
+ncol | int | Number of columns in plot legends.
+confidence | float | Confidence interval calculated for smooth, defaults to `0.95`
+metadata | bool | Optional plot metadata, defaults to `true`
+csv | bool | Optional output metadata tables to csv, defaults to `false`
+\*Options | [QStringList] | Key/value list of options for a specific plot. Plots include "roc", "det", "iet", "cmc"
+
+Specific plot options are described below:
+
+Key | Value | Description
+--- | ---- | -----------
+title | [QString] | Plot title
+size | float | Line width
+legendPosition | [QPointF] | Legend coordinates on plot, ex. legendPosition=(X,Y)
+textSize | float | Size of text for title, legend and axes
+xTitle/yTitle | [QString] | Title for x/y axis
+xLog/yLog | bool | Plot log scale for x/y axis
+xLimits/yLimits | [QPointF] | Set x/y axis limits, ex. xLimits=(lower,upper)
+xLabels/yLabels | [QString] | Labels for ticks on x/y axis, ex. xLabeles=percent or xLabels=c(1,5,10)
+xBreaks/yBreaks | [QString] | Specify breaks/ticks on x/y axis, ex. xBreaks=pretty_breaks(n=10) or xBreaks=c(1,5,10)
+
+If specifying plot options it is a good idea to wrap the destination file in single quotes to avoid parsing errors.
+The example below plots plots the six br_eval results in the Algorithm_Dataset folder described above, sets the number of legend columns and specifies some options for the CMC plot.
+
+`br -plot Algorithm_Dataset/* 'destination.pdf[ncol=3,cmcOptions=[xLog=false,xLimits=(1,20),xBreaks=pretty_breaks(n=10),xTitle=Ranks 1 through 20]]'`
+
This function requires a current [R][R] installation with the following packages:
install.packages(c("ggplot2", "gplots", "reshape", "scales", "jpg", "png"))
@@ -1395,3 +1427,6 @@ Close a provided [Gallery](../cpp_api/gallery/gallery.md).
[R]: http://www.r-project.org/ "R"
[QRegExp]: http://doc.qt.io/qt-5/QRegExp.html "QRegExp"
+[QString]: http://doc.qt.io/qt-5/QString.html "QString"
+[QStringList]: http://doc.qt.io/qt-5/QStringList.html "QStringList"
+[QPointF]: http://doc.qt.io/qt-5/QPointF.html "QPointF"
diff --git a/openbr/core/plot.cpp b/openbr/core/plot.cpp
index 15c92ed..862c8d7 100644
--- a/openbr/core/plot.cpp
+++ b/openbr/core/plot.cpp
@@ -42,20 +42,6 @@ static QString getScale(const QString &mode, const QString &title, int vals)
// Custom sorting method to ensure datasets are ordered nicely
static bool sortFiles(const QString &fileA, const QString &fileB)
{
- QFileInfo fileInfoA(fileA);
- QFileInfo fileInfoB(fileB);
-
- if (fileInfoA.fileName().contains("Good")) return true;
- if (fileInfoB.fileName().contains("Good")) return false;
- if (fileInfoA.fileName().contains("Bad")) return true;
- if (fileInfoB.fileName().contains("Bad")) return false;
- if (fileInfoA.fileName().contains("Ugly")) return true;
- if (fileInfoB.fileName().contains("Ugly")) return false;
- if (fileInfoA.fileName().contains("MEDS")) return true;
- if (fileInfoB.fileName().contains("MEDS")) return false;
- if (fileInfoA.fileName().contains("PCSO")) return true;
- if (fileInfoB.fileName().contains("PCSO")) return false;
-
return fileA < fileB;
}
@@ -65,7 +51,8 @@ struct RPlot
QFile file;
QStringList pivotHeaders;
QVector< QSet > pivotItems;
- float confidence;
+ float confidence; // confidence interval for plotting across splits
+ int ncol; // Number of columns for plot legends
bool flip;
@@ -124,6 +111,7 @@ struct RPlot
}
file.write("data <- rbind(data, tmp)\n");
}
+
for (int i=0; i major.size) {
@@ -133,13 +121,10 @@ struct RPlot
minor = Pivot(i, size, pivotHeaders[i]);
}
}
+
const QString &smooth = destination.get("smooth", "");
- if (destination.contains(QString("confidence"))) {
- const QString &CI = destination.get("confidence");
- confidence = !CI.isEmpty() ? CI.toFloat()/100.0 : 0.95;
- } else {
- confidence = 0.95;
- }
+ confidence = destination.get("confidence", 95) / 100.0;
+
major.smooth = !smooth.isEmpty() && (major.header == smooth) && (major.size > 1);
minor.smooth = !smooth.isEmpty() && (minor.header == smooth) && (minor.size > 1);
if (major.smooth) major.size = 1;
@@ -147,6 +132,7 @@ struct RPlot
if (major.size < minor.size)
std::swap(major, minor);
+ ncol = destination.get("ncol", major.size > 1 ? major.size : (minor.header.isEmpty() ? major.size : minor.size));
flip = minor.header == "Algorithm";
// Format data
if (isEvalFormat)
@@ -187,7 +173,7 @@ struct RPlot
"TS$Y <- as.character(TS$Y)\n"
"CMC$Y <- as.numeric(as.character(CMC$Y))\n"
"\n"
- "if (%1) {\n\tsummarySE <- function(data=NULL, measurevar, groupvars=NULL, na.rm=FALSE, conf.interval=%7, .drop=TRUE) {\n\t\t"
+ "if (%1) {\n\tsummarySE <- function(data=NULL, measurevar, groupvars=NULL, na.rm=FALSE, conf.interval=%3, .drop=TRUE) {\n\t\t"
"require(plyr)\n\n\t\tlength2 <- function (x, na.rm=FALSE) {\n\t\t\tif (na.rm) sum(!is.na(x))\n\t\t\telse length(x)"
"\n\t\t}\n\n\t\tdatac <- ddply(data, groupvars, .drop=.drop, .fun = function(xx, col) {\n\t\t\t"
"c(N=length2(xx[[col]], na.rm=na.rm), mean=mean(xx[[col]], na.rm=na.rm), sd=sd(xx[[col]], na.rm=na.rm))\n\t\t\t},"
@@ -197,6 +183,7 @@ struct RPlot
"datac$lower <- if(datac[, measurevar] - datac$ci > 0) (datac[, measurevar] - datac$ci) else 0\n\n\t\treturn(datac)\n\t}\n\t"
"DET <- summarySE(DET, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
"IET <- summarySE(IET, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
+ "CMC <- summarySE(CMC, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
"ERR <- summarySE(ERR, measurevar=\"X\", groupvars=c(\"Error\", \"%2\", \"Y\"))\n\t"
"TF <- summarySE(TF, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
"FT <- summarySE(FT, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
@@ -204,82 +191,112 @@ struct RPlot
"# Code to format FAR values\n"
"far_names <- list('0.001'=\"FAR = 0.1%\", '0.01'=\"FAR = 1%\")\n"
"far_labeller <- function(variable,value) { return(far_names[as.character(value)]) }\n"
- "\n"
- "# Code to format TAR@FAR table\n"
- "algs <- unique(%6)\n"
- "algs <- algs[!duplicated(algs)]\n"
- "mat <- matrix(%3,nrow=6,ncol=length(algs),byrow=FALSE)\n"
- "colnames(mat) <- algs \n"
- "rownames(mat) <- c(\"FAR = 1e-06\", \"FAR = 1e-05\", \"FAR = 1e-04\", \"FAR = 1e-03\", \"FAR = 1e-02\", \"FAR = 1e-01\")\n"
- "FTtable <- as.table(mat)\n"
- "\n"
- "# Code to format FAR@TAR table\n"
- "mat <- matrix(%4,nrow=6,ncol=length(algs),byrow=FALSE)\n"
- "colnames(mat) <- algs \n"
- "rownames(mat) <- c(\"TAR = 0.40\", \"TAR = 0.50\", \"TAR = 0.65\", \"TAR = 0.75\", \"TAR = 0.85\", \"TAR = 0.95\")\n"
- "F_at_Ttable <- as.table(mat)\n"
- "\n"
- "# Code to format CMC Table\n"
- "mat <- matrix(%5,nrow=6,ncol=length(algs),byrow=FALSE)\n"
- "colnames(mat) <- algs \n"
- "rownames(mat) <- c(\" Rank 1\", \"Rank 5\", \"Rank 10\", \"Rank 20\", \"Rank 50\", \"Rank 100\")\n"
- "CMCtable <- as.table(mat)\n"
- "\n"
- "# Code to format Template Size Table\n"
- "if (nrow(TS) != 0) {\n\t"
- "mat <- matrix(TS$Y,nrow=1,ncol=length(algs),byrow=FALSE)\n\t"
- "colnames(mat) <- algs\n\t"
- "rownames(mat) <- c(\"Template Size (bytes):\")\n\t"
- "TStable <- as.table(mat)\n}\n").arg(((major.smooth || minor.smooth) ? "TRUE" : "FALSE"), major.size > 1 ? major.header : (minor.header.isEmpty() ? major.header : minor.header),
- (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(TF$Y, 3)), round(TF$ci, 3), sep=\"\\u00b1\")" : "TF$Y",
- (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(FT$Y, 3)), round(FT$ci, 3), sep=\"\\u00b1\")" : "FT$Y",
- (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(CT$Y, 3)), round(CT$ci, 3), sep=\"\\u00b1\")" : "CT$Y",
- (major.size > 1 && minor.size > 1) && !(major.smooth || minor.smooth) ? QString("paste(TF$%1, TF$%2, sep=\"_\")").arg(major.header, minor.header) : QString("TF$%1").arg(major.size > 1 ? major.header : (minor.header.isEmpty() ? major.header : minor.header)),
- QString::number(confidence))));
+ "\n").arg((major.smooth || minor.smooth) ? "TRUE" : "FALSE",
+ major.size > 1 ? major.header : (minor.header.isEmpty() ? major.header : minor.header),
+ QString::number(confidence))));
// Open output device
file.write(qPrintable(QString("\n"
"# Open output device\n"
- "%1(\"%2.%1\")\n").arg(suffix, basename)));
-
- // Write metadata table
- if ((suffix == "pdf") && isEvalFormat) {
- file.write("\n"
- "# Write metadata table\n");
- QString textplot = "MT <- as.data.frame(Metadata[c(1,2,3,4,5),])\n"
- "par(mfrow=c(4,1))\n"
- "plot.new()\n"
- "print(title(paste(\"%1 - %2\",date(),sep=\"\\n\")))\n"
- "mat <- matrix(MT$X[c(1,2)],ncol=2)\n"
- "colnames(mat) <- c(\"Gallery\", \"Probe\")\n"
- "imageTable <- as.table(mat)\n"
- "print(textplot(imageTable,show.rownames=FALSE))\n"
- "print(title(\"Images\"))\n"
- "mat <- matrix(MT$X[c(3,4,5)],ncol=3)\n"
- "colnames(mat) <- c(\"Genuine\", \"Impostor\", \"Ignored\")\n"
- "matchTable <- as.table(mat)\n"
- "print(textplot(matchTable,show.rownames=FALSE))\n"
- "print(title(\"Matches\"))\n"
- "plot.new()\n"
- "print(title(\"Gallery * Probe = Genuine + Impostor + Ignored\"))\n"
- "plot.new()\n"
- "print(textplot(FTtable))\n"
- "print(title(\"Table of True Accept Rates at various False Accept Rates\"))\n"
- "print(textplot(F_at_Ttable))\n"
- "print(title(\"Table of False Accept Rates at various True Accept Rates\"))\n"
- "print(textplot(CMCtable))\n"
- "print(title(\"Table of retrieval rate at various ranks\"))\n"
- "if (nrow(TS) != 0) {\n\t"
- "print(textplot(TStable, cex=1.15))\n\t"
- "print(title(\"Template Size by Algorithm\"))\n}\n";
- file.write(qPrintable(textplot.arg(PRODUCT_NAME, PRODUCT_VERSION)));
- }
+ "%1(\"%2.%1\"%3)\n").arg(suffix, basename, suffix != "pdf" ? ", width=800, height=800" : "")));
// Write figures
file.write("\n"
"# Write figures\n");
}
+ void plotMetadata(bool csv)
+ {
+ file.write(qPrintable(QString("# Code to format TAR@FAR table\n"
+ "algs <- unique(%4)\n"
+ "algs <- algs[!duplicated(algs)]\n"
+ "mat <- matrix(%1,nrow=6,ncol=length(algs),byrow=FALSE)\n"
+ "colnames(mat) <- algs \n"
+ "rownames(mat) <- c(\"FAR = 1e-06\", \"FAR = 1e-05\", \"FAR = 1e-04\", \"FAR = 1e-03\", \"FAR = 1e-02\", \"FAR = 1e-01\")\n"
+ "TFtable <- as.table(mat)\n"
+ "\n"
+ "# Code to format FAR@TAR table\n"
+ "mat <- matrix(%2,nrow=6,ncol=length(algs),byrow=FALSE)\n"
+ "colnames(mat) <- algs \n"
+ "rownames(mat) <- c(\"TAR = 0.40\", \"TAR = 0.50\", \"TAR = 0.65\", \"TAR = 0.75\", \"TAR = 0.85\", \"TAR = 0.95\")\n"
+ "FTtable <- as.table(mat)\n"
+ "\n"
+ "# Code to format CMC Table\n"
+ "mat <- matrix(%3,nrow=6,ncol=length(algs),byrow=FALSE)\n"
+ "colnames(mat) <- algs \n"
+ "rownames(mat) <- c(\"Rank 1\", \"Rank 5\", \"Rank 10\", \"Rank 20\", \"Rank 50\", \"Rank 100\")\n"
+ "CMCtable <- as.table(mat)\n"
+ "\n"
+ "# Code to format Template Size Table\n"
+ "if (nrow(TS) != 0) {\n\t"
+ "mat <- matrix(TS$Y,nrow=1,ncol=length(algs),byrow=FALSE)\n\t"
+ "colnames(mat) <- algs\n\t"
+ "rownames(mat) <- c(\"Template Size (bytes):\")\n\t"
+ "TStable <- as.table(mat)\n}"
+ "\n").arg((major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(TF$Y, 3)), round(TF$ci, 3), sep=\"\\u00b1\")" : "TF$Y",
+ (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(FT$Y, 3)), round(FT$ci, 3), sep=\"\\u00b1\")" : "FT$Y",
+ (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(CT$Y, 3)), round(CT$ci, 3), sep=\"\\u00b1\")" : "CT$Y",
+ (major.size > 1 && minor.size > 1) && !(major.smooth || minor.smooth) ? QString("paste(TF$%1, TF$%2, sep=\"_\")").arg(major.header, minor.header)
+ : QString("TF$%1").arg(major.size > 1 ? major.header : (minor.header.isEmpty() ? major.header : minor.header)))));
+
+ file.write("\n# Write metadata table\n");
+ QString textplot = "MT <- as.data.frame(Metadata[c(1,2,3,4,5),])\n"
+ "par(mfrow=c(4,1))\n"
+ "plot.new()\n"
+ "print(title(paste(\"%1 - %2\",date(),sep=\"\\n\")))\n"
+ "mat <- matrix(MT$X[c(1,2)],ncol=2)\n"
+ "colnames(mat) <- c(\"Gallery\", \"Probe\")\n"
+ "imageTable <- as.table(mat)\n"
+ "print(textplot(imageTable,show.rownames=FALSE))\n"
+ "print(title(\"Images\"))\n"
+ "mat <- matrix(MT$X[c(3,4,5)],ncol=3)\n"
+ "colnames(mat) <- c(\"Genuine\", \"Impostor\", \"Ignored\")\n"
+ "matchTable <- as.table(mat)\n"
+ "print(textplot(matchTable,show.rownames=FALSE))\n"
+ "print(title(\"Matches\"))\n"
+ "plot.new()\n"
+ "print(title(\"Gallery * Probe = Genuine + Impostor + Ignored\"))\n";
+ file.write(qPrintable(textplot.arg(PRODUCT_NAME, PRODUCT_VERSION)));
+
+ if (csv)
+ textplot = QString("write.csv(TFtable,file=\"%1_TF.csv\")\n"
+ "write.csv(FTtable,file=\"%1_FT.csv\")\n"
+ "write.csv(CMCtable,file=\"%1_CMC.csv\")\n\n").arg(basename);
+ else
+ textplot = "plot.new()\n"
+ "print(textplot(TFtable))\n"
+ "print(title(\"Table of True Accept Rates at various False Accept Rates\"))\n"
+ "print(textplot(FTtable))\n"
+ "print(title(\"Table of False Accept Rates at various True Accept Rates\"))\n"
+ "print(textplot(CMCtable))\n"
+ "print(title(\"Table of retrieval rate at various ranks\"))\n"
+ "if (nrow(TS) != 0) {\n\t"
+ "print(textplot(TStable, cex=1.15))\n\t"
+ "print(title(\"Template Size by Algorithm\"))\n}\n\n";
+ file.write(qPrintable(textplot));
+ }
+
+ void qplot(QString geom, QString data, bool flipY, File opts)
+ {
+ file.write(qPrintable(QString("qplot(X, %1, data=%2, geom=\"%3\", main=\"%4\"").arg(flipY ? "1-Y" : "Y", data, geom, opts.get("title", "")) +
+ (opts.contains("size") ? QString(", size=I(%1)").arg(opts.get("size")) : QString()) +
+ (major.size > 1 ? QString(", colour=factor(%1)").arg(major.header) : QString()) +
+ (minor.size > 1 ? QString(", linetype=factor(%1)").arg(minor.header) : QString()) +
+ (QString(", xlab=\"%1\", ylab=\"%2\") + theme_minimal()").arg(opts.get("xTitle"), opts.get("yTitle"))) +
+ ((major.smooth || minor.smooth) && confidence != 0 && data != "CMC" ? QString(" + geom_errorbar(data=%1[seq(1, NROW(%1), by = 29),], aes(x=X, ymin=%2), width=0.1, alpha=I(1/2))").arg(data, flipY ? "(1-lower), ymax=(1-upper)" : "lower, ymax=upper") : QString()) +
+ (major.size > 1 ? getScale("colour", major.header, major.size) : QString()) +
+ (minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(minor.header) : QString()) +
+ (opts.getBool("xLog") ? QString(" + scale_x_log10(labels=%1, breaks=%2) + annotation_logticks(sides=\"b\")").arg(opts.get("xLabels", "trans_format(\"log10\", math_format())"), opts.get("xBreaks", "waiver()"))
+ : QString(" + scale_x_continuous(labels=%1, breaks=%2)").arg(opts.get("xLabels", "percent"), opts.get("xBreaks", "pretty_breaks(n=10)"))) +
+ (opts.getBool("yLog") ? QString(" + scale_y_log10(labels=%1, breaks=%2) + annotation_logticks(sides=\"l\")").arg(opts.get("yLabels", "trans_format(\"log10\", math_format())"), opts.get("yBreaks", "waiver()"))
+ : QString(" + scale_y_continuous(labels=%1, breaks=%2)").arg(opts.get("yLabels", "percent"), opts.get("yBreaks", "pretty_breaks(n=10)"))) +
+ (opts.contains("xLimits") ? QString(" + xlim%1").arg(QtUtils::toString(opts.get("xLimits", QPointF()))) : QString()) +
+ (opts.contains("yLimits") ? QString(" + ylim%1").arg(QtUtils::toString(opts.get("yLimits", QPointF()))) : QString()) +
+ QString(" + theme(legend.title = element_text(size = %1), legend.text = element_text(size = %1), plot.title = element_text(size = %1), axis.text = element_text(size = %1), axis.title.x = element_text(size = %1), axis.title.y = element_text(size = %1),").arg(QString::number(opts.get("textSize",12))) +
+ QString(" legend.position=%1, legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = \"gray\"), panel.grid.minor = element_line(colour = \"gray\", linetype = \"dashed\"))").arg(opts.contains("legendPosition") ? "c"+QtUtils::toString(opts.get("legendPosition")) : "'bottom'") +
+ QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(ncol)));
+ }
+
bool finalize(bool show = false)
{
file.write("dev.off()\n");
@@ -297,66 +314,31 @@ bool Plot(const QStringList &files, const File &destination, bool show)
{
qDebug("Plotting %d file(s) to %s", files.size(), qPrintable(destination));
- const bool minimalist = destination.getBool("minimalist");
- const bool uncertainty = destination.get("uncertainty");
+ RPlot p(files, destination);
// Use a br::file for simple storage of plot options
- File cmcOpts;
- const QStringList cmcOptions = destination.get("cmcOptions", QStringList());
- foreach (const QString& option, cmcOptions) {
- QStringList words = QtUtils::parse(option, '=');
- QtUtils::checkArgsSize(words[0],words,1,2);
- cmcOpts.set(words[0],words[1]);
- }
-
- File rocOpts;
- const QStringList rocOptions = destination.get("rocOptions", QStringList());
- foreach (const QString& option, rocOptions) {
- QStringList words = QtUtils::parse(option, '=');
- QtUtils::checkArgsSize(words[0],words,1,2);
- rocOpts.set(words[0],words[1]);
+ QMap optMap;
+ optMap.insert("rocOptions", File(QString("[xTitle=False Accept Rate,yTitle=True Accept Rate,xLog=true,yLog=false]")));
+ optMap.insert("detOptions", File(QString("[xTitle=False Accept Rate,yTitle=False Reject Rate,xLog=true,yLog=true]")));
+ optMap.insert("ietOptions", File(QString("[xTitle=False Positive Identification Rate (FPIR),yTitle=False Negative Identification Rate (FNIR),xLog=true,yLog=true]")));
+ optMap.insert("cmcOptions", File(QString("[xTitle=Rank,yTitle=Retrieval Rate,xLog=true,yLog=false,size=1,xLabels=c(1,5,10,50,100),xBreaks=c(1,5,10,50,100)]")));
+
+ foreach (const QString &key, optMap.keys()) {
+ const QStringList options = destination.get(key, QStringList());
+ foreach (const QString &option, options) {
+ QStringList words = QtUtils::parse(option, '=');
+ QtUtils::checkArgsSize(words[0], words, 1, 2);
+ optMap[key].set(words[0], words[1]);
+ }
}
-
- RPlot p(files, destination);
-
- p.file.write(qPrintable(QString("qplot(X, 1-Y, data=DET, geom=\"line\", main=\"%1\"").arg(rocOpts.get("title",QString())) +
- (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
- (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
- QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()") +
- ((p.major.smooth || p.minor.smooth) && p.confidence != 0 ? " + geom_errorbar(data=DET[seq(1, NROW(DET), by = 29),], aes(x=X, ymin=(1-lower), ymax=(1-upper)), width=0.1, alpha=I(1/2))" : QString()) +
- (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=trans_format(\"log10\", math_format()))") +
- (rocOpts.contains("yLimits") ? QString(" + scale_y_continuous(labels=percent) + coord_cartesian(ylim=%1)").arg("c"+QtUtils::toString(rocOpts.get("yLimits",QPointF()))) : QString(" + scale_y_continuous(labels=percent)")) +
- QString(" + annotation_logticks(sides=\"b\")") +
- QString(" + theme(legend.title = element_text(size = %1), plot.title = element_text(size = %1), axis.text = element_text(size = %1), axis.title.x = element_text(size = %1), axis.title.y = element_text(size = %1),"
- " legend.position=%2, legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = \"gray\"), panel.grid.minor = element_line(colour = \"gray\", linetype = \"dashed\"), legend.text = element_text(size = %1))").arg(QString::number(rocOpts.get("textSize",12)), rocOpts.contains("legendPosition") ? "c"+QtUtils::toString(rocOpts.get("legendPosition")) : "'bottom'") +
- QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(destination.get("ncol", p.major.size > 1 ? p.major.size : (p.minor.header.isEmpty() ? p.major.size : p.minor.size)))));
-
- p.file.write(qPrintable(QString("qplot(X, Y, data=DET, geom=\"line\"") +
- (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
- (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
- QString(", xlab=\"False Accept Rate\", ylab=\"False Reject Rate\") + geom_abline(alpha=0.5, colour=\"grey\", linetype=\"dashed\") + theme_minimal()") +
- ((p.major.smooth || p.minor.smooth) && p.confidence != 0 ? " + geom_errorbar(data=DET[seq(1, NROW(DET), by = 29),], aes(x=X, ymin=lower, ymax=upper), width=0.1, alpha=I(1/2))" : QString()) +
- (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(" + theme(legend.title = element_text(size = %1), plot.title = element_text(size = %1), axis.text = element_text(size = %1), axis.title.x = element_text(size = %1), axis.title.y = element_text(size = %1),"
- " legend.position=%2, legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = \"gray\"), panel.grid.minor = element_line(colour = \"gray\", linetype = \"dashed\"), legend.text = element_text(size = %1))").arg(QString::number(rocOpts.get("textSize",12)), rocOpts.contains("legendPosition") ? "c"+QtUtils::toString(rocOpts.get("legendPosition")) : "'bottom'") +
- QString(" + scale_x_log10(labels=trans_format(\"log10\", math_format())) + scale_y_log10(labels=trans_format(\"log10\", math_format())) + annotation_logticks()") +
- QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(destination.get("ncol", p.major.size > 1 ? p.major.size : (p.minor.header.isEmpty() ? p.major.size : p.minor.size)))));
-
- p.file.write(qPrintable(QString("qplot(X, Y, data=IET, geom=\"line\"") +
- (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
- (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
- QString(", xlab=\"False Positive Identification Rate (FPIR)\", ylab=\"False Negative Identification Rate (FNIR)\") + theme_minimal()") +
- ((p.major.smooth || p.minor.smooth) && p.confidence != 0 ? " + geom_errorbar(data=IET[seq(1, NROW(IET), by = 29),], aes(x=X, ymin=lower, ymax=upper), width=0.1, alpha=I(1/2))" : QString()) +
- (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(" + theme(legend.title = element_text(size = %1), plot.title = element_text(size = %1), axis.text = element_text(size = %1), axis.title.x = element_text(size = %1), axis.title.y = element_text(size = %1),"
- " legend.position=%2, legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = \"gray\"), panel.grid.minor = element_line(colour = \"gray\", linetype = \"dashed\"), legend.text = element_text(size = %1))").arg(QString::number(rocOpts.get("textSize",12)), rocOpts.contains("legendPosition") ? "c"+QtUtils::toString(rocOpts.get("legendPosition")) : "'bottom'") +
- QString(" + scale_x_log10(labels=trans_format(\"log10\", math_format())) + scale_y_log10(labels=trans_format(\"log10\", math_format())) + annotation_logticks()") +
- QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(destination.get("ncol", p.major.size > 1 ? p.major.size : (p.minor.header.isEmpty() ? p.major.size : p.minor.size)))));
+ // optional plot metadata and accuracy tables
+ if (destination.getBool("metadata", true))
+ p.plotMetadata(destination.getBool("csv", false));
+ p.qplot("line", "DET", true, optMap["rocOptions"]);
+ p.qplot("line", "DET", false, optMap["detOptions"]);
+ p.qplot("line", "IET", false, optMap["ietOptions"]);
+ p.qplot("line", "CMC", false, optMap["cmcOptions"]);
p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") +
QString(", xlab=\"Score\", ylab=\"Frequency\"") +
@@ -364,17 +346,6 @@ bool Plot(const QStringList &files, const File &destination, bool show)
(p.major.size > 1 ? (p.minor.size > 1 ? QString(" + facet_grid(%2 ~ %1, scales=\"free\")").arg((p.flip ? p.major.header : p.minor.header), (p.flip ? p.minor.header : p.major.header)) : QString(" + facet_wrap(~ %1, scales = \"free\")").arg(p.major.header)) : QString()) +
QString(" + theme(aspect.ratio=1)\n\n")));
- p.file.write(qPrintable(QString("ggplot(CMC, aes(x=X, y=Y%1%2)) + ggtitle(\"%3\") + xlab(\"Rank\") + ylab(\"Retrieval Rate\")").arg(p.major.size > 1 ? QString(" ,colour=factor(%1)").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString(), cmcOpts.get("title",QString())) +
- QString(((p.major.smooth || p.minor.smooth) ? (!uncertainty ? " + stat_summary(geom=\"line\", fun.y=mean, size=%1)" : " + stat_summary(geom=\"line\", fun.y=min, aes(linetype=\"Min/Max\"), size=%1) + stat_summary(geom=\"line\", "
- "fun.y=max, aes(linetype=\"Min/Max\"), size=%1) + stat_summary(geom=\"line\", fun.y=mean, aes(linetype=\"Mean\"), size=%1) + scale_linetype_manual(\"Legend\", values=c(\"Mean\"=1, \"Min/Max\"=2))") : " + geom_line(size=%1)")).arg(QString::number(cmcOpts.get("thickness",1))) +
- (minimalist ? "" : " + scale_x_log10(labels=c(1,5,10,50,100), breaks=c(1,5,10,50,100)) + annotation_logticks(sides=\"b\")") +
- (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()) +
- (cmcOpts.contains("yLimits") ? QString(" + scale_y_continuous(labels=percent) + coord_cartesian(ylim=%1)").arg("c"+QtUtils::toString(cmcOpts.get("yLimits",QPointF()))) : QString(" + scale_y_continuous(labels=percent)")) +
- QString(" + theme_minimal() + theme(legend.title = element_text(size = %1), plot.title = element_text(size = %1), axis.text = element_text(size = %1), axis.title.x = element_text(size = %1), axis.title.y = element_text(size = %1),"
- " legend.position=%2, legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = \"gray\"), panel.grid.minor = element_line(colour = \"gray\", linetype = \"dashed\"), legend.text = element_text(size = %1))").arg(QString::number(cmcOpts.get("textSize",12)), cmcOpts.contains("legendPosition") ? "c"+QtUtils::toString(cmcOpts.get("legendPosition")) : "'bottom'") +
- QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(destination.get("ncol", p.major.size > 1 ? p.major.size : (p.minor.header.isEmpty() ? p.major.size : p.minor.size)))));
-
p.file.write(qPrintable(QString("qplot(factor(%1)%2, data=BC, %3").arg(p.major.smooth ? (p.minor.header.isEmpty() ? "Algorithm" : p.minor.header) : p.major.header, (p.major.smooth || p.minor.smooth) ? ", Y" : "", (p.major.smooth || p.minor.smooth) ? "geom=\"boxplot\"" : "geom=\"bar\", position=\"dodge\", weight=Y") +
(p.major.size > 1 ? QString(", fill=factor(%1)").arg(p.major.header) : QString()) +
QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()") +
@@ -451,6 +422,20 @@ bool PlotDetection(const QStringList &files, const File &destination, bool show)
qDebug("Plotting %d detection file(s) to %s", files.size(), qPrintable(destination));
RPlot p(files, destination, false);
+ // Use a br::file for simple storage of plot options
+ QMap optMap;
+ optMap.insert("rocOptions", File(QString("[xTitle=False Accepts Per Image,yTitle=True Accept Rate,xLog=true,yLog=false]")));
+ optMap.insert("prOptions", File(QString("[xTitle=False Accept Rate,yTitle=False Reject Rate,xLog=true,yLog=true]")));
+
+ foreach (const QString &key, optMap.keys()) {
+ const QStringList options = destination.get(key, QStringList());
+ foreach (const QString &option, options) {
+ QStringList words = QtUtils::parse(option, '=');
+ QtUtils::checkArgsSize(words[0], words, 1, 2);
+ optMap[key].set(words[0], words[1]);
+ }
+ }
+
p.file.write("# Split data into individual plots\n"
"plot_index = which(names(data)==\"Plot\")\n"
"DiscreteROC <- data[grep(\"DiscreteROC\",data$Plot),-c(1)]\n"
@@ -466,23 +451,15 @@ bool PlotDetection(const QStringList &files, const File &destination, bool show)
if (filesHaveSinglePoint(files))
plotType = QString("point");
- foreach (const QString &type, QStringList() << "Discrete" << "Continuous")
- 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" : QString(", geom=\"%1\"").arg(plotType)) +
- (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
- (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
- QString(", xlab=\"False Accepts Per Image\", 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() + scale_y_continuous(labels=percent, limits=c(0,1)) + annotation_logticks(sides=\"b\") + ggtitle(\"%1\") + theme(legend.position=\"bottom\")\n\n").arg(type)));
-
- foreach (const QString &type, QStringList() << "Discrete" << "Continuous")
- 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" : QString(", geom=\"%1\"").arg(plotType)) +
- (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
- (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
- QString(", xlab=\"Recall\", ylab=\"Precision\") + 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_continuous(limits=c(0,1)) + scale_y_continuous(labels=percent, limits=c(0,1)) + ggtitle(\"%1\") + theme(legend.position=\"bottom\")\n\n").arg(type)));
+ foreach (const QString &type, QStringList() << "Discrete" << "Continuous") {
+ optMap["rocOptions"].set("title", type);
+ p.qplot(plotType, type + "ROC", false, optMap["rocOptions"]);
+ }
+
+ foreach (const QString &type, QStringList() << "Discrete" << "Continuous") {
+ optMap["prOptions"].set("title", type);
+ p.qplot(plotType, type + "PR", false, optMap["prOptions"]);
+ }
p.file.write(qPrintable(QString("qplot(X, data=Overlap, geom=\"histogram\", position=\"identity\", xlab=\"Overlap\", ylab=\"Frequency\")") +
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))") +