Commit ee5f23d444c62549eb99a5bb1873ca0e62c6219c

Authored by bhklein
1 parent b2ca8dda

expose axis types, bounds and ticks. Output tables to csv.

Showing 1 changed file with 150 additions and 173 deletions
openbr/core/plot.cpp
... ... @@ -42,20 +42,6 @@ static QString getScale(const QString &mode, const QString &title, int vals)
42 42 // Custom sorting method to ensure datasets are ordered nicely
43 43 static bool sortFiles(const QString &fileA, const QString &fileB)
44 44 {
45   - QFileInfo fileInfoA(fileA);
46   - QFileInfo fileInfoB(fileB);
47   -
48   - if (fileInfoA.fileName().contains("Good")) return true;
49   - if (fileInfoB.fileName().contains("Good")) return false;
50   - if (fileInfoA.fileName().contains("Bad")) return true;
51   - if (fileInfoB.fileName().contains("Bad")) return false;
52   - if (fileInfoA.fileName().contains("Ugly")) return true;
53   - if (fileInfoB.fileName().contains("Ugly")) return false;
54   - if (fileInfoA.fileName().contains("MEDS")) return true;
55   - if (fileInfoB.fileName().contains("MEDS")) return false;
56   - if (fileInfoA.fileName().contains("PCSO")) return true;
57   - if (fileInfoB.fileName().contains("PCSO")) return false;
58   -
59 45 return fileA < fileB;
60 46 }
61 47  
... ... @@ -65,7 +51,8 @@ struct RPlot
65 51 QFile file;
66 52 QStringList pivotHeaders;
67 53 QVector< QSet<QString> > pivotItems;
68   - float confidence;
  54 + float confidence; // confidence interval for plotting across splits
  55 + int ncol; // Number of columns for plot legends
69 56  
70 57 bool flip;
71 58  
... ... @@ -124,6 +111,7 @@ struct RPlot
124 111 }
125 112 file.write("data <- rbind(data, tmp)\n");
126 113 }
  114 +
127 115 for (int i=0; i<pivotItems.size(); i++) {
128 116 const int size = pivotItems[i].size();
129 117 if (size > major.size) {
... ... @@ -133,13 +121,10 @@ struct RPlot
133 121 minor = Pivot(i, size, pivotHeaders[i]);
134 122 }
135 123 }
  124 +
136 125 const QString &smooth = destination.get<QString>("smooth", "");
137   - if (destination.contains(QString("confidence"))) {
138   - const QString &CI = destination.get<QString>("confidence");
139   - confidence = !CI.isEmpty() ? CI.toFloat()/100.0 : 0.95;
140   - } else {
141   - confidence = 0.95;
142   - }
  126 + confidence = destination.get<float>("confidence", 95) / 100.0;
  127 +
143 128 major.smooth = !smooth.isEmpty() && (major.header == smooth) && (major.size > 1);
144 129 minor.smooth = !smooth.isEmpty() && (minor.header == smooth) && (minor.size > 1);
145 130 if (major.smooth) major.size = 1;
... ... @@ -147,6 +132,7 @@ struct RPlot
147 132 if (major.size < minor.size)
148 133 std::swap(major, minor);
149 134  
  135 + ncol = destination.get<int>("ncol", major.size > 1 ? major.size : (minor.header.isEmpty() ? major.size : minor.size));
150 136 flip = minor.header == "Algorithm";
151 137 // Format data
152 138 if (isEvalFormat)
... ... @@ -187,7 +173,7 @@ struct RPlot
187 173 "TS$Y <- as.character(TS$Y)\n"
188 174 "CMC$Y <- as.numeric(as.character(CMC$Y))\n"
189 175 "\n"
190   - "if (%1) {\n\tsummarySE <- function(data=NULL, measurevar, groupvars=NULL, na.rm=FALSE, conf.interval=%7, .drop=TRUE) {\n\t\t"
  176 + "if (%1) {\n\tsummarySE <- function(data=NULL, measurevar, groupvars=NULL, na.rm=FALSE, conf.interval=%3, .drop=TRUE) {\n\t\t"
