diff --git a/docs/docs/api_docs/c_api/functions.md b/docs/docs/api_docs/c_api/functions.md index d9f241b..26106de 100644 --- a/docs/docs/api_docs/c_api/functions.md +++ b/docs/docs/api_docs/c_api/functions.md @@ -635,8 +635,8 @@ 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) +xLabels/yLabels | [QString] | Labels for ticks on x/y axis, ex. xLabeles=percent or xLabels=(1,5,10) +xBreaks/yBreaks | [QString] | Specify breaks/ticks on x/y axis, ex. xBreaks=pretty_breaks(n=10) or xBreaks=(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. diff --git a/openbr/core/plot.cpp b/openbr/core/plot.cpp index 862c8d7..94f84dd 100644 --- a/openbr/core/plot.cpp +++ b/openbr/core/plot.cpp @@ -23,6 +23,25 @@ using namespace cv; namespace br { +// Flattens file metadata to an R list() +static QString toRList(const File &opts) +{ + QStringList retValues; + QString format = "%1=%2"; + foreach (const QString &key, opts.localKeys()) { + const QString value = QtUtils::toString(opts.value(key)); + if (value.startsWith("(")) + retValues.append(format.arg(key, "\"c" + value + "\"")); + else if (value == "true") + retValues.append(format.arg(key, "TRUE")); + else if (value == "false") + retValues.append(format.arg(key, "FALSE")); + else + retValues.append(format.arg(key, "\"" + value + "\"")); + } + return retValues.join(","); +} + static QStringList getPivots(const QString &file, bool headers) { QString str; @@ -31,14 +50,6 @@ static QStringList getPivots(const QString &file, bool headers) return str.split("_"); } -static QString getScale(const QString &mode, const QString &title, int vals) -{ - if (vals > 12) return " + scale_"+mode+"_discrete(\""+title+"\")"; - else if (vals > 11) return " + scale_"+mode+"_brewer(\""+title+"\", palette=\"Set3\")"; - else if (vals > 9) return " + scale_"+mode+"_brewer(\""+title+"\", palette=\"Paired\")"; - else return " + scale_"+mode+"_brewer(\""+title+"\", palette=\"Set1\")"; -} - // Custom sorting method to ensure datasets are ordered nicely static bool sortFiles(const QString &fileA, const QString &fileB) { @@ -51,10 +62,6 @@ struct RPlot QFile file; QStringList pivotHeaders; QVector< QSet > pivotItems; - float confidence; // confidence interval for plotting across splits - int ncol; // Number of columns for plot legends - - bool flip; struct Pivot { @@ -68,7 +75,7 @@ struct RPlot Pivot major, minor; - RPlot(QStringList files, const File &destination, bool isEvalFormat = true) + RPlot(QStringList files, const File &destination) { if (files.isEmpty()) qFatal("Empty file list."); qSort(files.begin(), files.end(), sortFiles); @@ -83,13 +90,9 @@ struct RPlot bool success = file.open(QFile::WriteOnly); if (!success) qFatal("Failed to open %s for writing.", qPrintable(file.fileName())); - file.write("# Load libraries\n" - "library(ggplot2)\n" - "library(gplots)\n" - "library(reshape)\n" - "library(scales)\n" - "\n" - "# Read CSVs\n" + // Copy plot_utils.R into output script with source() + file.write(qPrintable(QString("source(\"%1\")\n\n").arg(Globals->sdkPath + "/share/openbr/plotting/plot_utils.R"))); + file.write("# Read CSVs\n" "data <- NULL\n"); // Read files and retrieve pivots @@ -123,8 +126,6 @@ struct RPlot } const QString &smooth = destination.get("smooth", ""); - 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; @@ -132,68 +133,19 @@ 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) - file.write(qPrintable(QString("\n" - "# Split data into individual plots\n" - "plot_index = which(names(data)==\"Plot\")\n" - "Metadata <- data[grep(\"Metadata\",data$Plot),-c(1)]\n" - "IM <- data[grep(\"IM\",data$Plot),-c(1)]\n" - "GM <- data[grep(\"GM\",data$Plot),-c(1)]\n" - "DET <- data[grep(\"DET\",data$Plot),-c(1)]\n" - "IET <- data[grep(\"IET\",data$Plot),-c(1)]\n" - "FAR <- data[grep(\"FAR\",data$Plot),-c(1)]\n" - "FRR <- data[grep(\"FRR\",data$Plot),-c(1)]\n" - "SD <- data[grep(\"SD\",data$Plot),-c(1)]\n" - "TF <- data[grep(\"TF\",data$Plot),-c(1)]\n" - "FT <- data[grep(\"FT\",data$Plot),-c(1)]\n" - "CT <- data[grep(\"CT\",data$Plot),-c(1)]\n" - "BC <- data[grep(\"BC\",data$Plot),-c(1)]\n" - "TS <- data[grep(\"TS\",data$Plot),-c(1)]\n" - "CMC <- data[grep(\"CMC\",data$Plot),-c(1)]\n" - "FAR$Error <- \"FAR\"\n" - "FRR$Error <- \"FRR\"\n" - "ERR <- rbind(FAR, FRR)\n" - "rm(data, FAR, FRR)\n" - "\n" - "# Format data\n" - "Metadata$Y<-factor(Metadata$Y, levels=c(\"Genuine\",\"Impostor\",\"Ignored\",\"Gallery\",\"Probe\"))\n" - "IM$Y <- as.character(IM$Y)\n" - "GM$Y <- as.character(GM$Y)\n" - "DET$Y <- as.numeric(as.character(DET$Y))\n" - "IET$Y <- as.numeric(as.character(IET$Y))\n" - "ERR$Y <- as.numeric(as.character(ERR$Y))\n" - "SD$Y <- as.factor(unique(as.character(SD$Y)))\n" - "TF$Y <- as.numeric(as.character(TF$Y))\n" - "FT$Y <- as.numeric(as.character(FT$Y))\n" - "CT$Y <- as.numeric(as.character(CT$Y))\n" - "BC$Y <- as.numeric(as.character(BC$Y))\n" - "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=%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}," - "\n\t\t\tmeasurevar\n\t\t)\n\n\t\tdatac <- rename(datac, c(\"mean\" = measurevar))\n\t\tdatac$se <- datac$sd / sqrt(datac$N)" - "\n\t\tciMult <- qt(conf.interval/2 + .5, datac$N-1)\n\t\tdatac$ci <- datac$se * ciMult\n\n\t\t" - "datac$upper <- if(datac[, measurevar] + datac$ci < 1) (datac[, measurevar] + datac$ci) else 1\n\t\t" - "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" - "CT <- summarySE(CT, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n}\n\n" - "# 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").arg((major.smooth || minor.smooth) ? "TRUE" : "FALSE", - major.size > 1 ? major.header : (minor.header.isEmpty() ? major.header : minor.header), - QString::number(confidence)))); + // Set variables in R + file.write(qPrintable(QString("\nconfidence <- %1\n").arg(destination.get("confidence", 95) / 100.0))); + file.write(qPrintable(QString("ncol <- %1\n").arg(destination.get("ncol", major.size > 1 ? major.size : (minor.header.isEmpty() ? major.size : minor.size))))); + file.write(qPrintable(QString("basename <- \"%1\"\n").arg(basename))); + file.write(qPrintable(QString("smooth <- %1\n").arg((major.smooth || minor.smooth) && (destination.get("confidence", 95) / 100.0) != 0 ? "TRUE" : "FALSE"))); + file.write(qPrintable(QString("csv <- %1\n").arg(destination.getBool("csv") ? "TRUE" : "FALSE"))); + file.write(qPrintable(QString("majorHeader <- \"%1\"\n").arg(major.header))); + file.write(qPrintable(QString("majorSize <- %1\n").arg(major.size))); + file.write(qPrintable(QString("majorSmooth <- %1\n").arg(major.smooth ? "TRUE" : "FALSE"))); + file.write(qPrintable(QString("minorHeader <- \"%1\"\n").arg(minor.header))); + file.write(qPrintable(QString("minorSize <- %1\n").arg(minor.size))); + file.write(qPrintable(QString("minorSmooth <- %1\n").arg(minor.smooth ? "TRUE" : "FALSE"))); + file.write(qPrintable(QString("flip <- %1\n").arg(minor.header == "Algorithm" ? "TRUE" : "FALSE"))); // Open output device file.write(qPrintable(QString("\n" @@ -205,98 +157,6 @@ struct RPlot "# 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"); @@ -315,13 +175,27 @@ bool Plot(const QStringList &files, const File &destination, bool show) qDebug("Plotting %d file(s) to %s", files.size(), qPrintable(destination)); RPlot p(files, destination); + p.file.write("\nformatData()\n\n"); + p.file.write(qPrintable(QString("algs <- %1\n").arg((p.major.size > 1 && p.minor.size > 1) && !(p.major.smooth || p.minor.smooth) ? QString("paste(TF$%1, TF$%2, sep=\"_\")").arg(p.major.header, p.minor.header) + : QString("TF$%1").arg(p.major.size > 1 ? p.major.header : (p.minor.header.isEmpty() ? p.major.header : p.minor.header))))); + p.file.write("algs <- algs[!duplicated(algs)]\n"); + + if (p.major.smooth || p.minor.smooth) { + QString groupvar = p.major.size > 1 ? p.major.header : (p.minor.header.isEmpty() ? p.major.header : p.minor.header); + foreach(const QString &data, QStringList() << "DET" << "IET" << "CMC" << "TF" << "FT" << "CT") { + p.file.write(qPrintable(QString("%1 <- summarySE(%1, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"), conf.interval=confidence)" + "\n").arg(data, groupvar))); + } + p.file.write(qPrintable(QString("%1 <- summarySE(%1, measurevar=\"X\", groupvars=c(\"Error\", \"%2\", \"Y\"), conf.interval=confidence)" + "\n\n").arg("ERR", groupvar))); + } // Use a br::file for simple storage of plot options 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)]"))); + optMap.insert("cmcOptions", File(QString("[xTitle=Rank,yTitle=Retrieval Rate,xLog=true,yLog=false,size=1,xLabels=(1,5,10,50,100),xBreaks=(1,5,10,50,100)]"))); foreach (const QString &key, optMap.keys()) { const QStringList options = destination.get(key, QStringList()); @@ -333,56 +207,33 @@ bool Plot(const QStringList &files, const File &destination, bool show) } // 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\"") + - QString(") + scale_fill_manual(\"Ground Truth\", values=c(\"blue\", \"red\")) + 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))") + - (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("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()") + - (p.major.size > 1 ? getScale("fill", p.major.header, p.major.size) : QString()) + - (p.minor.size > 1 ? QString(" + facet_grid(%2 ~ X)").arg(p.minor.header) : QString(" + facet_grid(. ~ X, labeller=far_labeller)")) + - QString(" + scale_y_continuous(labels=percent) + theme(legend.position=\"none\", axis.text.x=element_text(angle=-90, hjust=0))%1").arg((p.major.smooth || p.minor.smooth) ? "" : " + geom_text(data=BC, aes(label=Y, y=0.05))") + "\n\n")); - - p.file.write(qPrintable(QString("qplot(X, Y, data=ERR, geom=\"line\", linetype=Error") + - ((p.flip ? p.major.size : p.minor.size) > 1 ? QString(", colour=factor(%1)").arg(p.flip ? p.major.header : p.minor.header) : QString()) + - QString(", xlab=\"Score\", ylab=\"Error Rate\") + theme_minimal()") + - ((p.flip ? p.major.size : p.minor.size) > 1 ? getScale("colour", p.flip ? p.major.header : p.minor.header, p.flip ? p.major.size : p.minor.size) : QString()) + - QString(" + scale_y_log10(labels=percent) + annotation_logticks(sides=\"l\")") + - ((p.flip ? p.minor.size : p.major.size) > 1 ? QString(" + facet_wrap(~ %1, scales=\"free_x\")").arg(p.flip ? p.minor.header : p.major.header) : QString()) + - QString(" + theme(aspect.ratio=1)\n\n"))); - - p.file.write(qPrintable(QString("if (nrow(IM) != 0) {\n\tlibrary(jpeg)\n\tlibrary(png)\n\tlibrary(grid)\n\t") + - QString("multiplot <- function(..., plotlist=NULL, cols) {\n\t") + - QString("\trequire(grid)\n\n\t\t# Make a list from the ... arguments and plotlist\n\t\tplots <- c(list(...), plotlist)\n") + - QString("\t\tnumPlots = length(plots)\n\n\t\t# Make the panel\n\t\tplotCols = cols\n\t\tplotRows = ceiling(numPlots/plotCols)\n\n") + - QString("\t\t# Set up the page\n\t\tgrid.newpage()\n\t\tpushViewport(viewport(layout = grid.layout(plotRows, plotCols)))\n\t\tvplayout <- function(x, y)\n\t\t\tviewport(layout.pos.row = x, layout.pos.col = y)\n\n") + - QString("\t\t# Make each plot, in the correct location\n\t\tfor (i in 1:numPlots) {\n\t\t\tcurRow = ceiling(i/plotCols)\n\t\t\tcurCol = (i-1) %% plotCols + 1\n\t\t\tprint(plots[[i]], vp = vplayout(curRow, curCol))\n\t\t}\n\t}\n\n"))); - - p.file.write(qPrintable(QString("\t# Print impostor matches above the EER\n\tfor (i in 1:nrow(IM)) {\n\t\tscore <- IM[i,1]\n\t\tfiles <- IM[i,2]\n\t\talg <- IM[i,3]\n\t\tfiles <- unlist(strsplit(files, \"[:]\"))\n\n\t\text1 <- unlist(strsplit(files[2], \"[.]\"))[2]\n\t\text2 <- unlist(strsplit(files[4], \"[.]\"))[2]\n\t\t") + - QString("if (ext1 == \"jpg\" || ext1 == \"JPEG\" || ext1 == \"jpeg\" || ext1 == \"JPG\") {\n\t\t\timg1 <- readJPEG(files[2])\n\t\t} else if (ext1 == \"PNG\" || ext1 == \"png\") {\n\t\t\timg1 <- readPNG(files[2])\n\t\t} else if (ext1 == \"TIFF\" || ext1 == \"tiff\" || ext1 == \"TIF\" || ext1 == \"tif\") {\n\t\t\timg1 <- readTIFF(files[2])\n\t\t} else {\n\t\t\tnext\n\t\t}\n\t\tif (ext2 == \"jpg\" || ext2 == \"JPEG\" || ext2 == \"jpeg\" || ext2 == \"JPG\") {\n\t\t\timg2 <- readJPEG(files[4])\n\t\t} ") + - QString("else if (ext2 == \"PNG\" || ext2 == \"png\") {\n\t\t\timg2 <- readPNG(files[4])\n\t\t} else if (ext2 == \"TIFF\" || ext2 == \"tiff\" || ext2 == \"TIF\" || ext2 == \"tif\") {\n\t\t\timg2 <- readTIFF(files[4])\n\t\t} else {\n\t\t\tnext\n\t\t}") + - QString("\n\t\tname1 <- files[1]\n\t\tname2 <- files[3]\n\n\t\tg1 <- rasterGrob(img1, interpolate=TRUE)\n\t\tg2 <- rasterGrob(img2, interpolate=TRUE)\n\n\t\t") + - QString("plot1 <- qplot(1:10, 1:10, geom=\"blank\") + annotation_custom(g1, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + theme(axis.line=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) + labs(title=alg) + ylab(unlist(strsplit(files[2], \"[/]\"))[length(unlist(strsplit(files[2], \"[/]\")))]) + xlab(name1)\n\t\t") + - QString("plot2 <- qplot(1:10, 1:10, geom=\"blank\") + annotation_custom(g2, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + theme(axis.line=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) + labs(title=paste(\"Impostor score =\", score)) + ylab(unlist(strsplit(files[4], \"[/]\"))[length(unlist(strsplit(files[4], \"[/]\")))]) + xlab(name2)\n\n\t\t") + - QString("multiplot(plot1, plot2, cols=2)\n\t}"))); - - p.file.write(qPrintable(QString("\n\n\t# Print genuine matches below the EER\n\tfor (i in 1:nrow(GM)) {\n\t\tscore <- GM[i,1]\n\t\tfiles <- GM[i,2]\n\t\talg <- GM[i,3]\n\t\tfiles <- unlist(strsplit(files, \"[:]\"))\n\n\t\text1 <- unlist(strsplit(files[2], \"[.]\"))[2]\n\t\text2 <- unlist(strsplit(files[4], \"[.]\"))[2]\n\t\t") + - QString("if (ext1 == \"jpg\" || ext1 == \"JPEG\" || ext1 == \"jpeg\" || ext1 == \"JPG\") {\n\t\t\timg1 <- readJPEG(files[2])\n\t\t} else if (ext1 == \"PNG\" || ext1 == \"png\") {\n\t\t\timg1 <- readPNG(files[2])\n\t\t} else if (ext1 == \"TIFF\" || ext1 == \"tiff\" || ext1 == \"TIF\" || ext1 == \"tif\") {\n\t\t\timg1 <- readTIFF(files[2])\n\t\t} else {\n\t\t\tnext\n\t\t}\n\t\tif (ext2 == \"jpg\" || ext2 == \"JPEG\" || ext2 == \"jpeg\" || ext2 == \"JPG\") {\n\t\t\timg2 <- readJPEG(files[4])\n\t\t} ") + - QString("else if (ext2 == \"PNG\" || ext2 == \"png\") {\n\t\t\timg2 <- readPNG(files[4])\n\t\t} else if (ext2 == \"TIFF\" || ext2 == \"tiff\" || ext2 == \"TIF\" || ext2 == \"tif\") {\n\t\t\timg2 <- readTIFF(files[4])\n\t\t} else {\n\t\t\tnext\n\t\t}") + - QString("\n\t\tname1 <- files[1]\n\t\tname2 <- files[3]\n\n\t\tg1 <- rasterGrob(img1, interpolate=TRUE)\n\t\tg2 <- rasterGrob(img2, interpolate=TRUE)\n\n\t\t") + - QString("plot1 <- qplot(1:10, 1:10, geom=\"blank\") + annotation_custom(g1, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + theme(axis.line=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) + labs(title=alg) + ylab(unlist(strsplit(files[2], \"[/]\"))[length(unlist(strsplit(files[2], \"[/]\")))]) + xlab(name1)\n\t\t") + - QString("plot2 <- qplot(1:10, 1:10, geom=\"blank\") + annotation_custom(g2, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + theme(axis.line=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) + labs(title=paste(\"Genuine score =\", score)) + ylab(unlist(strsplit(files[4], \"[/]\"))[length(unlist(strsplit(files[4], \"[/]\")))]) + xlab(name2)\n\n\t\t") + - QString("multiplot(plot1, plot2, cols=2)\n\t}\n}\n\n"))); + if (destination.getBool("metadata", true)) { + p.file.write("\n# Write metadata table\n"); + p.file.write(qPrintable(QString("plotMetadata(metadata=Metadata, title=\"%1 - %2\")\n").arg(PRODUCT_NAME, PRODUCT_VERSION))); + + if (!destination.getBool("csv")) p.file.write("plot.new()\n"); + QString table = "plotTable(tableData=%1, name=%2, labels=%3)\n"; + p.file.write(qPrintable(table.arg("TF", "\"Table of True Accept Rates at various False Accept Rates\"", + "c(\"FAR = 1e-06\", \"FAR = 1e-05\", \"FAR = 1e-04\", \"FAR = 1e-03\", \"FAR = 1e-02\", \"FAR = 1e-01\")"))); + p.file.write(qPrintable(table.arg("FT", "\"Table of False Accept Rates at various True Accept Rates\"", + "c(\"TAR = 0.40\", \"TAR = 0.50\", \"TAR = 0.65\", \"TAR = 0.75\", \"TAR = 0.85\", \"TAR = 0.95\")"))); + p.file.write(qPrintable(table.arg("CT", "\"Table of retrieval rate at various ranks\"", + "c(\"Rank 1\", \"Rank 5\", \"Rank 10\", \"Rank 20\", \"Rank 50\", \"Rank 100\")"))); + p.file.write(qPrintable(table.arg("TS", "\"Template Size by Algorithm\"", + "c(\"Template Size (bytes):\")"))); + p.file.write("\n"); + } + + // Write plots + QString plot = "plot <- plotLine(lineData=%1, options=list(%2), flipY=%3)\nplot\n"; + p.file.write(qPrintable(QString(plot).arg("DET", toRList(optMap["rocOptions"]), "TRUE"))); + p.file.write(qPrintable(QString(plot).arg("DET", toRList(optMap["detOptions"]), "FALSE"))); + p.file.write(qPrintable(QString(plot).arg("IET", toRList(optMap["ietOptions"]), "FALSE"))); + p.file.write(qPrintable(QString(plot).arg("CMC", toRList(optMap["cmcOptions"]), "FALSE"))); + p.file.write("plot <- plotSD(sdData=SD)\nplot\n"); + p.file.write("plot <- plotBC(bcData=BC)\nplot\n"); + p.file.write("plot <- plotERR(errData=ERR)\nplot\n"); + p.file.write("plotEERSamples(imData=IM, gmData=GM)\n\n"); return p.finalize(show); } @@ -420,7 +271,8 @@ bool filesHaveSinglePoint(const QStringList &files) { 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); + RPlot p(files, destination); + p.file.write("\nformatData(type=\"detection\")\n\n"); // Use a br::file for simple storage of plot options QMap optMap; @@ -436,40 +288,26 @@ bool PlotDetection(const QStringList &files, const File &destination, bool show) } } - 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" - "ContinuousROC <- data[grep(\"ContinuousROC\",data$Plot),-c(1)]\n" - "DiscretePR <- data[grep(\"DiscretePR\",data$Plot),-c(1)]\n" - "ContinuousPR <- data[grep(\"ContinuousPR\",data$Plot),-c(1)]\n" - "Overlap <- data[grep(\"Overlap\",data$Plot),-c(1)]\n" - "AverageOverlap <- data[grep(\"AverageOverlap\",data$Plot),-c(1)]\n" - "rm(data)\n" - "\n"); - QString plotType("line"); if (filesHaveSinglePoint(files)) plotType = QString("point"); + QString plot = "plot <- plotLine(lineData=%1, options=list(%2), flipY=%3, geometry=%4)\nplot\n"; foreach (const QString &type, QStringList() << "Discrete" << "Continuous") { optMap["rocOptions"].set("title", type); - p.qplot(plotType, type + "ROC", false, optMap["rocOptions"]); + p.file.write(qPrintable(QString(plot).arg(type + "ROC", toRList(optMap["rocOptions"]), "FALSE", "\"" + plotType + "\""))); } 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(plot).arg(type + "PR", toRList(optMap["prOptions"]), "FALSE", "\"" + plotType + "\""))); } - - 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))") + - (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()) + - QString(" + theme(aspect.ratio=1, legend.position=\"bottom\")\n\n"))); + p.file.write("plot <- plotOverlap(overlapData=Overlap)\nplot\n"); p.file.write(qPrintable(QString("ggplot(AverageOverlap, aes(x=%1, y=%2, label=round(X,3)), main=\"Average Overlap\") + geom_text() + theme_minimal()").arg(p.minor.size > 1 ? p.minor.header : "'X'", p.major.size > 1 ? p.major.header : "'Y'") + QString("%1%2\n\n").arg(p.minor.size > 1 ? "" : " + xlab(NULL)", p.major.size > 1 ? "" : " + ylab(NULL)"))); - p.file.write(qPrintable(QString("ggplot(AverageOverlap, aes(x=%1, y=%2, fill=X)) + geom_tile() + scale_fill_continuous(\"Average Overlap\") + theme_minimal()").arg(p.minor.size > 1 ? p.minor.header : "'X'", p.major.size > 1 ? p.major.header : "'Y'") + + p.file.write(qPrintable(QString("ggplot(AverageOverlap, aes(x=%1, y=%2, fill=X)) + geom_tile() + scale_fill_continuous(\"Average Overlap\", guide=FALSE) + theme_minimal()").arg(p.minor.size > 1 ? p.minor.header : "'X'", p.major.size > 1 ? p.major.header : "'Y'") + QString("%1%2\n\n").arg(p.minor.size > 1 ? "" : " + xlab(NULL)", p.major.size > 1 ? "" : " + ylab(NULL)"))); return p.finalize(show); @@ -478,109 +316,12 @@ bool PlotDetection(const QStringList &files, const File &destination, bool show) bool PlotLandmarking(const QStringList &files, const File &destination, bool show) { qDebug("Plotting %d landmarking file(s) to %s", files.size(), qPrintable(destination)); - RPlot p(files, destination, false); - - p.file.write(qPrintable(QString("# Split data into individual plots\n" - "plot_index = which(names(data)==\"Plot\")\n" - "Box <- data[grep(\"Box\",data$Plot),-c(1)]\n" - "Box$X <- factor(Box$X, levels = Box$X, ordered = TRUE)\n" - "Sample <- data[grep(\"Sample\",data$Plot),-c(1)]\n" - "Sample$X <- as.character(Sample$X)\n" - "EXT <- data[grep(\"EXT\",data$Plot),-c(1)]\n" - "EXT$X <- as.character(EXT$X)\n" - "EXP <- data[grep(\"EXP\",data$Plot),-c(1)]\n" - "EXP$X <- as.character(EXP$X)\n" - "NormLength <- data[grep(\"NormLength\",data$Plot),-c(1)]\n" - "rm(data)\n" - "\n"))); - - p.file.write(qPrintable(QString("summarySE <- function(data=NULL, measurevar, groupvars=NULL, na.rm=FALSE, conf.interval=.95, .drop=TRUE) {\n\t" - "require(plyr)\n\n\tlength2 <- function (x, na.rm=FALSE) {\n\t\tif (na.rm) sum(!is.na(x))\n\t\telse length(x)" - "\n\t}\n\n\tdatac <- ddply(data, groupvars, .drop=.drop, .fun = function(xx, col) {\n\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}," - "\n\t\tmeasurevar\n\t)\n\n\tdatac <- rename(datac, c(\"mean\" = measurevar))\n\tdatac$se <- datac$sd / sqrt(datac$N)" - "\n\tciMult <- qt(conf.interval/2 + .5, datac$N-1)\n\tdatac$ci <- datac$se * ciMult\n\n\treturn(datac)\n}\n"))); - - - p.file.write(qPrintable(QString("\nreadData <- function(data) {\n\texamples <- list()\n" - "\tfor (i in 1:nrow(data)) {\n" - "\t\tpath <- data[i,1]\n" - "\t\tvalue <- data[i,2]\n" - "\t\tfile <- unlist(strsplit(path, \"[.]\"))[1]\n" - "\t\text <- unlist(strsplit(path, \"[.]\"))[2]\n" - "\t\tif (ext == \"jpg\" || ext == \"JPEG\" || ext == \"jpeg\" || ext == \"JPG\") {\n" - "\t\t\timg <- readJPEG(path)\n" - "\t\t} else if (ext == \"PNG\" || ext == \"png\") {\n" - "\t\t\timg <- readPNG(path)\n" - "\t\t} else if (ext == \"TIFF\" || ext == \"tiff\" || ext == \"TIF\" || ext == \"tif\") { \n" - "\t\t\timg <- readTIFF(path)\n" - "}else {\n" - "\t\t\tnext\n" - "\t\t}\n" - "\t\texample <- list(file = file, value = value, image = img)\n" - "\t\texamples[[i]] <- example\n" - "\t}\n" - "\treturn(examples)\n" - "}\n"))); - - p.file.write(qPrintable(QString("\nlibrary(jpeg)\n" - "library(png)\n" - "library(grid)\n" - "multiplot <- function(..., plotlist=NULL, cols) {\n" - "\trequire(grid)\n" - "\t# Make a list from the ... arguments and plotlist\n" - "\tplots <- c(list(...), plotlist)\n" - "\tnumPlots = length(plots)\n" - "\t# Make the panel\n" - "\tplotCols = cols\n" - "\tplotRows = ceiling(numPlots/plotCols)\n" - "\t# Set up the page\n" - "\tgrid.newpage()\n" - "\tpushViewport(viewport(layout = grid.layout(plotRows, plotCols)))\n" - "\tvplayout <- function(x, y)\n" - "\tviewport(layout.pos.row = x, layout.pos.col = y)\n" - "\t# Make each plot, in the correct location\n" - "\tfor (i in 1:numPlots) {\n" - "\t\tcurRow = ceiling(i/plotCols)\n" - "\t\tcurCol = (i-1) %% plotCols + 1\n" - "\t\tprint(plots[[i]], vp = vplayout(curRow, curCol))\n" - "\t}\n" - "}\n"))); - - p.file.write(qPrintable(QString("\nplotImage <- function(image, title=NULL, label=NULL) { \n" - "\tp <- qplot(1:10, 1:10, geom=\"blank\") + annotation_custom(rasterGrob(image$image), xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + theme(axis.line=element_blank(), axis.title.y=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), line=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) + labs(title=title) + xlab(label)\n" - "\treturn(p)" - "}\n"))); - - p.file.write(qPrintable(QString("\nsample <- readData(Sample) \n" - "rows <- sample[[1]]$value\n" - "algs <- unique(Box$%1)\n" - "algs <- algs[!duplicated(algs)]\n" - "print(plotImage(sample[[1]],\"Sample Landmarks\",sprintf(\"Total Landmarks: %s\",sample[[1]]$value))) \n" - "if (nrow(EXT) != 0 && nrow(EXP)) {\n" - "\tfor (j in 1:length(algs)) {\n" - "\ttruthSample <- readData(EXT[EXT$. == algs[[j]],])\n" - "\tpredictedSample <- readData(EXP[EXP$. == algs[[j]],])\n" - "\t\tfor (i in 1:length(predictedSample)) {\n" - "\t\t\tmultiplot(plotImage(predictedSample[[i]],sprintf(\"%s\\nPredicted Landmarks\",algs[[j]]),sprintf(\"Average Landmark Error: %.3f\",predictedSample[[i]]$value)),plotImage(truthSample[[i]],\"Ground Truth\\nLandmarks\",\"\"),cols=2)\n" - "\t\t}\n" - "\t}\n" - "}\n").arg(p.major.size > 1 ? p.major.header : (p.minor.header.isEmpty() ? p.major.header : p.minor.header)))); - - p.file.write(qPrintable(QString("\n" - "# Code to format error table\n" - "StatBox <- summarySE(Box, measurevar=\"Y\", groupvars=c(\"%1\",\"X\"))\n" - "OverallStatBox <- summarySE(Box, measurevar=\"Y\", groupvars=c(\"%1\"))\n" - "mat <- matrix(paste(as.character(round(StatBox$Y, 3)), round(StatBox$ci, 3), sep=\" \\u00b1 \"),nrow=rows,ncol=length(algs),byrow=FALSE)\n" - "mat <- rbind(mat, paste(as.character(round(OverallStatBox$Y, 3)), round(OverallStatBox$ci, 3), sep=\" \\u00b1 \"))\n" - "mat <- rbind(mat, as.character(round(NormLength$Y, 3)))\n" - "colnames(mat) <- algs\n" - "rownames(mat) <- c(seq(0,rows-1),\"Aggregate\",\"Average IPD\")\n" - "ETable <- as.table(mat)\n").arg(p.major.size > 1 ? p.major.header : (p.minor.header.isEmpty() ? p.major.header : p.minor.header)))); - - p.file.write(qPrintable(QString("\n" - "print(textplot(ETable))\n" - "print(title(\"Landmarking Error Rates\"))\n"))); + RPlot p(files, destination); + p.file.write("\nformatData(type=\"landmarking\")\n\n"); + p.file.write(qPrintable(QString("algs <- uniqueBox$%1)\n").arg(p.major.size > 1 ? p.major.header : (p.minor.header.isEmpty() ? p.major.header : p.minor.header)))); + p.file.write("algs <- algs[!duplicated(algs)]\n"); + p.file.write("plotLandmarkSamples(samples=sample, expData=EXP, extData=EXT)\n"); + p.file.write("plotLandmarkTables(tableData=Box)\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()) + @@ -599,7 +340,7 @@ bool PlotMetadata(const QStringList &files, const QString &columns, bool show) { qDebug("Plotting %d metadata file(s) for columns %s", files.size(), qPrintable(columns)); - RPlot p(files, "PlotMetadata", false); + RPlot p(files, "PlotMetadata"); foreach (const QString &column, columns.split(";")) p.file.write(qPrintable(QString("qplot(%1, %2, data=data, geom=\"violin\", fill=%1) + coord_flip() + theme_minimal()\nggsave(\"%2.pdf\")\n").arg(p.major.header, column))); return p.finalize(show); diff --git a/share/openbr/plotting/plot_utils.R b/share/openbr/plotting/plot_utils.R new file mode 100644 index 0000000..ea19d71 --- /dev/null +++ b/share/openbr/plotting/plot_utils.R @@ -0,0 +1,370 @@ +# Load libraries +library("ggplot2") +library("gplots") +library("reshape") +library("scales") +library("jpeg") +library("png") +library("grid") + +# Code to format FAR values +far_names <- list('0.001'="FAR = 0.1%", '0.01'="FAR = 1%") +far_labeller <- function(variable,value) { return(far_names[as.character(value)]) } + +getScale <- function(mode, title, vals) { + if (vals > 12) return(do.call(paste("scale", mode, "discrete", sep="_"), list(title))) + else if (vals > 11) return(do.call(paste("scale", mode, "brewer", sep="_"), list(title, palette="Set3"))) + else if (vals > 9) return(do.call(paste("scale", mode, "brewer", sep="_"), list(title, palette="Paired"))) + else return(do.call(paste("scale", mode, "brewer", sep="_"), list(title, palette="Set1"))) +} + +plotMetadata <- function(metadata=NULL, title=NULL) { + MT <- as.data.frame(metadata[c(1, 2, 3, 4, 5),]) + par(mfrow=c(4, 1)) + plot.new() + print(title(paste(title, date(), sep="\n"))) + mat <- matrix(MT$X[c(1, 2)], ncol=2) + colnames(mat) <- c("Gallery", "Probe") + imageTable <- as.table(mat) + print(textplot(imageTable, show.rownames=FALSE)) + print(title("Images")) + mat <- matrix(MT$X[c(3, 4, 5)], ncol=3) + colnames(mat) <- c("Genuine", "Impostor", "Ignored") + matchTable <- as.table(mat) + print(textplot(matchTable, show.rownames=FALSE)) + print(title("Matches")) + plot.new() + print(title("Gallery * Probe = Genuine + Impostor + Ignored")) +} + +plotTable <- function(tableData=NULL, name=NULL, labels=NULL) { + if (nrow(tableData) == 0) return() + if (smooth && confidence != 0) { + input = paste(as.character(round(tableData$Y, 3)), round(tableData$ci, 3), sep="\u00b1") + } else { + input = tableData$Y + } + mat <- matrix(input, nrow=length(labels), ncol=length(algs), byrow=FALSE) + colnames(mat) <- algs + rownames(mat) <- labels + table <- as.table(mat) + if (csv) { + write.csv(table, file=paste(paste(basename, deparse(substitute(data)), sep="_"), ".csv", sep="")) + } else { + print(textplot(table)) + print(title(name)) + } +} + +plotLandmarkTables <- function(tableData=NULL) { + if(majorSize > 1) { + var <- majorHeader + } else { + if(minorHeader == "") var <- majorHeader else var <- minorHeader + } + StatBox <- summarySE(tableData, measurevar="Y", groupvars=c(var,"X")) + OverallStatBox <- summarySE(tableData, measurevar="Y", groupvars=c(var)) + mat <- matrix(paste(as.character(round(StatBox$Y, 3)), round(StatBox$ci, 3), sep=" \u00b1 "), nrow=rows, ncol=length(algs), byrow=FALSE) + mat <- rbind(mat, paste(as.character(round(OverallStatBox$Y, 3)), round(OverallStatBox$ci, 3), sep=" \u00b1 ")) + mat <- rbind(mat, as.character(round(NormLength$Y, 3))) + colnames(mat) <- algs + rownames(mat) <- c(seq(0, rows-1), "Aggregate","Average IPD") + ETable <- as.table(mat) + print(textplot(ETable)) + print(title("Landmarking Error Rates")) +} + +plotLine <- function(lineData=NULL, options=NULL, flipY=FALSE, geometry="line") { + textSize <- if("textSize" %in% names(options)) as.numeric(options$textSize) else 12 + p <- qplot(X, if(flipY) 1-Y else Y, data=lineData, main=options$title, geom=geometry, size=if("size" %in% names(options)) I(as.numeric(options$size)) else I(.5), colour=if(majorSize > 1) factor(eval(parse(text=majorHeader))) else NULL, linetype=if(minorSize > 1) factor(eval(parse(text=minorHeader))) else NULL, xlab=options$xTitle, ylab=options$yTitle) + theme_minimal() + if (smooth && deparse(substitute(lineData)) != "CMC" && confidence != 0) p <- p + geom_errorbar(data=lineData[seq(1, NROW(lineData), by = 29),], aes(x=X, ymin=if(flipY) (1-lower) else lower, ymax=if(flipY) (1-upper) else upper), width=0.1, alpha=I(1/2)) + if (majorSize > 1) p <- p + getScale("colour", majorHeader, majorSize) + if (minorSize > 1) p <- p + scale_linetype_discrete(minorHeader) + + # Set log/continuous scales, breaks and labels + if (options$xLog) + p <- p + scale_x_log10(labels=if("xLabels" %in% names(options)) eval(parse(text=options$xLabels)) else trans_format("log10", math_format()), breaks=if("xBreaks" %in% names(options)) eval(parse(text=options$xBreaks)) else waiver()) + annotation_logticks(sides="b") + else + p <- p + scale_x_continuous(labels=if("xLabels" %in% names(options)) eval(parse(text=options$xLabels)) else percent, breaks=if("xBreaks" %in% names(options)) eval(parse(text=options$xBreaks)) else pretty_breaks(n=10)) + if (options$yLog) + p <- p + scale_y_log10(labels=if("yLabels" %in% names(options)) eval(parse(text=options$yLabels)) else trans_format("log10", math_format()), breaks=if("yBreaks" %in% names(options)) eval(parse(text=options$yBreaks)) else waiver()) + annotation_logticks(sides="l") + else + p <- p + scale_y_continuous(labels=if("yLabels" %in% names(options)) eval(parse(text=options$yLabels)) else percent, breaks=if("yBreaks" %in% names(options)) eval(parse(text=options$yBreaks)) else pretty_breaks(n=10)) + + if ("xLimits" %in% names(options)) p <- p + xlim(eval(parse(text=options$xLimits))) + if ("yLimits" %in% names(options)) p <- p + ylim(eval(parse(text=options$yLimits))) + p <- p + theme(legend.title = element_text(size = textSize), legend.text = element_text(size = textSize), plot.title = element_text(size = textSize), axis.text = element_text(size = textSize), axis.title.x = element_text(size = textSize), axis.title.y = element_text(size = textSize), legend.position=if("legendPosition" %in% names(options)) eval(parse(text=options$legendPosition)) else "bottom", legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = "gray"), panel.grid.minor = element_line(colour = "gray", linetype = "dashed")) + p <- p + guides(col=guide_legend(ncol=ncol)) + return(p) +} + +plotSD <- function(sdData=NULL) { + p <- qplot(X, data=sdData, geom="histogram", fill=Y, position="identity", alpha=I(1/2), xlab="Score", ylab="Frequency") + p <- p + scale_fill_manual("Ground Truth", values=c("blue", "red")) + 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)) + if (majorSize > 1) { + if (minorSize > 1) { + if (flip) { + A <- minorHeader + B <- majorHeader + } else { + A <- majorHeader + B <- minorHeader + } + p <- p + facet_grid(facets=as.formula(paste(A, "~", B)), scales="free") + } else { + p <- p + facet_wrap(facets=as.formula(paste("~", majorHeader)), scales="free") + } + } + p <- p + theme(aspect.ratio=1) + return(p) +} + +plotBC <- function(bcData=NULL) { + factor <- if (majorSmooth) minorHeader else majorHeader + plotString <- paste("qplot(factor(", factor, ")", if(smooth) ", Y" else "", ", data=bcData, ", if(smooth) "geom=\"boxplot\"" else "geom=\"bar\", position=\"dodge\", weight=Y", sep="") + p <- eval(parse(text=paste(plotString, if(majorSize > 1) paste(", fill=factor(", majorHeader, ")", sep="") else "", ", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()", sep=""))) + if(majorSize > 1) p <- p + getScale("fill", majorHeader, majorSize) + if(minorSize > 1) p <- p + facet_grid(facets=as.formula(paste(minorHeader, "~", "X"))) else p <- p + facet_grid(. ~ X, labeller=far_labeller) + p <- p + scale_y_continuous(labels=percent) + theme(legend.position="none", axis.text.x=element_text(angle=-90, hjust=0)) + if(!smooth) p <- p + geom_text(data=bcData, aes(label=bcData$Y, y=0.05)) + return(p) +} + +plotERR <- function(errData=NULL) { + if(flip) { + if(majorSize > 1) color <- majorHeader + } else { + if(minorSize > 1) color <- minorHeader + } + p <- qplot(X, Y, data=errData, geom="line", linetype=Error, colour=if(exists("color")) factor(eval(parse(text=color))) else NULL, xlab="Score", ylab="Error Rate") + theme_minimal() + if(flip) { + if(majorSize > 1) + p <- p + getScale("colour", majorHeader, majorSize) + else if(minorSize > 1) + p <- p + getScale("colour", minorHeader, minorSize) + } + p <- p + scale_y_log10(labels=percent) + annotation_logticks(sides="l") + if(flip) { + if(minorSize > 1) { + facet <- minorHeader + p <- p + facet_wrap(as.formula(paste("~", facet)), scales="free_x") + } + } else { + if(majorSize >1) { + facet <- majorHeader + p <- p + facet_wrap(as.formula(paste("~", facet)), scales="free_x") + } + } + p <- p + theme(aspect.ratio=1) + return(p) +} + +plotOverlap <- function(overlapData=NULL) { + p <- qplot(X, data=overlapData, geom="histogram", position="identity", xlab="Overlap", ylab="Frequency") + p <- p + 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)) + if(majorSize > 1) { + if(minorSize) { + p <- p + facet_grid(facets=as.formula(paste(minorHeader, "~", majorHeader)), scales="free") + } else { + p <- p + facet_wrap(facets=as.formula(paste("~", majorHeader)), scales="free") + } + } + p <- p + theme(aspect.ratio=1, legend.position="bottom") + return(p) +} + +formatData <- function(type="eval") { + if (type == "eval") { + # Split data into individual plots + plot_index <<- which(names(data)=="Plot") + Metadata <<- data[grep("Metadata",data$Plot),-c(1)] + IM <<- data[grep("IM",data$Plot),-c(1)] + GM <<- data[grep("GM",data$Plot),-c(1)] + DET <<- data[grep("DET",data$Plot),-c(1)] + IET <<- data[grep("IET",data$Plot),-c(1)] + FAR <- data[grep("FAR",data$Plot),-c(1)] + FRR <- data[grep("FRR",data$Plot),-c(1)] + SD <<- data[grep("SD",data$Plot),-c(1)] + TF <<- data[grep("TF",data$Plot),-c(1)] + FT <<- data[grep("FT",data$Plot),-c(1)] + CT <<- data[grep("CT",data$Plot),-c(1)] + BC <<- data[grep("BC",data$Plot),-c(1)] + TS <<- data[grep("TS",data$Plot),-c(1)] + CMC <<- data[grep("CMC",data$Plot),-c(1)] + FAR$Error <- "FAR" + FRR$Error <- "FRR" + ERR <<- rbind(FAR, FRR) + + # Format data + Metadata$Y<-factor(Metadata$Y, levels=c("Genuine", "Impostor", "Ignored", "Gallery", "Probe")) + IM$Y <<- as.character(IM$Y) + GM$Y <<- as.character(GM$Y) + DET$Y <<- as.numeric(as.character(DET$Y)) + IET$Y <<- as.numeric(as.character(IET$Y)) + ERR$Y <<- as.numeric(as.character(ERR$Y)) + SD$Y <<- as.factor(unique(as.character(SD$Y))) + TF$Y <<- as.numeric(as.character(TF$Y)) + FT$Y <<- as.numeric(as.character(FT$Y)) + CT$Y <<- as.numeric(as.character(CT$Y)) + BC$Y <<- as.numeric(as.character(BC$Y)) + TS$Y <<- as.character(TS$Y) + CMC$Y <<- as.numeric(as.character(CMC$Y)) + } else if (type == "detection") { + # Split data into individual plots + DiscreteROC <<- data[grep("DiscreteROC",data$Plot),-c(1)] + ContinuousROC <<- data[grep("ContinuousROC",data$Plot),-c(1)] + DiscretePR <<- data[grep("DiscretePR",data$Plot),-c(1)] + ContinuousPR <<- data[grep("ContinuousPR",data$Plot),-c(1)] + Overlap <<- data[grep("Overlap",data$Plot),-c(1)] + AverageOverlap <<- data[grep("AverageOverlap",data$Plot),-c(1)] + } else if (type == "landmarking") { + # Split data into individual plots + Box <<- data[grep("Box",data$Plot),-c(1)] + Box$X <<- factor(Box$X, levels = Box$X, ordered = TRUE) + Sample <<- data[grep("Sample",data$Plot),-c(1)] + Sample$X <<- as.character(Sample$X) + EXT <<- data[grep("EXT",data$Plot),-c(1)] + EXT$X <<- as.character(EXT$X) + EXP <<- data[grep("EXP",data$Plot),-c(1)] + EXP$X <<- as.character(EXP$X) + NormLength <<- data[grep("NormLength",data$Plot),-c(1)] + sample <<- readImageData(Sample) + rows <<- sample[[1]]$value + } +} + +summarySE <- function(data=NULL, measurevar, groupvars=NULL, na.rm=FALSE, conf.interval=0.95, .drop=TRUE) { + # derived from http://www.cookbook-r.com/Manipulating_data/Summarizing_data/ + require(plyr) + + length2 <- function (x, na.rm=FALSE) { + if (na.rm) sum(!is.na(x)) + else length(x) + } + + datac <- ddply(data, groupvars, .drop=.drop, .fun = function(xx, col) { + 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)) + }, + measurevar + ) + + datac <- rename(datac, c("mean" = measurevar)) + datac$se <- datac$sd / sqrt(datac$N) + ciMult <- qt(conf.interval/2 + .5, datac$N-1) + datac$ci <- datac$se * ciMult + + datac$upper <- if(datac[, measurevar] + datac$ci < 1) (datac[, measurevar] + datac$ci) else 1 + datac$lower <- if(datac[, measurevar] - datac$ci > 0) (datac[, measurevar] - datac$ci) else 0 + + return(datac) +} + +multiplot <- function(..., plotlist=NULL, cols) { + require(grid) + # Make a list from the ... arguments and plotlist + plots <- c(list(...), plotlist) + numPlots = length(plots) + # Make the panel + plotCols = cols + plotRows = ceiling(numPlots/plotCols) + # Set up the page + grid.newpage() + pushViewport(viewport(layout = grid.layout(plotRows, plotCols))) + vplayout <- function(x, y) + viewport(layout.pos.row = x, layout.pos.col = y) + # Make each plot, in the correct location + for (i in 1:numPlots) { + curRow = ceiling(i/plotCols) + curCol = (i-1) %% plotCols + 1 + print(plots[[i]], vp = vplayout(curRow, curCol)) + } +} + +plotEERSamples <- function(imData=NULL, gmData=NULL) { + if(nrow(imData) == 0) return() + + printImages <- function(images, label) { + for (i in 1:nrow(images)) { + score <- images[i,1] + files <- images[i,2] + alg <- images[i,3] + files <- unlist(strsplit(files, "[:]")) + + ext1 <- unlist(strsplit(files[2], "[.]"))[2] + ext2 <- unlist(strsplit(files[4], "[.]"))[2] + if (ext1 == "jpg" || ext1 == "JPEG" || ext1 == "jpeg" || ext1 == "JPG") { + img1 <- readJPEG(files[2]) + } else if (ext1 == "PNG" || ext1 == "png") { + img1 <- readPNG(files[2]) + } else if (ext1 == "TIFF" || ext1 == "tiff" || ext1 == "TIF" || ext1 == "tif") { + img1 <- readTIFF(files[2]) + } else { + next + } + if (ext2 == "jpg" || ext2 == "JPEG" || ext2 == "jpeg" || ext2 == "JPG") { + img2 <- readJPEG(files[4]) + } else if (ext2 == "PNG" || ext2 == "png") { + img2 <- readPNG(files[4]) + } else if (ext2 == "TIFF" || ext2 == "tiff" || ext2 == "TIF" || ext2 == "tif") { + img2 <- readTIFF(files[4]) + } else { + next + } + name1 <- files[1] + name2 <- files[3] + + g1 <- rasterGrob(img1, interpolate=TRUE) + g2 <- rasterGrob(img2, interpolate=TRUE) + + plot1 <- qplot(1:10, 1:10, geom="blank") + annotation_custom(g1, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + theme(axis.line=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) + labs(title=alg) + ylab(unlist(strsplit(files[2], "[/]"))[length(unlist(strsplit(files[2], "[/]")))]) + xlab(name1) + plot2 <- qplot(1:10, 1:10, geom="blank") + annotation_custom(g2, xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + theme(axis.line=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) + labs(title=paste(label, " score =", score)) + ylab(unlist(strsplit(files[4], "[/]"))[length(unlist(strsplit(files[4], "[/]")))]) + xlab(name2) + + multiplot(plot1, plot2, cols=2) + } + } + printImages(imData, "Impostor") + printImages(gmData, "Genuine") +} + +plotLandmarkSamples <- function(samples=NULL, expData=NULL, extData=NULL) { + print(plotImage(samples[[1]], "Sample Landmarks", sprintf("Total Landmarks: %s", samples[[1]]$value))) + if (nrow(EXT) != 0 && nrow(EXP)) { + for (j in 1:length(algs)) { + truthSample <- readData(EXT[EXT$. == algs[[j]],]) + predictedSample <- readData(EXP[EXP$. == algs[[j]],]) + for (i in 1:length(predictedSample)) { + multiplot(plotImage(predictedSample[[i]], sprintf("%s\nPredicted Landmarks", algs[[j]]), sprintf("Average Landmark Error: %.3f", predictedSample[[i]]$value)), plotImage(truthSample[[i]], "Ground Truth\nLandmarks", ""), cols=2) + } + } + } +} + +readImageData <- function(data) { + examples <- list() + for (i in 1:nrow(data)) { + path <- data[i,1] + value <- data[i,2] + file <- unlist(strsplit(path, "[.]"))[1] + ext <- unlist(strsplit(path, "[.]"))[2] + if (ext == "jpg" || ext == "JPEG" || ext == "jpeg" || ext == "JPG") { + img <- readJPEG(path) + } else if (ext == "PNG" || ext == "png") { + img <- readPNG(path) + } else if (ext == "TIFF" || ext == "tiff" || ext == "TIF" || ext == "tif") { + img <- readTIFF(path) + }else { + next + } + example <- list(file = file, value = value, image = img) + examples[[i]] <- example + } + return(examples) +} + +plotImage <- function(image, title=NULL, label=NULL) { + p <- qplot(1:10, 1:10, geom="blank") + annotation_custom(rasterGrob(image$image), xmin=-Inf, xmax=Inf, ymin=-Inf, ymax=Inf) + p <- p + theme(axis.line=element_blank(), axis.title.y=element_blank(), axis.text.x=element_blank(), axis.text.y=element_blank(), line=element_blank(), axis.ticks=element_blank(), panel.background=element_blank()) + p <- p + labs(title=title) + xlab(label) + return(p) +} +