Commit 546b3b47e049601250c8f6b0822c1998f4609465

Authored by Ben Klein
2 parents 3fc8ee22 abe33c8e

Merge pull request #395 from biometrics/plotting

Plotting customization
docs/docs/api_docs/c_api/functions.md
@@ -595,14 +595,15 @@ In order of their output, the figures are: @@ -595,14 +595,15 @@ In order of their output, the figures are:
595 1. Metadata table 595 1. Metadata table
596 2. Receiver Operating Characteristic (ROC) 596 2. Receiver Operating Characteristic (ROC)
597 3. Detection Error Tradeoff (DET) 597 3. Detection Error Tradeoff (DET)
598 -4. Score Distribution (SD) histogram  
599 -5. True Accept Rate Bar Chart (BC)  
600 -6. Cumulative Match Characteristic (CMC)  
601 -7. Error Rate (ERR) curve 598 +4. Identification Error Tradeoff (IET)
  599 +5. Cumulative Match Characteristic (CMC)
  600 +6. Score Distribution (SD) histogram
  601 +7. True Accept Rate Bar Chart (BC)
  602 +8. Error Rate (ERR) curve
602 603
603 Two files will be created: 604 Two files will be created:
604 * **destination.R** which is the auto-generated R script used to render the figures. 605 * **destination.R** which is the auto-generated R script used to render the figures.
605 - * **destination.pdf** which has all of the figures in one file multi-page file. 606 + * **destination.pdf** which has all of the figures in one file multi-page file (note that **destination%1d.png** will output each figure to a separate file).
606 607
607 OpenBR uses file and folder names to automatically determine the plot legend. 608 OpenBR uses file and folder names to automatically determine the plot legend.
608 For example, let's consider the case where three algorithms (<tt>A</tt>, <tt>B</tt>, & <tt>C</tt>) were each evaluated on two datasets (<tt>Y</tt> & <tt>Z</tt>). 609 For example, let's consider the case where three algorithms (<tt>A</tt>, <tt>B</tt>, & <tt>C</tt>) were each evaluated on two datasets (<tt>Y</tt> & <tt>Z</tt>).
@@ -611,6 +612,37 @@ The &#39;&lt;tt&gt;_&lt;/tt&gt;&#39; character plays a special role in determining the legend title( @@ -611,6 +612,37 @@ The &#39;&lt;tt&gt;_&lt;/tt&gt;&#39; character plays a special role in determining the legend title(
611 In this case, <tt>A</tt>, <tt>B</tt>, & <tt>C</tt> will be identified as different values of type <tt>Algorithm</tt>, and each will be assigned its own color; <tt>Y</tt> & <tt>Z</tt> will be identified as different values of type Dataset, and each will be assigned its own line style. 612 In this case, <tt>A</tt>, <tt>B</tt>, & <tt>C</tt> will be identified as different values of type <tt>Algorithm</tt>, and each will be assigned its own color; <tt>Y</tt> & <tt>Z</tt> will be identified as different values of type Dataset, and each will be assigned its own line style.
612 Matches around the EER will be displayed if the matches parameter is set in [br_eval](#br_eval). 613 Matches around the EER will be displayed if the matches parameter is set in [br_eval](#br_eval).
613 614
  615 +It is possible to customize some aspects of your plots using the [File](../cpp_api/file/file.md) key/value metadata convention; possible keys are described below.
  616 +
  617 +
  618 +Key | Value | Description
  619 +--- | ---- | -----------
  620 +smooth | [QString] | The file pivot to average across evaluation splits. Typically "Dataset" if using the folder name from above.
  621 +ncol | int | Number of columns in plot legends.
  622 +confidence | float | Confidence interval calculated for smooth, defaults to `0.95`
  623 +metadata | bool | Optional plot metadata, defaults to `true`
  624 +csv | bool | Optional output metadata tables to csv, defaults to `false`
  625 +\*Options | [QStringList] | Key/value list of options for a specific plot. Plots include "roc", "det", "iet", "cmc"
  626 +
  627 +Specific plot options are described below:
  628 +
  629 +Key | Value | Description
  630 +--- | ---- | -----------
  631 +title | [QString] | Plot title
  632 +size | float | Line width
  633 +legendPosition | [QPointF] | Legend coordinates on plot, ex. legendPosition=(X,Y)
  634 +textSize | float | Size of text for title, legend and axes
  635 +xTitle/yTitle | [QString] | Title for x/y axis
  636 +xLog/yLog | bool | Plot log scale for x/y axis
  637 +xLimits/yLimits | [QPointF] | Set x/y axis limits, ex. xLimits=(lower,upper)
  638 +xLabels/yLabels | [QString] | Labels for ticks on x/y axis, ex. xLabeles=percent or xLabels=c(1,5,10)
  639 +xBreaks/yBreaks | [QString] | Specify breaks/ticks on x/y axis, ex. xBreaks=pretty_breaks(n=10) or xBreaks=c(1,5,10)
  640 +
  641 +If specifying plot options it is a good idea to wrap the destination file in single quotes to avoid parsing errors.
  642 +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.
  643 +
  644 +`br -plot Algorithm_Dataset/* 'destination.pdf[ncol=3,cmcOptions=[xLog=false,xLimits=(1,20),xBreaks=pretty_breaks(n=10),xTitle=Ranks 1 through 20]]'`
  645 +
614 This function requires a current [R][R] installation with the following packages: 646 This function requires a current [R][R] installation with the following packages:
615 647
616 install.packages(c("ggplot2", "gplots", "reshape", "scales", "jpg", "png")) 648 install.packages(c("ggplot2", "gplots", "reshape", "scales", "jpg", "png"))
@@ -1395,3 +1427,6 @@ Close a provided [Gallery](../cpp_api/gallery/gallery.md). @@ -1395,3 +1427,6 @@ Close a provided [Gallery](../cpp_api/gallery/gallery.md).
1395 <!-- Links --> 1427 <!-- Links -->
1396 [R]: http://www.r-project.org/ "R" 1428 [R]: http://www.r-project.org/ "R"
1397 [QRegExp]: http://doc.qt.io/qt-5/QRegExp.html "QRegExp" 1429 [QRegExp]: http://doc.qt.io/qt-5/QRegExp.html "QRegExp"
  1430 +[QString]: http://doc.qt.io/qt-5/QString.html "QString"
  1431 +[QStringList]: http://doc.qt.io/qt-5/QStringList.html "QStringList"
  1432 +[QPointF]: http://doc.qt.io/qt-5/QPointF.html "QPointF"
openbr/core/plot.cpp
@@ -42,20 +42,6 @@ static QString getScale(const QString &amp;mode, const QString &amp;title, int vals) @@ -42,20 +42,6 @@ static QString getScale(const QString &amp;mode, const QString &amp;title, int vals)
42 // Custom sorting method to ensure datasets are ordered nicely 42 // Custom sorting method to ensure datasets are ordered nicely
43 static bool sortFiles(const QString &fileA, const QString &fileB) 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 return fileA < fileB; 45 return fileA < fileB;
60 } 46 }
61 47
@@ -65,7 +51,8 @@ struct RPlot @@ -65,7 +51,8 @@ struct RPlot
65 QFile file; 51 QFile file;
66 QStringList pivotHeaders; 52 QStringList pivotHeaders;
67 QVector< QSet<QString> > pivotItems; 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 bool flip; 57 bool flip;
71 58
@@ -124,6 +111,7 @@ struct RPlot @@ -124,6 +111,7 @@ struct RPlot
124 } 111 }
125 file.write("data <- rbind(data, tmp)\n"); 112 file.write("data <- rbind(data, tmp)\n");
126 } 113 }
  114 +