191 177 "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)"
192 178 "\n\t\t}\n\n\t\tdatac <- ddply(data, groupvars, .drop=.drop, .fun = function(xx, col) {\n\t\t\t"
193 179 "c(N=length2(xx[[col]], na.rm=na.rm), mean=mean(xx[[col]], na.rm=na.rm), sd=sd(xx[[col]], na.rm=na.rm))\n\t\t\t},"
... ... @@ -197,6 +183,7 @@ struct RPlot
197 183 "datac$lower <- if(datac[, measurevar] - datac$ci > 0) (datac[, measurevar] - datac$ci) else 0\n\n\t\treturn(datac)\n\t}\n\t"
198 184 "DET <- summarySE(DET, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
199 185 "IET <- summarySE(IET, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
  186 + "CMC <- summarySE(CMC, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
200 187 "ERR <- summarySE(ERR, measurevar=\"X\", groupvars=c(\"Error\", \"%2\", \"Y\"))\n\t"
201 188 "TF <- summarySE(TF, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
202 189 "FT <- summarySE(FT, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
... ... @@ -204,82 +191,112 @@ struct RPlot
204 191 "# Code to format FAR values\n"
205 192 "far_names <- list('0.001'=\"FAR = 0.1%\", '0.01'=\"FAR = 1%\")\n"
206 193 "far_labeller <- function(variable,value) { return(far_names[as.character(value)]) }\n"
207   - "\n"
208   - "# Code to format TAR@FAR table\n"
209   - "algs <- unique(%6)\n"
210   - "algs <- algs[!duplicated(algs)]\n"
211   - "mat <- matrix(%3,nrow=6,ncol=length(algs),byrow=FALSE)\n"
212   - "colnames(mat) <- algs \n"
213   - "rownames(mat) <- c(\"FAR = 1e-06\", \"FAR = 1e-05\", \"FAR = 1e-04\", \"FAR = 1e-03\", \"FAR = 1e-02\", \"FAR = 1e-01\")\n"
214   - "FTtable <- as.table(mat)\n"
215   - "\n"
216   - "# Code to format FAR@TAR table\n"
217   - "mat <- matrix(%4,nrow=6,ncol=length(algs),byrow=FALSE)\n"
218   - "colnames(mat) <- algs \n"
219   - "rownames(mat) <- c(\"TAR = 0.40\", \"TAR = 0.50\", \"TAR = 0.65\", \"TAR = 0.75\", \"TAR = 0.85\", \"TAR = 0.95\")\n"
220   - "F_at_Ttable <- as.table(mat)\n"
221   - "\n"
222   - "# Code to format CMC Table\n"
223   - "mat <- matrix(%5,nrow=6,ncol=length(algs),byrow=FALSE)\n"
224   - "colnames(mat) <- algs \n"
225   - "rownames(mat) <- c(\" Rank 1\", \"Rank 5\", \"Rank 10\", \"Rank 20\", \"Rank 50\", \"Rank 100\")\n"
226   - "CMCtable <- as.table(mat)\n"
227   - "\n"
228   - "# Code to format Template Size Table\n"
229   - "if (nrow(TS) != 0) {\n\t"
230   - "mat <- matrix(TS$Y,nrow=1,ncol=length(algs),byrow=FALSE)\n\t"
231   - "colnames(mat) <- algs\n\t"
232   - "rownames(mat) <- c(\"Template Size (bytes):\")\n\t"
233   - "TStable <- as.table(mat)\n}\n").arg(((major.smooth || minor.smooth) ? "TRUE" : "FALSE"), major.size > 1 ? major.header : (minor.header.isEmpty() ? major.header : minor.header),
234   - (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(TF$Y, 3)), round(TF$ci, 3), sep=\"\\u00b1\")" : "TF$Y",
235   - (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(FT$Y, 3)), round(FT$ci, 3), sep=\"\\u00b1\")" : "FT$Y",
236   - (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(CT$Y, 3)), round(CT$ci, 3), sep=\"\\u00b1\")" : "CT$Y",
237   - (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)),
238   - QString::number(confidence))));
  194 + "\n").arg((major.smooth || minor.smooth) ? "TRUE" : "FALSE",
  195 + major.size > 1 ? major.header : (minor.header.isEmpty() ? major.header : minor.header),
  196 + QString::number(confidence))));
