diff --git a/CMakeLists.txt b/CMakeLists.txt index 4085264..d039624 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,6 +142,7 @@ endif() # Look for extensions to OpenBR set(BR_THIRDPARTY_PLUGINS_DIR CACHE PATH "") set(BR_THIRDPARTY_APPS_DIR CACHE PATH "") +set(BR_THIRDPARTY_DIR CACHE PATH "") mark_as_advanced(BR_THIRDPARTY_PLUGINS_DIR) mark_as_advanced(BR_THIRDPARTY_APPS_DIR) diff --git a/README.md b/README.md index 04c15a5..bf6ebeb 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,4 @@ Identify the latest [tagged release](https://github.com/biometrics/openbr/releas $ git submodule init $ git submodule update -**[Build Instructions](http://openbiometrics.org/doxygen/latest/installation.html)** +**[Build Instructions](http://openbiometrics.org/docs/install/)** 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/openbr/openbr_plugin.cpp b/openbr/openbr_plugin.cpp index 8b8b9bc..1ce41f6 100644 --- a/openbr/openbr_plugin.cpp +++ b/openbr/openbr_plugin.cpp @@ -415,6 +415,29 @@ static T findAndRemove(QVariantMap &map, const QString &key, const T &defaultVal br_utemplate Template::toUniversalTemplate(const Template &t) { QVariantMap map = t.file.localMetadata(); + + // QJsonObject::fromVariantMap (below) fails to convert + // QRects and QPoints to string, replacing them with null values. + // so hand-convert these weirdos + foreach (const QString &k, map.keys()) { + QVariant v = map[k]; + if (v.canConvert(QVariant::PointF) || v.canConvert(QVariant::RectF)) { + QString newv = QtUtils::toString(v); + map[k] = newv; + } + // lists of points and rects, too + else if (v.type() == QVariant::List) { + QVariantList oldlist = qvariant_cast(v); + if (!oldlist.isEmpty() && (oldlist.first().canConvert(QVariant::PointF) || oldlist.first().canConvert(QVariant::RectF))) { + QVariantList newlist; + foreach (const QVariant &subv, oldlist) { + newlist.append(QtUtils::toString(subv)); + } + map[k] = newlist; + } + } + } + const int32_t algorithmID = findAndRemove (map, "AlgorithmID", 0); const int32_t x = findAndRemove (map, "X" , 0); const int32_t y = findAndRemove (map, "Y" , 0); @@ -429,6 +452,49 @@ br_utemplate Template::toUniversalTemplate(const Template &t) Template Template::fromUniversalTemplate(br_const_utemplate ut) { QVariantMap map = QJsonDocument::fromJson(QByteArray((const char*) ut->data)).object().toVariantMap(); + + // QJsonDocument::fromJson doesn't know about QRects and QPoints + // so convert any QStrings that can be converted + foreach (const QString &k, map.keys()) { + QVariant v = map[k]; + QVariant newv; + bool istype; + if (v.type() == QVariant::String) { + QString vstr = qvariant_cast(v); + newv = QtUtils::toRect(vstr, &istype); + if (!istype) { + newv = QtUtils::toPoint(vstr, &istype); + } else if (!istype) { + newv = v; + } + map[k] = newv; + } + // convert lists of rects and points, too + else if (v.type() == QVariant::List) { + QVariantList oldlist = qvariant_cast(v); + if (!oldlist.isEmpty() && oldlist.first().type() == QVariant::String) { + QString test = qvariant_cast(oldlist.first()); + QtUtils::toRect(test, &istype); + QVariantList newlist; + if (istype) { + foreach (const QVariant &subv, oldlist) { + newlist.append(QtUtils::toRect(qvariant_cast(subv))); + } + } else { + QtUtils::toPoint(test, &istype); + if (istype) { + foreach (const QVariant &subv, oldlist) { + newlist.append(QtUtils::toPoint(qvariant_cast(subv))); + } + } else { + newlist = oldlist; + } + } + map[k] = newlist; + } + } + } + map.insert("AlgorithmID", ut->algorithmID); map.insert("X" , ut->x ); map.insert("Y" , ut->y ); diff --git a/openbr/plugins/classification/lda.cpp b/openbr/plugins/classification/lda.cpp index 6ffdf4e..762aa62 100644 --- a/openbr/plugins/classification/lda.cpp +++ b/openbr/plugins/classification/lda.cpp @@ -15,9 +15,6 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include -#include -#include - #include #include @@ -58,7 +55,6 @@ BR_REGISTER(Initializer, EigenInitializer) class PCATransform : public Transform { Q_OBJECT - friend class DFFSTransform; friend class LDATransform; protected: @@ -224,54 +220,6 @@ BR_REGISTER(Transform, PCATransform) /*! * \ingroup transforms - * \brief Computes Distance From Feature Space (DFFS) - * \br_paper Moghaddam, Baback, and Alex Pentland. - * "Probabilistic visual learning for object representation." - * Pattern Analysis and Machine Intelligence, IEEE Transactions on 19.7 (1997): 696-710. - * \author Josh Klontz \cite jklontz - * \br_property float keep Sets PCA keep property. Default is 0.95. - */ -class DFFSTransform : public Transform -{ - Q_OBJECT - Q_PROPERTY(float keep READ get_keep WRITE set_keep RESET reset_keep STORED false) - BR_PROPERTY(float, keep, 0.95) - - PCATransform pca; - Transform *cvtFloat; - - void init() - { - pca.keep = keep; - cvtFloat = make("CvtFloat"); - } - - void train(const TemplateList &data) - { - pca.train((*cvtFloat)(data)); - } - - void project(const Template &src, Template &dst) const - { - dst = src; - dst.file.set("DFFS", sqrt(pca.residualReconstructionError((*cvtFloat)(src)))); - } - - void store(QDataStream &stream) const - { - pca.store(stream); - } - - void load(QDataStream &stream) - { - pca.load(stream); - } -}; - -BR_REGISTER(Transform, DFFSTransform) - -/*! - * \ingroup transforms * \brief Projects input into learned Linear Discriminant Analysis subspace. * \author Brendan Klare \cite bklare * \author Josh Klontz \cite jklontz @@ -793,439 +741,6 @@ class WCDATransform : public Transform BR_REGISTER(Transform, WCDATransform) -// For use in BBLDAAlignmentTransform -// A single decision boundary with gaussian distributed responses -struct DecisionBoundary -{ - VectorXf function; - float positiveMean, negativeMean, positiveSD, negativeSD; -}; - -QDataStream &operator<<(QDataStream &stream, const DecisionBoundary &db) -{ - return stream << db.function << db.positiveMean << db.negativeMean << db.positiveSD << db.negativeSD; -} - -QDataStream &operator>>(QDataStream &stream, DecisionBoundary &db) -{ - return stream >> db.function >> db.positiveMean >> db.negativeMean >> db.positiveSD >> db.negativeSD; -} - -/*! - * \ingroup transforms - * \brief Boosted Binary LDA classifier for local alignment. - * \author Unknown \cite Unknown - */ -class BBLDAAlignmentTransform : public MetaTransform -{ - Q_OBJECT - Q_PROPERTY(QList anchors READ get_anchors WRITE set_anchors RESET reset_anchors STORED false) // A list of two indicies to provide initial rotation and scaling of training data - Q_PROPERTY(QString detectionKey READ get_detectionKey WRITE set_detectionKey RESET reset_detectionKey STORED false) // Metadata key containing the detection bounding box - Q_PROPERTY(int iad READ get_iad WRITE set_iad RESET reset_iad STORED false) // Inter-anchor distance in pixels - Q_PROPERTY(float radius READ get_radius WRITE set_radius RESET reset_radius STORED false) // Kernel size as a fraction of IAD - Q_PROPERTY(int resolution READ get_resolution WRITE set_resolution RESET reset_resolution STORED false) // Project resolution for sampling rotation and scale - BR_PROPERTY(QList, anchors, QList()) - BR_PROPERTY(QString, detectionKey, "FrontalFace") - BR_PROPERTY(int, iad, 16) - BR_PROPERTY(float, radius, 2) - BR_PROPERTY(int, resolution, 20) - - typedef Matrix MatrixX8U; - - struct RegistrationMatrix - { - Mat src; - Point2f center; - double angle, scale; - - RegistrationMatrix(const Mat &src = Mat(), const Point2f ¢er = Point2f(), double angle = 0, double scale = 1) - : src(src) - , center(center) - , angle(angle) - , scale(scale) - {} - - Mat calculateRotationMatrix2D(int size) const - { - Mat rotationMatrix2D = getRotationMatrix2D(center, angle, scale); - rotationMatrix2D.at(0, 2) -= (center.x - size / 2.0); // Adjust the center to the middle of the dst image - rotationMatrix2D.at(1, 2) -= (center.y - size / 2.0); - return rotationMatrix2D; - } - - Point2f invert(double x, double y, int size) const - { - Mat m; - invertAffineTransform(calculateRotationMatrix2D(size), m); - - Mat src(1, 1, CV_64FC2); - src.ptr()[0] = y; // According to the docs these should be specified in the opposite order ... - src.ptr()[1] = x; // ... however running it seems to suggest this is the correct way - - Mat dst; - transform(src, dst, m); - return Point2f(dst.ptr()[0], dst.ptr()[1]); - } - - MatrixX8U warp(int size) const - { - const Mat rotationMatrix2D = calculateRotationMatrix2D(size); - Mat dst; - warpAffine(src, dst, rotationMatrix2D, Size(size, size), INTER_LINEAR, BORDER_REFLECT); - return Map(dst.ptr(), size, size); - } - }; - - float rotationMax, scaleMin, scaleMax, xTranslationMax, yTranslationMin, yTranslationMax; - QList decisionBoundaries; - - static void show(const char *name, const MatrixX8U &data) - { - const Mat mat(data.rows(), data.cols(), CV_8UC1, (void*) data.data()); - imshow(name, mat); - waitKey(-1); - } - - static void show(const char *name, MatrixXf data) - { - data.array() -= data.minCoeff(); - data.array() *= (255 / data.maxCoeff()); - show(name, MatrixX8U(data.cast())); - } - - static void show(const char *name, VectorXf data) - { - MatrixXf resized = data; - const int size = int(sqrtf(float(data.rows()))); - resized.resize(size, size); - show(name, resized); - } - - static MatrixXf getSample(const MatrixXf ®istered, int j, int k, int kernelSize) - { - MatrixXf sample(registered.block(j, k, kernelSize, kernelSize)); - const float norm = sample.norm(); - if (norm > 0) - sample /= norm; - return sample; - } - - static MatrixXf getSample(const QList ®istered, int i, int j, int k, int kernelSize) - { - return getSample(registered[i], j, k, kernelSize); - } - - static float IADError(const MatrixXf &responses, const MatrixXf &mask) - { - const int responseSize = sqrtf(responses.cols()); - int numImages = 0; - float totalError = 0; - for (int i=0; i::max(); - int maxX = -1; - int maxY = -1; - for (int j=0; j 0) { - const float response = responses(i, j*responseSize+k); - if (response > maxResponse) { - maxResponse = response; - maxY = j; - maxX = k; - } - } - totalError += sqrtf(powf(maxX - responseSize/2, 2) + powf(maxY - responseSize/2, 2)) / responseSize; - numImages++; - } - return totalError / numImages; - } - - static bool isPositive(int j, int k, int responseSize) - { - return (abs(j - responseSize/2) <= 0) && (abs(k - responseSize/2) <= 0); - } - - static MatrixXf /* output responses */ computeBoundaryRecursive(const QList ®istered, int kernelSize, const MatrixXf &prior /* input responses */, QList &boundaries) - { - const int numImages = registered.size(); - const int responseSize = registered.first().cols() - kernelSize + 1; - int positiveSamples = 0; - int negativeSamples = 0; - - // Compute weights, a weight of zero operates as a mask - MatrixXf weights(numImages, responseSize*responseSize); - float totalPositiveWeight = 0, totalNegativeWeight = 0; - for (int i=0; i 0) { - if (positive) { totalPositiveWeight += weight; positiveSamples++; } - else { totalNegativeWeight += weight; negativeSamples++; } - } - } - - // Normalize weights to preserve class sample ratio - const float positiveWeightAdjustment = positiveSamples / totalPositiveWeight; - const float negativeWeightAdjustment = negativeSamples / totalNegativeWeight; - for (int i=0; i 0) { - if (isPositive(j, k, responseSize)) positiveMean += sample * weight; - else negativeMean += sample * weight; - } - } - positiveMean /= positiveSamples; - negativeMean /= negativeSamples; - - // Compute weighted scatter matrix and decision boundary - MatrixXf scatter = MatrixXf::Zero(kernelSize*kernelSize, kernelSize*kernelSize); - for (int i=0; i 0) { - const MatrixXf ¢eredSampleMatrix = getSample(registered, i, j, k, kernelSize) - (isPositive(j, k, responseSize) ? positiveMean : negativeMean); - const Map centeredSample(centeredSampleMatrix.data(), kernelSize*kernelSize); - scatter.noalias() += centeredSample * centeredSample.transpose() * weights(i, j*responseSize+k); - } - } - scatter /= (positiveSamples + negativeSamples); // normalize for numerical stability - DecisionBoundary db; - db.function = scatter.inverse() * VectorXf(positiveMean - negativeMean); // fisher linear discriminant - - // Compute response values to decision boundary - MatrixXf posterior(numImages, responseSize*responseSize); - for (int i=0; i 0) { - const MatrixXf sample(getSample(registered, i, j, k, kernelSize)); - posterior(i, j*responseSize + k) = db.function.transpose() * Map(sample.data(), kernelSize*kernelSize); - } - - // Compute class response means - db.positiveMean = 0; - db.negativeMean = 0; - for (int i=0; i 0) { - const float response = posterior(i, j*responseSize + k); - if (isPositive(j, k, responseSize)) db.positiveMean += response; - else db.negativeMean += response; - } - db.positiveMean /= positiveSamples; - db.negativeMean /= negativeSamples; - - // Compute class response standard deviations - db.positiveSD = 0; - db.negativeSD = 0; - for (int i=0; i 0) { - const float response = posterior(i, j*responseSize + k); - if (isPositive(j, k, responseSize)) db.positiveSD += powf(response-db.positiveMean, 2.f); - else db.negativeSD += powf(response-db.negativeMean, 2.f); - } - db.positiveSD = sqrtf(db.positiveSD / positiveSamples); - db.negativeSD = sqrtf(db.negativeSD / negativeSamples); - - // Normalize responses and propogating prior probabilities - for (int i=0; i 0) { - const float positiveDensity = 1 / (sqrtf(2 * CV_PI) * db.positiveSD) * expf(-0.5 * powf((response - db.positiveMean) / db.positiveSD, 2.f)); - const float negativeDensity = 1 / (sqrtf(2 * CV_PI) * db.negativeSD) * expf(-0.5 * powf((response - db.negativeMean) / db.negativeSD, 2.f)); - response = prior(i, j*responseSize+k) * positiveDensity / (positiveDensity + negativeDensity); - } else { - response = 0; - } - } - const float previousMeanError = IADError(prior, weights); - const float meanError = IADError(posterior, weights); - qDebug() << "Samples positive & negative:" << positiveSamples << negativeSamples; - qDebug() << "Positive mean & stddev:" << db.positiveMean << db.positiveSD; - qDebug() << "Negative mean & stddev:" << db.negativeMean << db.negativeSD; - qDebug() << "Mean error before & after:" << previousMeanError << meanError; - - if (meanError < previousMeanError) { - boundaries.append(db); - return computeBoundaryRecursive(registered, kernelSize, posterior, boundaries); - } else { - return prior; - } - } - - void train(const TemplateList &data) - { - QList registrationMatricies; - { - if (Globals->verbose) - qDebug("Preprocessing training data..."); - - rotationMax = -std::numeric_limits::max(); - scaleMin = std::numeric_limits::max(); - scaleMax = -std::numeric_limits::max(); - xTranslationMax = -std::numeric_limits::max(); - yTranslationMin = std::numeric_limits::max(); - yTranslationMax = -std::numeric_limits::max(); - - foreach (const Template &t, data) { - const QRectF detection = t.file.get(detectionKey); - const QList points = t.file.points(); - const float length = sqrt(pow(points[anchors[1]].x() - points[anchors[0]].x(), 2) + - pow(points[anchors[1]].y() - points[anchors[0]].y(), 2)); - const float rotation = atan2(points[anchors[1]].y() - points[anchors[0]].y(), - points[anchors[1]].x() - points[anchors[0]].x()) * 180 / CV_PI; - const float scale = length / detection.width(); - const float xCenter = (points[anchors[0]].x() + points[anchors[1]].x()) / 2; - const float yCenter = (points[anchors[0]].y() + points[anchors[1]].y()) / 2; - const float xTranslation = (xCenter - detection.x() - detection.width() /2) / detection.width(); - const float yTranslation = (yCenter - detection.y() - detection.height()/2) / detection.width(); - - if (detection.contains(xCenter, yCenter) /* Sometimes the face detector gets the wrong face */) { - rotationMax = max(rotationMax, fabsf(rotation)); - scaleMin = min(scaleMin, scale); - scaleMax = max(scaleMax, scale); - xTranslationMax = max(xTranslationMax, fabsf(xTranslation)); - yTranslationMin = min(yTranslationMin, yTranslation); - yTranslationMax = max(yTranslationMax, yTranslation); - } - - registrationMatricies.append(RegistrationMatrix(t, Point2f(xCenter, yCenter), rotation, iad / length)); - } - } - - if (Globals->verbose) - qDebug("Learning affine model ..."); - - // Construct the registered training data for the landmark - const int regionSize = 2 * iad * radius; // Train on a search size that is twice to the kernel size - QList registered; - foreach (const RegistrationMatrix ®istrationMatrix, registrationMatricies) - registered.append(registrationMatrix.warp(regionSize).cast()); - - // Boosted LDA - const int numImages = registered.size(); - const int kernelSize = iad * radius; - const int responseSize = regionSize - kernelSize + 1; - const MatrixXf prior = MatrixXf::Ones(numImages, responseSize*responseSize); - const MatrixXf responses = computeBoundaryRecursive(registered, kernelSize, prior, decisionBoundaries); - -// for (int i=0; i::max(); -// int maxX = -1; -// int maxY = -1; -// for (int j=0; j maxResponse) { -// maxResponse = response; -// maxY = j; -// maxX = k; -// } -// } - -// MatrixXf draw = registered[i]; -// const int r = 3; -// maxX += kernelSize / 2; -// maxY += kernelSize / 2; -// for (int i=-r; i<=r; i++) -// draw(regionSize/2 + i, regionSize/2) *= 2; -// for (int i=-r; i<=r; i++) -// draw(regionSize/2, regionSize/2 + i) *= 2; -// for (int i=-r; i<=r; i++) -// draw(maxX + i, maxY) /= 2; -// for (int i=-r; i<=r; i++) -// draw(maxX, maxY + i) /= 2; -// show("draw", draw); -// show("responses", VectorXf(responses.row(i))); -// } - - if (Globals->verbose) - qDebug("Learned %d function(s) with error %g", decisionBoundaries.size(), IADError(responses, prior)); - } - - void project(const Template &src, Template &dst) const - { - dst = src; - const QRectF detection = src.file.get(detectionKey); - RegistrationMatrix registrationMatrix(src, OpenCVUtils::toPoint(detection.center())); - - const int regionSize = iad * (1 + radius); - const int kernelSize = iad * radius; - const int responseSize = regionSize - kernelSize + 1; - const float rotationStep = (2 * rotationMax) / (resolution - 1); - const float scaleStep = (scaleMax - scaleMin) / (resolution - 1); - - float bestResponse = -std::numeric_limits::max(), bestRotation = 0, bestScale = 0; - int bestX = 0, bestY =0; - - for (float rotation = -rotationMax; rotation <= rotationMax; rotation += rotationStep) { - registrationMatrix.angle = rotation; - for (float scale = scaleMin; scale <= scaleMax; scale += scaleStep) { - registrationMatrix.scale = iad / (scale * detection.width()); - const MatrixXf registered = registrationMatrix.warp(regionSize).cast(); - - for (int j=0; j(sample.data(), kernelSize*kernelSize); - const float positiveDensity = 1 / (sqrtf(2 * CV_PI) * db.positiveSD) * expf(-0.5 * powf((response - db.positiveMean) / db.positiveSD, 2.f)); - const float negativeDensity = 1 / (sqrtf(2 * CV_PI) * db.negativeSD) * expf(-0.5 * powf((response - db.negativeMean) / db.negativeSD, 2.f)); - posterior *= positiveDensity / (positiveDensity + negativeDensity); - } - if (posterior > bestResponse) { - bestResponse = posterior; - bestX = k + kernelSize/2; - bestY = j + kernelSize/2; - bestRotation = rotation; - bestScale = scale; - } - } - } - } - } - - void store(QDataStream &stream) const - { - stream << rotationMax << scaleMin << scaleMax << xTranslationMax << yTranslationMin << yTranslationMax << decisionBoundaries; - } - - void load(QDataStream &stream) - { - stream >> rotationMax >> scaleMin >> scaleMax >> xTranslationMax >> yTranslationMin >> yTranslationMax >> decisionBoundaries; - } -}; - -BR_REGISTER(Transform, BBLDAAlignmentTransform) - } // namespace br #include "classification/lda.moc" diff --git a/openbr/plugins/cmake/dlib.cmake b/openbr/plugins/cmake/dlib.cmake index eb98cb5..ddfa577 100644 --- a/openbr/plugins/cmake/dlib.cmake +++ b/openbr/plugins/cmake/dlib.cmake @@ -2,7 +2,7 @@ set(BR_WITH_DLIB ON CACHE BOOL "Build with DLib") if(${BR_WITH_DLIB}) ExternalProject_Add(dlib - URL http://downloads.sourceforge.net/project/dclib/dlib/v18.16/dlib-18.16.tar.bz2 + URL https://github.com/davisking/dlib/releases/download/v18.16/dlib-18.16.tar.bz2 URL_MD5 e9e5449bc25370afce2d254327afac99 SOURCE_DIR "${CMAKE_SOURCE_DIR}/3rdparty/dlib-18.16" CONFIGURE_COMMAND "" diff --git a/openbr/plugins/core/algorithms.cpp b/openbr/plugins/core/algorithms.cpp index d58ff34..f343826 100644 --- a/openbr/plugins/core/algorithms.cpp +++ b/openbr/plugins/core/algorithms.cpp @@ -34,7 +34,6 @@ class AlgorithmsInitializer : public Initializer Globals->abbreviations.insert("FaceRecognition", "FaceDetection+FaceRecognitionRegistration++++SetMetadata(AlgorithmID,-1):Unit(ByteL1)"); Globals->abbreviations.insert("GenderClassification", "FaceDetection+Expand+FaceClassificationRegistration+Expand+++Discard"); Globals->abbreviations.insert("AgeRegression", "FaceDetection+Expand+FaceClassificationRegistration+Expand+++Discard"); - Globals->abbreviations.insert("FaceQuality", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(64,64,0.25,0.35)+ImageQuality+Cvt(Gray)+DFFS+Discard"); Globals->abbreviations.insert("MedianFace", "Open+Expand+Cascade(FrontalFace)+ASEFEyes+Affine(256,256,0.37,0.45)+Center(Median)"); Globals->abbreviations.insert("BlurredFaceDetection", "Open+LimitSize(1024)+SkinMask/(Cvt(Gray)+GradientMask)+And+Morph(Erode,16)+LargestConvexArea"); Globals->abbreviations.insert("DrawFaceDetection", "Open+Cascade(FrontalFace)+Expand+ASEFEyes+Draw(inPlace=true)"); diff --git a/openbr/plugins/metadata/expandrect.cpp b/openbr/plugins/metadata/expandrect.cpp index 0fc8727..54ac0f1 100644 --- a/openbr/plugins/metadata/expandrect.cpp +++ b/openbr/plugins/metadata/expandrect.cpp @@ -23,6 +23,7 @@ namespace br * \ingroup transforms * \brief Expand the width and height of a Template's rects by input width and height factors. * \author Charles Otto \cite caotto + * \author Brendan Klare \cite bklare */ class ExpandRectTransform : public UntrainableTransform { @@ -31,35 +32,23 @@ class ExpandRectTransform : public UntrainableTransform Q_PROPERTY(float heightExpand READ get_heightExpand WRITE set_heightExpand RESET reset_heightExpand STORED false) BR_PROPERTY(float, widthExpand, .5) BR_PROPERTY(float, heightExpand, .5) + void project(const Template &src, Template &dst) const { dst = src; - QList rects = dst.file.rects(); + dst.file.clearRects(); + QList rects = src.file.rects(); for (int i=0;i < rects.size(); i++) { - QRectF rect = rects[i]; - - qreal width = rect.width(); - qreal height = rect.height(); - float half_w_expansion = widthExpand / 2; - float half_h_expansion = heightExpand / 2; - - qreal half_width = width * widthExpand; - qreal quarter_width = width * half_w_expansion; - qreal half_height = height * heightExpand; - qreal quarter_height = height * half_h_expansion; - - rect.setX(std::max(qreal(0),(rect.x() - quarter_width))); - rect.setY(std::max(qreal(0),(rect.y() - quarter_height))); - - qreal x2 = std::min(rect.width() + half_width + rect.x(), qreal(src.m().cols) - 1); - qreal y2 = std::min(rect.height() + half_height + rect.y(), qreal(src.m().rows) - 1); + qreal widthGrowth = rects[i].width() * widthExpand; + qreal heightGrowth = rects[i].height() * heightExpand; - rect.setWidth(x2 - rect.x()); - rect.setHeight(y2 - rect.y()); + qreal x = std::max(qreal(0), rects[i].x() - widthGrowth / 2.); + qreal y = std::max(qreal(0), rects[i].y() - heightGrowth / 2.); + dst.file.appendRect(QRectF(x, y, + std::min(src.m().cols - x - 1, rects[i].width() + widthGrowth), + std::min(src.m().rows - y - 1, rects[i].height() + heightGrowth))); - rects[i] = rect; } - dst.file.setRects(rects); } }; diff --git a/openbr/plugins/metadata/pointstorects.cpp b/openbr/plugins/metadata/pointstorects.cpp new file mode 100644 index 0000000..8e3fb67 --- /dev/null +++ b/openbr/plugins/metadata/pointstorects.cpp @@ -0,0 +1,58 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2015 Rank One Computing Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief For each point, add a rectangle with radius as a half width + * \author Brendan Klare \cite bklare + */ + +class PointsToRectsTransform : public UntrainableTransform +{ + Q_OBJECT + Q_PROPERTY(float radius READ get_radius WRITE set_radius RESET reset_radius STORED false) + Q_PROPERTY(bool clearRects READ get_clearRects WRITE set_clearRects RESET reset_clearRects STORED false) + BR_PROPERTY(float, radius, 4) + BR_PROPERTY(bool, clearRects, true) + + void project(const Template &src, Template &dst) const + { + dst = src; + + if (clearRects) + dst.file.clearRects(); + + if (src.file.points().isEmpty()) { + if (Globals->verbose) qWarning("No landmarks"); + return; + } + + for (int i = 0; i < src.file.points().size(); i++) { + dst.file.appendRect(QRectF(src.file.points()[i].x() - radius, src.file.points()[i].y() - radius, radius * 2, radius * 2)); + } + } +}; + +BR_REGISTER(Transform, PointsToRectsTransform) + +} // namespace br + +#include "metadata/pointstorects.moc" diff --git a/openbr/plugins/metadata/rectstomats.cpp b/openbr/plugins/metadata/rectstomats.cpp new file mode 100644 index 0000000..42c55da --- /dev/null +++ b/openbr/plugins/metadata/rectstomats.cpp @@ -0,0 +1,45 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Copyright 2015 Rank One Computing Corporation * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief For each rectangle bounding box in src, the mat is cropped and appended + * the the template's list of mats. + * \author Brendan Klare \cite bklare + */ +class RectsToMatsTransform : public UntrainableTransform +{ + Q_OBJECT + +private: + void project(const Template &src, Template &dst) const + { + for (int i = 0; i < src.file.rects().size(); i++) + dst += cv::Mat(src, OpenCVUtils::toRect(src.file.rects()[i])); + } +}; + +BR_REGISTER(Transform, RectsToMatsTransform) + +} // namespace br + +#include "metadata/rectstomats.moc" diff --git a/openbr/plugins/metadata/rectstotemplates.cpp b/openbr/plugins/metadata/rectstotemplates.cpp index bcfde35..bfe8e77 100644 --- a/openbr/plugins/metadata/rectstotemplates.cpp +++ b/openbr/plugins/metadata/rectstotemplates.cpp @@ -1,19 +1,3 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Copyright 2012 The MITRE Corporation * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - #include #include diff --git a/openbr/plugins/metadata/recttokeys.cpp b/openbr/plugins/metadata/recttokeys.cpp new file mode 100644 index 0000000..16d6be8 --- /dev/null +++ b/openbr/plugins/metadata/recttokeys.cpp @@ -0,0 +1,35 @@ +#include + +namespace br +{ + +/*! + * \ingroup transforms + * \brief Convert a rect to X, Y, Width, and Height. Handy for saving universal templates. + * \author Austin Blanton \cite imaus10 + */ +class RectToKeysTransform : public UntrainableMetadataTransform +{ + Q_OBJECT + Q_PROPERTY(QString key READ get_key WRITE set_key RESET reset_key STORED false) + BR_PROPERTY(QString, key, "") + + void projectMetadata(const File &src, File &dst) const + { + dst = src; + if (src.contains(key)) { + QRectF r = src.get(key); + dst.set("Height", r.height()); + dst.set("Width", r.width()); + dst.set("X", r.left()); + dst.set("Y", r.bottom()); + } + } + +}; + +BR_REGISTER(Transform, RectToKeysTransform) + +} // namespace br + +#include "metadata/recttokeys.moc" 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) +} +