127 for (int i=0; i<pivotItems.size(); i++) { 115 for (int i=0; i<pivotItems.size(); i++) {
128 const int size = pivotItems[i].size(); 116 const int size = pivotItems[i].size();
129 if (size > major.size) { 117 if (size > major.size) {
@@ -133,13 +121,10 @@ struct RPlot @@ -133,13 +121,10 @@ struct RPlot
133 minor = Pivot(i, size, pivotHeaders[i]); 121 minor = Pivot(i, size, pivotHeaders[i]);
134 } 122 }
135 } 123 }
  124 +
136 const QString &smooth = destination.get<QString>("smooth", ""); 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 major.smooth = !smooth.isEmpty() && (major.header == smooth) && (major.size > 1); 128 major.smooth = !smooth.isEmpty() && (major.header == smooth) && (major.size > 1);
144 minor.smooth = !smooth.isEmpty() && (minor.header == smooth) && (minor.size > 1); 129 minor.smooth = !smooth.isEmpty() && (minor.header == smooth) && (minor.size > 1);
145 if (major.smooth) major.size = 1; 130 if (major.smooth) major.size = 1;
@@ -147,6 +132,7 @@ struct RPlot @@ -147,6 +132,7 @@ struct RPlot
147 if (major.size < minor.size) 132 if (major.size < minor.size)
148 std::swap(major, minor); 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 flip = minor.header == "Algorithm"; 136 flip = minor.header == "Algorithm";
151 // Format data 137 // Format data
152 if (isEvalFormat) 138 if (isEvalFormat)
@@ -187,7 +173,7 @@ struct RPlot @@ -187,7 +173,7 @@ struct RPlot
187 "TS$Y <- as.character(TS$Y)\n" 173 "TS$Y <- as.character(TS$Y)\n"
188 "CMC$Y <- as.numeric(as.character(CMC$Y))\n" 174 "CMC$Y <- as.numeric(as.character(CMC$Y))\n"
189 "\n" 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 "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)" 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 "\n\t\t}\n\n\t\tdatac <- ddply(data, groupvars, .drop=.drop, .fun = function(xx, col) {\n\t\t\t" 178 "\n\t\t}\n\n\t\tdatac <- ddply(data, groupvars, .drop=.drop, .fun = function(xx, col) {\n\t\t\t"
193 "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}," 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,6 +183,7 @@ struct RPlot
197 "datac$lower <- if(datac[, measurevar] - datac$ci > 0) (datac[, measurevar] - datac$ci) else 0\n\n\t\treturn(datac)\n\t}\n\t" 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 "DET <- summarySE(DET, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t" 184 "DET <- summarySE(DET, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
199 "IET <- summarySE(IET, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t" 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 "ERR <- summarySE(ERR, measurevar=\"X\", groupvars=c(\"Error\", \"%2\", \"Y\"))\n\t" 187 "ERR <- summarySE(ERR, measurevar=\"X\", groupvars=c(\"Error\", \"%2\", \"Y\"))\n\t"
201 "TF <- summarySE(TF, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t" 188 "TF <- summarySE(TF, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
202 "FT <- summarySE(FT, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t" 189 "FT <- summarySE(FT, measurevar=\"Y\", groupvars=c(\"%2\", \"X\"))\n\t"
@@ -204,82 +191,112 @@ struct RPlot @@ -204,82 +191,112 @@ struct RPlot
204 "# Code to format FAR values\n" 191 "# Code to format FAR values\n"
205 "far_names <- list('0.001'=\"FAR = 0.1%\", '0.01'=\"FAR = 1%\")\n" 192 "far_names <- list('0.001'=\"FAR = 0.1%\", '0.01'=\"FAR = 1%\")\n"
206 "far_labeller <- function(variable,value) { return(far_names[as.character(value)]) }\n" 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 // Open output device 198 // Open output device
241 file.write(qPrintable(QString("\n" 199 file.write(qPrintable(QString("\n"
242 "# Open output device\n" 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=800, height=800" : "")));
277 202
278 // Write figures 203 // Write figures
279 file.write("\n" 204 file.write("\n"
280 "# Write figures\n"); 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>("xTitle"), opts.get<QString>("yTitle"))) +
  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, breaks=%2) + annotation_logticks(sides=\"b\")").arg(opts.get<QString>("xLabels", "trans_format(\"log10\", math_format())"), opts.get<QString>("xBreaks", "waiver()"))
  290 + : QString(" + scale_x_continuous(labels=%1, breaks=%2)").arg(opts.get<QString>("xLabels", "percent"), opts.get<QString>("xBreaks", "pretty_breaks(n=10)"))) +
  291 + (opts.getBool("yLog") ? QString(" + scale_y_log10(labels=%1, breaks=%2) + annotation_logticks(sides=\"l\")").arg(opts.get<QString>("yLabels", "trans_format(\"log10\", math_format())"), opts.get<QString>("yBreaks", "waiver()"))
  292 + : QString(" + scale_y_continuous(labels=%1, breaks=%2)").arg(opts.get<QString>("yLabels", "percent"), opts.get<QString>("yBreaks", "pretty_breaks(n=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 bool finalize(bool show = false) 300 bool finalize(bool show = false)
284 { 301 {
285 file.write("dev.off()\n"); 302 file.write("dev.off()\n");
@@ -297,66 +314,31 @@ bool Plot(const QStringList &amp;files, const File &amp;destination, bool show) @@ -297,66 +314,31 @@ bool Plot(const QStringList &amp;files, const File &amp;destination, bool show)
297 { 314 {
298 qDebug("Plotting %d file(s) to %s", files.size(), qPrintable(destination)); 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 // Use a br::file for simple storage of plot options 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("[xTitle=False Accept Rate,yTitle=True Accept Rate,xLog=true,yLog=false]")));
  322 + optMap.insert("detOptions", File(QString("[xTitle=False Accept Rate,yTitle=False Reject Rate,xLog=true,yLog=true]")));
  323 + optMap.insert("ietOptions", File(QString("[xTitle=False Positive Identification Rate (FPIR),yTitle=False Negative Identification Rate (FNIR),xLog=true,yLog=true]")));
  324 + 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)]")));
  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 p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") + 343 p.file.write(qPrintable(QString("qplot(X, data=SD, geom=\"histogram\", fill=Y, position=\"identity\", alpha=I(1/2)") +
362 QString(", xlab=\"Score\", ylab=\"Frequency\"") + 344 QString(", xlab=\"Score\", ylab=\"Frequency\"") +
@@ -364,17 +346,6 @@ bool Plot(const QStringList &amp;files, const File &amp;destination, bool show) @@ -364,17 +346,6 @@ bool Plot(const QStringList &amp;files, const File &amp;destination, bool show)
364 (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()) + 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 QString(" + theme(aspect.ratio=1)\n\n"))); 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 (p.major.size > 1 ? QString(", fill=factor(%1)").arg(p.major.header) : QString()) + 350 (p.major.size > 1 ? QString(", fill=factor(%1)").arg(p.major.header) : QString()) +
380 QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()") + 351 QString(", xlab=\"False Accept Rate\", ylab=\"True Accept Rate\") + theme_minimal()") +
@@ -451,6 +422,20 @@ bool PlotDetection(const QStringList &amp;files, const File &amp;destination, bool show) @@ -451,6 +422,20 @@ bool PlotDetection(const QStringList &amp;files, const File &amp;destination, bool show)
451 qDebug("Plotting %d detection file(s) to %s", files.size(), qPrintable(destination)); 422 qDebug("Plotting %d detection file(s) to %s", files.size(), qPrintable(destination));
452 RPlot p(files, destination, false); 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("[xTitle=False Accepts Per Image,yTitle=True Accept Rate,xLog=true,yLog=false]")));
  428 + optMap.insert("prOptions", File(QString("[xTitle=False Accept Rate,yTitle=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 p.file.write("# Split data into individual plots\n" 439 p.file.write("# Split data into individual plots\n"
455 "plot_index = which(names(data)==\"Plot\")\n" 440 "plot_index = which(names(data)==\"Plot\")\n"
456 "DiscreteROC <- data[grep(\"DiscreteROC\",data$Plot),-c(1)]\n" 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,23 +451,15 @@ bool PlotDetection(const QStringList &amp;files, const File &amp;destination, bool show)
466 if (filesHaveSinglePoint(files)) 451 if (filesHaveSinglePoint(files))
467 plotType = QString("point"); 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 p.file.write(qPrintable(QString("qplot(X, data=Overlap, geom=\"histogram\", position=\"identity\", xlab=\"Overlap\", ylab=\"Frequency\")") + 464 p.file.write(qPrintable(QString("qplot(X, data=Overlap, geom=\"histogram\", position=\"identity\", xlab=\"Overlap\", ylab=\"Frequency\")") +
488 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))") + 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))") +