239 197  
240 198 // Open output device
241 199 file.write(qPrintable(QString("\n"
242 200 "# Open output device\n"
243   - "%1(\"%2.%1\")\n").arg(suffix, basename)));
244   -
245   - // Write metadata table
246   - if ((suffix == "pdf") && isEvalFormat) {
247   - file.write("\n"
248   - "# Write metadata table\n");
249   - QString textplot = "MT <- as.data.frame(Metadata[c(1,2,3,4,5),])\n"
250   - "par(mfrow=c(4,1))\n"
251   - "plot.new()\n"
252   - "print(title(paste(\"%1 - %2\",date(),sep=\"\\n\")))\n"
253   - "mat <- matrix(MT$X[c(1,2)],ncol=2)\n"
254   - "colnames(mat) <- c(\"Gallery\", \"Probe\")\n"
255   - "imageTable <- as.table(mat)\n"
256   - "print(textplot(imageTable,show.rownames=FALSE))\n"
257   - "print(title(\"Images\"))\n"
258   - "mat <- matrix(MT$X[c(3,4,5)],ncol=3)\n"
259   - "colnames(mat) <- c(\"Genuine\", \"Impostor\", \"Ignored\")\n"
260   - "matchTable <- as.table(mat)\n"
261   - "print(textplot(matchTable,show.rownames=FALSE))\n"
262   - "print(title(\"Matches\"))\n"
263   - "plot.new()\n"
264   - "print(title(\"Gallery * Probe = Genuine + Impostor + Ignored\"))\n"
265   - "plot.new()\n"
266   - "print(textplot(FTtable))\n"
267   - "print(title(\"Table of True Accept Rates at various False Accept Rates\"))\n"
268   - "print(textplot(F_at_Ttable))\n"
269   - "print(title(\"Table of False Accept Rates at various True Accept Rates\"))\n"
270   - "print(textplot(CMCtable))\n"
271   - "print(title(\"Table of retrieval rate at various ranks\"))\n"
272   - "if (nrow(TS) != 0) {\n\t"
273   - "print(textplot(TStable, cex=1.15))\n\t"
274   - "print(title(\"Template Size by Algorithm\"))\n}\n";
275   - file.write(qPrintable(textplot.arg(PRODUCT_NAME, PRODUCT_VERSION)));
276   - }
  201 + "%1(\"%2.%1\"%3)\n").arg(suffix, basename, suffix != "pdf" ? ", width=1000, height=1000" : "")));
277 202  
278 203 // Write figures
279 204 file.write("\n"
280 205 "# Write figures\n");
281 206 }
282 207  
  208 + void plotMetadata(bool csv)
  209 + {
  210 + file.write(qPrintable(QString("# Code to format TAR@FAR table\n"
  211 + "algs <- unique(%4)\n"
  212 + "algs <- algs[!duplicated(algs)]\n"
  213 + "mat <- matrix(%1,nrow=6,ncol=length(algs),byrow=FALSE)\n"
  214 + "colnames(mat) <- algs \n"
  215 + "rownames(mat) <- c(\"FAR = 1e-06\", \"FAR = 1e-05\", \"FAR = 1e-04\", \"FAR = 1e-03\", \"FAR = 1e-02\", \"FAR = 1e-01\")\n"
  216 + "TFtable <- as.table(mat)\n"
  217 + "\n"
  218 + "# Code to format FAR@TAR table\n"
  219 + "mat <- matrix(%2,nrow=6,ncol=length(algs),byrow=FALSE)\n"
  220 + "colnames(mat) <- algs \n"
  221 + "rownames(mat) <- c(\"TAR = 0.40\", \"TAR = 0.50\", \"TAR = 0.65\", \"TAR = 0.75\", \"TAR = 0.85\", \"TAR = 0.95\")\n"
  222 + "FTtable <- as.table(mat)\n"
  223 + "\n"
  224 + "# Code to format CMC Table\n"
  225 + "mat <- matrix(%3,nrow=6,ncol=length(algs),byrow=FALSE)\n"
  226 + "colnames(mat) <- algs \n"
  227 + "rownames(mat) <- c(\"Rank 1\", \"Rank 5\", \"Rank 10\", \"Rank 20\", \"Rank 50\", \"Rank 100\")\n"
  228 + "CMCtable <- as.table(mat)\n"
  229 + "\n"
  230 + "# Code to format Template Size Table\n"
  231 + "if (nrow(TS) != 0) {\n\t"
  232 + "mat <- matrix(TS$Y,nrow=1,ncol=length(algs),byrow=FALSE)\n\t"
  233 + "colnames(mat) <- algs\n\t"
  234 + "rownames(mat) <- c(\"Template Size (bytes):\")\n\t"
  235 + "TStable <- as.table(mat)\n}"
  236 + "\n").arg((major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(TF$Y, 3)), round(TF$ci, 3), sep=\"\\u00b1\")" : "TF$Y",
  237 + (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(FT$Y, 3)), round(FT$ci, 3), sep=\"\\u00b1\")" : "FT$Y",
  238 + (major.smooth || minor.smooth) && confidence != 0 ? "paste(as.character(round(CT$Y, 3)), round(CT$ci, 3), sep=\"\\u00b1\")" : "CT$Y",
  239 + (major.size > 1 && minor.size > 1) && !(major.smooth || minor.smooth) ? QString("paste(TF$%1, TF$%2, sep=\"_\")").arg(major.header, minor.header)
  240 + : QString("TF$%1").arg(major.size > 1 ? major.header : (minor.header.isEmpty() ? major.header : minor.header)))));
  241 +
  242 + file.write("\n# Write metadata table\n");
  243 + QString textplot = "MT <- as.data.frame(Metadata[c(1,2,3,4,5),])\n"
  244 + "par(mfrow=c(4,1))\n"
  245 + "plot.new()\n"
  246 + "print(title(paste(\"%1 - %2\",date(),sep=\"\\n\")))\n"
  247 + "mat <- matrix(MT$X[c(1,2)],ncol=2)\n"
  248 + "colnames(mat) <- c(\"Gallery\", \"Probe\")\n"
  249 + "imageTable <- as.table(mat)\n"
  250 + "print(textplot(imageTable,show.rownames=FALSE))\n"
  251 + "print(title(\"Images\"))\n"
  252 + "mat <- matrix(MT$X[c(3,4,5)],ncol=3)\n"
  253 + "colnames(mat) <- c(\"Genuine\", \"Impostor\", \"Ignored\")\n"
  254 + "matchTable <- as.table(mat)\n"
  255 + "print(textplot(matchTable,show.rownames=FALSE))\n"
  256 + "print(title(\"Matches\"))\n"
  257 + "plot.new()\n"
  258 + "print(title(\"Gallery * Probe = Genuine + Impostor + Ignored\"))\n";
  259 + file.write(qPrintable(textplot.arg(PRODUCT_NAME, PRODUCT_VERSION)));
  260 +
  261 + if (csv)
  262 + textplot = QString("write.csv(TFtable,file=\"%1_TF.csv\")\n"
  263 + "write.csv(FTtable,file=\"%1_FT.csv\")\n"
  264 + "write.csv(CMCtable,file=\"%1_CMC.csv\")\n\n").arg(basename);
  265 + else
  266 + textplot = "plot.new()\n"
  267 + "print(textplot(TFtable))\n"
  268 + "print(title(\"Table of True Accept Rates at various False Accept Rates\"))\n"
  269 + "print(textplot(FTtable))\n"
  270 + "print(title(\"Table of False Accept Rates at various True Accept Rates\"))\n"
  271 + "print(textplot(CMCtable))\n"
  272 + "print(title(\"Table of retrieval rate at various ranks\"))\n"
  273 + "if (nrow(TS) != 0) {\n\t"
  274 + "print(textplot(TStable, cex=1.15))\n\t"
  275 + "print(title(\"Template Size by Algorithm\"))\n}\n\n";
  276 + file.write(qPrintable(textplot));
  277 + }
  278 +
  279 + void qplot(QString geom, QString data, bool flipY, File opts)
  280 + {
  281 + file.write(qPrintable(QString("qplot(X, %1, data=%2, geom=\"%3\", main=\"%4\"").arg(flipY ? "1-Y" : "Y", data, geom, opts.get<QString>("title", "")) +
  282 + (opts.contains("size") ? QString(", size=I(%1)").arg(opts.get<QString>("size")) : QString()) +
  283 + (major.size > 1 ? QString(", colour=factor(%1)").arg(major.header) : QString()) +
  284 + (minor.size > 1 ? QString(", linetype=factor(%1)").arg(minor.header) : QString()) +
  285 + (QString(", xlab=\"%1\", ylab=\"%2\") + theme_minimal()").arg(opts.get<QString>("xLab"), opts.get<QString>("yLab"))) +
  286 + ((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()) +
  287 + (major.size > 1 ? getScale("colour", major.header, major.size) : QString()) +
  288 + (minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(minor.header) : QString()) +
  289 + (opts.getBool("xLog") ? QString(" + scale_x_log10(labels=%1) + annotation_logticks(sides=\"b\")").arg(data == "CMC" ? "c(1,5,10,50,100), breaks=c(1,5,10,50,100)" : "trans_format(\"log10\", math_format())")
  290 + : QString(" + scale_x_continuous(labels=%1, breaks=pretty_breaks(n=%2))").arg(data == "CMC" ? "waiver()" : "percent", opts.get<QString>("xTicks", "10"))) +
  291 + (opts.getBool("yLog") ? " + scale_y_log10(labels=trans_format(\"log10\", math_format())) + annotation_logticks(sides=\"l\")"
  292 + : QString(" + scale_y_continuous(labels=percent, breaks=pretty_breaks(n=%1))").arg(opts.get<QString>("yTicks", "10"))) +
  293 + (opts.contains("xLimits") ? QString(" + xlim%1").arg(QtUtils::toString(opts.get<QPointF>("xLimits", QPointF()))) : QString()) +
  294 + (opts.contains("yLimits") ? QString(" + ylim%1").arg(QtUtils::toString(opts.get<QPointF>("yLimits", QPointF()))) : QString()) +
  295 + 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<float>("textSize",12))) +
  296 + 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<QPointF>("legendPosition")) : "'bottom'") +
  297 + QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(ncol)));
  298 + }
  299 +
283 300 bool finalize(bool show = false)
284 301 {
285 302 file.write("dev.off()\n");
... ... @@ -297,66 +314,31 @@ bool Plot(const QStringList &amp;files, const File &amp;destination, bool show)
297 314 {
298 315 qDebug("Plotting %d file(s) to %s", files.size(), qPrintable(destination));
299 316  
300   - const bool minimalist = destination.getBool("minimalist");
301   - const bool uncertainty = destination.get<bool>("uncertainty");
  317 + RPlot p(files, destination);
302 318  
303 319 // Use a br::file for simple storage of plot options
304   - File cmcOpts;
305   - const QStringList cmcOptions = destination.get<QStringList>("cmcOptions", QStringList());
306   - foreach (const QString& option, cmcOptions) {
307   - QStringList words = QtUtils::parse(option, '=');
308   - QtUtils::checkArgsSize(words[0],words,1,2);
309   - cmcOpts.set(words[0],words[1]);
310   - }
311   -
312   - File rocOpts;
313   - const QStringList rocOptions = destination.get<QStringList>("rocOptions", QStringList());
314   - foreach (const QString& option, rocOptions) {
315   - QStringList words = QtUtils::parse(option, '=');
316   - QtUtils::checkArgsSize(words[0],words,1,2);
317   - rocOpts.set(words[0],words[1]);
  320 + QMap<QString,File> optMap;
  321 + optMap.insert("rocOptions", File(QString("[xLab=False Accept Rate,yLab=True Accept Rate,xLog=true,yLog=false]")));
  322 + optMap.insert("detOptions", File(QString("[xLab=False Accept Rate,yLab=False Reject Rate,xLog=true,yLog=true]")));
  323 + optMap.insert("ietOptions", File(QString("[xLab=False Positive Identification Rate (FPIR),yLab=False Negative Identification Rate (FNIR),xLog=true,yLog=true]")));
  324 + optMap.insert("cmcOptions", File(QString("[xLab=Rank,yLab=Retrieval Rate,xLog=true,yLog=false,size=1]")));
  325 +
  326 + foreach (const QString &key, optMap.keys()) {
  327 + const QStringList options = destination.get<QStringList>(key, QStringList());
  328 + foreach (const QString &option, options) {
  329 + QStringList words = QtUtils::parse(option, '=');
  330 + QtUtils::checkArgsSize(words[0], words, 1, 2);
  331 + optMap[key].set(words[0], words[1]);
  332 + }
318 333 }
319 334  
320   -
321   - RPlot p(files, destination);
322   -
323   - p.file.write(qPrintable(QString("qplot(X, 1-Y, data=DET, geom=\"line\", main=\"%1\"").arg(rocOpts.get<QString>("title",QString())) +
324   - (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
325   - (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
326   - QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()") +
327   - ((p.major.smooth || p.minor.smooth) && p.confidence != 0 ? " + geom_errorbar(data=DET[seq(1, NROW(DET), by = 29),], aes(x=X, ymin=(1-lower), ymax=(1-upper)), width=0.1, alpha=I(1/2))" : QString()) +
328   - (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
329   - (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
330   - QString(" + scale_x_log10(labels=trans_format(\"log10\", math_format()))") +
331   - (rocOpts.contains("yLimits") ? QString(" + scale_y_continuous(labels=percent) + coord_cartesian(ylim=%1)").arg("c"+QtUtils::toString(rocOpts.get<QPointF>("yLimits",QPointF()))) : QString(" + scale_y_continuous(labels=percent)")) +
332   - QString(" + annotation_logticks(sides=\"b\")") +
333   - QString(" + theme(legend.title = element_text(size = %1), plot.title = element_text(size = %1), axis.text = element_text(size = %1), axis.title.x = element_text(size = %1), axis.title.y = element_text(size = %1),"
334   - " legend.position=%2, legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = \"gray\"), panel.grid.minor = element_line(colour = \"gray\", linetype = \"dashed\"), legend.text = element_text(size = %1))").arg(QString::number(rocOpts.get<float>("textSize",12)), rocOpts.contains("legendPosition") ? "c"+QtUtils::toString(rocOpts.get<QPointF>("legendPosition")) : "'bottom'") +
335   - QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(destination.get<int>("ncol", p.major.size > 1 ? p.major.size : (p.minor.header.isEmpty() ? p.major.size : p.minor.size)))));
336   -
337   - p.file.write(qPrintable(QString("qplot(X, Y, data=DET, geom=\"line\"") +
338   - (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
339   - (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
340   - QString(", xlab=\"False Accept Rate\", ylab=\"False Reject Rate\") + geom_abline(alpha=0.5, colour=\"grey\", linetype=\"dashed\") + theme_minimal()") +
341   - ((p.major.smooth || p.minor.smooth) && p.confidence != 0 ? " + geom_errorbar(data=DET[seq(1, NROW(DET), by = 29),], aes(x=X, ymin=lower, ymax=upper), width=0.1, alpha=I(1/2))" : QString()) +
342   - (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
343   - (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
344   - QString(" + theme(legend.title = element_text(size = %1), plot.title = element_text(size = %1), axis.text = element_text(size = %1), axis.title.x = element_text(size = %1), axis.title.y = element_text(size = %1),"
345   - " legend.position=%2, legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = \"gray\"), panel.grid.minor = element_line(colour = \"gray\", linetype = \"dashed\"), legend.text = element_text(size = %1))").arg(QString::number(rocOpts.get<float>("textSize",12)), rocOpts.contains("legendPosition") ? "c"+QtUtils::toString(rocOpts.get<QPointF>("legendPosition")) : "'bottom'") +
346   - QString(" + scale_x_log10(labels=trans_format(\"log10\", math_format())) + scale_y_log10(labels=trans_format(\"log10\", math_format())) + annotation_logticks()") +
347   - QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(destination.get<int>("ncol", p.major.size > 1 ? p.major.size : (p.minor.header.isEmpty() ? p.major.size : p.minor.size)))));
348   -
349   - p.file.write(qPrintable(QString("qplot(X, Y, data=IET, geom=\"line\"") +
350   - (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
351   - (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
352   - QString(", xlab=\"False Positive Identification Rate (FPIR)\", ylab=\"False Negative Identification Rate (FNIR)\") + theme_minimal()") +
353   - ((p.major.smooth || p.minor.smooth) && p.confidence != 0 ? " + geom_errorbar(data=IET[seq(1, NROW(IET), by = 29),], aes(x=X, ymin=lower, ymax=upper), width=0.1, alpha=I(1/2))" : QString()) +
354   - (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
355   - (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
356   - QString(" + theme(legend.title = element_text(size = %1), plot.title = element_text(size = %1), axis.text = element_text(size = %1), axis.title.x = element_text(size = %1), axis.title.y = element_text(size = %1),"
357   - " legend.position=%2, legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = \"gray\"), panel.grid.minor = element_line(colour = \"gray\", linetype = \"dashed\"), legend.text = element_text(size = %1))").arg(QString::number(rocOpts.get<float>("textSize",12)), rocOpts.contains("legendPosition") ? "c"+QtUtils::toString(rocOpts.get<QPointF>("legendPosition")) : "'bottom'") +
358   - QString(" + scale_x_log10(labels=trans_format(\"log10\", math_format())) + scale_y_log10(labels=trans_format(\"log10\", math_format())) + annotation_logticks()") +
359   - QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(destination.get<int>("ncol", p.major.size > 1 ? p.major.size : (p.minor.header.isEmpty() ? p.major.size : p.minor.size)))));
  335 + // optional plot metadata and accuracy tables
  336 + if (destination.getBool("metadata", true))
  337 + p.plotMetadata(destination.getBool("csv", false));
  338 + p.qplot("line", "DET", true, optMap["rocOptions"]);
  339 + p.qplot("line", "DET", false, optMap["detOptions"]);
  340 + p.qplot("line", "IET", false, optMap["ietOptions"]);
  341 + p.qplot("line", "CMC", false, optMap["cmcOptions"]);
360 342  
361 343 p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") +
362 344 QString(", xlab=\"Score\", ylab=\"Frequency\"") +
... ... @@ -364,18 +346,7 @@ bool Plot(const QStringList &amp;files, const File &amp;destination, bool show)
364 346 (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()) +
365 347 QString(" + theme(aspect.ratio=1)\n\n")));
366 348  
367   - p.file.write(qPrintable(QString("ggplot(CMC, aes(x=X, y=Y%1%2)) + ggtitle(\"%3\") + xlab(\"Rank\") + ylab(\"Retrieval Rate\")").arg(p.major.size > 1 ? QString(" ,colour=factor(%1)").arg(p.major.header) : QString(), p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString(), cmcOpts.get<QString>("title",QString())) +
368   - QString(((p.major.smooth || p.minor.smooth) ? (!uncertainty ? " + stat_summary(geom=\"line\", fun.y=mean, size=%1)" : " + stat_summary(geom=\"line\", fun.y=min, aes(linetype=\"Min/Max\"), size=%1) + stat_summary(geom=\"line\", "
369   - "fun.y=max, aes(linetype=\"Min/Max\"), size=%1) + stat_summary(geom=\"line\", fun.y=mean, aes(linetype=\"Mean\"), size=%1) + scale_linetype_manual(\"Legend\", values=c(\"Mean\"=1, \"Min/Max\"=2))") : " + geom_line(size=%1)")).arg(QString::number(cmcOpts.get<float>("thickness",1))) +
370   - (minimalist ? "" : " + scale_x_log10(labels=c(1,5,10,50,100), breaks=c(1,5,10,50,100)) + annotation_logticks(sides=\"b\")") +
371   - (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
372   - (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
373   - (cmcOpts.contains("yLimits") ? QString(" + scale_y_continuous(labels=percent) + coord_cartesian(ylim=%1)").arg("c"+QtUtils::toString(cmcOpts.get<QPointF>("yLimits",QPointF()))) : QString(" + scale_y_continuous(labels=percent)")) +
374   - QString(" + theme_minimal() + theme(legend.title = element_text(size = %1), plot.title = element_text(size = %1), axis.text = element_text(size = %1), axis.title.x = element_text(size = %1), axis.title.y = element_text(size = %1),"
375   - " legend.position=%2, legend.background = element_rect(fill = 'white'), panel.grid.major = element_line(colour = \"gray\"), panel.grid.minor = element_line(colour = \"gray\", linetype = \"dashed\"), legend.text = element_text(size = %1))").arg(QString::number(cmcOpts.get<float>("textSize",12)), cmcOpts.contains("legendPosition") ? "c"+QtUtils::toString(cmcOpts.get<QPointF>("legendPosition")) : "'bottom'") +
376   - QString(" + guides(col=guide_legend(ncol=%1))\n\n").arg(destination.get<int>("ncol", p.major.size > 1 ? p.major.size : (p.minor.header.isEmpty() ? p.major.size : p.minor.size)))));
377   -
378   - 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") +
  349 + 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") +
379 350 (p.major.size > 1 ? QString(", fill=factor(%1)").arg(p.major.header) : QString()) +
380 351 QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()") +
381 352 (p.major.size > 1 ? getScale("fill", p.major.header, p.major.size) : QString()) +
... ... @@ -451,6 +422,20 @@ bool PlotDetection(const QStringList &amp;files, const File &amp;destination, bool show)
451 422 qDebug("Plotting %d detection file(s) to %s", files.size(), qPrintable(destination));
452 423 RPlot p(files, destination, false);
453 424  
  425 + // Use a br::file for simple storage of plot options
  426 + QMap<QString,File> optMap;
  427 + optMap.insert("rocOptions", File(QString("[xLab=False Accepts Per Image,yLab=True Accept Rate,xLog=true,yLog=false]")));
  428 + optMap.insert("prOptions", File(QString("[xLab=False Accept Rate,yLab=False Reject Rate,xLog=true,yLog=true]")));
  429 +
  430 + foreach (const QString &key, optMap.keys()) {
  431 + const QStringList options = destination.get<QStringList>(key, QStringList());
  432 + foreach (const QString &option, options) {
  433 + QStringList words = QtUtils::parse(option, '=');
  434 + QtUtils::checkArgsSize(words[0], words, 1, 2);
  435 + optMap[key].set(words[0], words[1]);
  436 + }
  437 + }
  438 +
454 439 p.file.write("# Split data into individual plots\n"
455 440 "plot_index = which(names(data)==\"Plot\")\n"
456 441 "DiscreteROC <- data[grep(\"DiscreteROC\",data$Plot),-c(1)]\n"
... ... @@ -466,23 +451,15 @@ bool PlotDetection(const QStringList &amp;files, const File &amp;destination, bool show)
466 451 if (filesHaveSinglePoint(files))
467 452 plotType = QString("point");
468 453  
469   - foreach (const QString &type, QStringList() << "Discrete" << "Continuous")
470   - p.file.write(qPrintable(QString("qplot(X, Y, data=%1ROC%2").arg(type, (p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : QString(", geom=\"%1\"").arg(plotType)) +
471   - (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
472   - (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
473   - QString(", xlab=\"False Accepts Per Image\", ylab=\"True Accept Rate\") + theme_minimal()") +
474   - (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
475   - (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
476   - QString(" + scale_x_log10() + scale_y_continuous(labels=percent, limits=c(0,1)) + annotation_logticks(sides=\"b\") + ggtitle(\"%1\") + theme(legend.position=\"bottom\")\n\n").arg(type)));
477   -
478   - foreach (const QString &type, QStringList() << "Discrete" << "Continuous")
479   - p.file.write(qPrintable(QString("qplot(X, Y, data=%1PR%2").arg(type, (p.major.smooth || p.minor.smooth) ? ", geom=\"smooth\", method=loess, level=0.99" : QString(", geom=\"%1\"").arg(plotType)) +
480   - (p.major.size > 1 ? QString(", colour=factor(%1)").arg(p.major.header) : QString()) +
481   - (p.minor.size > 1 ? QString(", linetype=factor(%1)").arg(p.minor.header) : QString()) +
482   - QString(", xlab=\"Recall\", ylab=\"Precision\") + theme_minimal()") +
483   - (p.major.size > 1 ? getScale("colour", p.major.header, p.major.size) : QString()) +
484   - (p.minor.size > 1 ? QString(" + scale_linetype_discrete(\"%1\")").arg(p.minor.header) : QString()) +
485   - QString(" + scale_x_continuous(limits=c(0,1)) + scale_y_continuous(labels=percent, limits=c(0,1)) + ggtitle(\"%1\") + theme(legend.position=\"bottom\")\n\n").arg(type)));
  454 + foreach (const QString &type, QStringList() << "Discrete" << "Continuous") {
  455 + optMap["rocOptions"].set("title", type);
  456 + p.qplot(plotType, type + "ROC", false, optMap["rocOptions"]);
  457 + }
  458 +
  459 + foreach (const QString &type, QStringList() << "Discrete" << "Continuous") {
  460 + optMap["prOptions"].set("title", type);
  461 + p.qplot(plotType, type + "PR", false, optMap["prOptions"]);
  462 + }
486 463  
487 464 p.file.write(qPrintable(QString("qplot(X, data=Overlap, geom=\"histogram\", position=\"identity\", xlab=\"Overlap\", ylab=\"Frequency\")") +
488 465 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))") +
... ...