package de.fub.agg2graph.gpseval.output; import de.fub.agg2graph.gpseval.TestResult; import de.fub.agg2graph.gpseval.WekaResult; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.List; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; /** * This class is used to export the evaluation results as graphs embedded into a * HTML-document. */ public class DetailedHTMLOutput extends TestResultsOutput { private int mGraphCount = 0; @Override public void run() { String htmlFormat = "<html><head><title>GPSEval Results</title><style>html {font-family: sans-serif;}a {text-decoration: none;color: #145A8F;}section > div {background-color: #B2D1E9;padding: 10px;margin: 10px;-webkit-border-radius: 10px; -moz-border-radius: 10px; -ms-border-radius: 10px; -o-border-radius: 10px; border-radius: 10px;}h1 {font-size: 16px;color: #333;}h2 {font-size: 14px;color: #333;}img {background-color: #fff;padding: 10px;-webkit-border-radius: 10px; -moz-border-radius: 10px; -ms-border-radius: 10px; -o-border-radius: 10px; border-radius: 10px;margin: 0 10px 10px 0;}table {display: inline-block;vertical-align: top;margin: 0 10px 10px 0;border-spacing: 0;}caption {caption-side:top;}th:first-child {-webkit-border-radius: 5px 0 0 0; -moz-border-radius: 5px 0 0 0; -ms-border-radius: 5px 0 0 0; -o-border-radius: 5px 0 0 0; border-radius: 5px 0 0 0;}th:last-child {-webkit-border-radius: 0 5px 0 0; -moz-border-radius: 0 5px 0 0; -ms-border-radius: 0 5px 0 0; -o-border-radius: 0 5px 0 0; border-radius: 0 5px 0 0;}th {background-color: #2558A6;padding: 5px;color: #fff;}td {background-color: #fff;padding: 5px;border-bottom: 1px solid #999;border-right: 1px solid #999;}td:first-child {border-left: 1px solid #2558A6;}td:last-child {border-right: 1px solid #2558A6;}tr:last-child td {border-bottom: 1px solid #2558A6;}tr:last-child td:first-child {-webkit-border-radius: 0 0 0 5px; -moz-border-radius: 0 0 0 5px; -ms-border-radius: 0 0 0 5px; -o-border-radius: 0 0 0 5px; border-radius: 0 0 0 5px;}tr:last-child td:last-child {-webkit-border-radius: 0 0 5px 0; -moz-border-radius: 0 0 5px 0; -ms-border-radius: 0 0 5px 0; -o-border-radius: 0 0 5px 0; border-radius: 0 0 5px 0;}</style></head><body><header><nav><h1>Test cases:</h1><ol>%s</ol></nav></header><section>%s</section></body></html>"; String navFormat = "<li><a href=\"#results%s\">%s</a></li>"; String resultFormat = "<div><a name=\"results%s\"></a><h1>%s</h1>%s</div>"; String graphFormat = "<img src=\"%s.svg\" alt=\"%s\" />"; StringBuilder nav = new StringBuilder(); StringBuilder results = new StringBuilder(); // for each test case result for (int i = 0; i < mResults.size(); i++) { TestResult result = mResults.get(i); StringBuilder resultContent = new StringBuilder(); // Add navigation link for the current test case nav.append(String.format(navFormat, i, result.mCfg.getName())); // Generate classifier graphs and tables int cvGraphNum = generateClassifierGraph( result.mCrossValidationResults, "Crossvalidation"); if (cvGraphNum != -1) { resultContent.append(String.format(graphFormat, cvGraphNum, "Crossvalidation")); } int ttGraphNum = generateClassifierGraph( result.mTrainingTestResults, "Training/Test"); if (ttGraphNum != -1) { resultContent.append(String.format(graphFormat, ttGraphNum, "Training/Test")); } resultContent.append(generateClassifierTable( result.mCrossValidationResults, "Crossvalidation")); resultContent.append(generateClassifierTable( result.mTrainingTestResults, "Training/Test")); // Generate cross validation graphs/tables for classes StringBuilder cvTables = new StringBuilder(); resultContent.append("<h2>Crossvalidation results</h2>"); for (WekaResult wResult : result.mCrossValidationResults) { int graphNum = generateClassesGraph(wResult); if (graphNum != -1) { resultContent.append(String.format(graphFormat, graphNum, "Crossvalidation: " + wResult.mClsName)); cvTables.append(generateClassesTable(wResult)); } } resultContent.append("<br />"); resultContent.append(cvTables.toString()); // Generate test/train graphs/tables for classes StringBuilder ttTables = new StringBuilder(); resultContent.append("<h2>Train/Test results</h2>"); for (WekaResult wResult : result.mTrainingTestResults) { int graphNum = generateClassesGraph(wResult); if (graphNum != -1) { resultContent.append(String.format(graphFormat, graphNum, "Train/Test: " + wResult.mClsName)); ttTables.append(generateClassesTable(wResult)); } } resultContent.append("<br />"); resultContent.append(ttTables.toString()); results.append(String.format(resultFormat, i, result.mCfg.getName(), resultContent.toString())); } // finally put all HTML together and save to file String out = String.format(htmlFormat, nav.toString(), results.toString()); File outFile = mResultsFolder.resolve("index.html").toFile(); try (FileWriter fw = new FileWriter(outFile); BufferedWriter bw = new BufferedWriter(fw)) { bw.write(out); } catch (IOException ex) { Logger.getLogger(DetailedHTMLOutput.class.getName()).log( Level.SEVERE, null, ex); } Logger.getLogger(DetailedHTMLOutput.class.getName()).log(Level.INFO, "Done. Written results to {0}", outFile.getAbsolutePath()); } /** * Generate the graph (data-file, gnuPlot-script and svg-image) that shows * how many tracks were classified correctly per classifier. * * @param results * @param title * @return */ private int generateClassifierGraph(List<WekaResult> results, String title) { int graphNum = mGraphCount++; // generate Data File File dataFile = mResultsFolder.resolve(graphNum + ".graphData") .toFile(); try (FileWriter fw = new FileWriter(dataFile); BufferedWriter bw = new BufferedWriter(fw)) { bw.write("Classifier \"correct classified\" \"incorrect classified\"\n"); for (WekaResult result : results) { double correct = result.mEval.correct(); double incorrect = result.mEval.incorrect(); double sum = correct + incorrect; double correctPerc = sum > 0 ? correct / sum : 0; double incorrectPerc = sum > 0 ? incorrect / sum : 0; bw.write(String.format(Locale.US, "\"%s\" %f %f\n", result.mClsName.replaceAll("[a-z]", ""), correctPerc, incorrectPerc)); } } catch (IOException ex) { Logger.getLogger(DetailedHTMLOutput.class.getName()).log( Level.SEVERE, null, ex); return -1; } // genereate GnuPlot script file File graphFile = mResultsFolder.resolve(graphNum + ".svg").toFile(); File scriptFile = mResultsFolder.resolve(graphNum + ".graphScript") .toFile(); try (FileWriter fw = new FileWriter(scriptFile); BufferedWriter bw = new BufferedWriter(fw)) { bw.write("set title \"" + title + "\"\n"); bw.write("set auto x\n"); bw.write("set yrange [0:1]\n"); bw.write("set style data histogram\n"); bw.write("set style histogram rowstacked\n"); bw.write("set style fill solid border -1\n"); bw.write("set boxwidth 0.4\n"); bw.write("set xtic scale 0\n"); bw.write("set grid ytics lw 0.5 lc rgb \"#999999\"\n"); bw.write("set key below\n"); bw.write("set lmargin 4\n"); bw.write("set rmargin 0\n"); bw.write("set tmargin 2\n"); bw.write("set bmargin 4\n"); bw.write("set terminal svg size 300,300\n"); bw.write("set output \"" + graphFile.getAbsolutePath() + "\"\n"); bw.write("plot \"" + dataFile.getAbsolutePath() + "\" using 2:xtic(1) ti col lc rgb \"#8AC62F\", \"\" u 3 ti col lc rgb \"#BE2F3B\"\n"); } catch (IOException ex) { Logger.getLogger(DetailedHTMLOutput.class.getName()).log( Level.SEVERE, null, ex); return -1; } // run GnuPlot try { runGnuPlot(scriptFile.getAbsolutePath()); } catch (IOException ex) { Logger.getLogger(DetailedHTMLOutput.class.getName()).log( Level.SEVERE, null, ex); return -1; } return graphNum; } /** * Generate the table that shows how many tracks were classified correctly * per classifier. * * @param results * @param title * @return */ private String generateClassifierTable(List<WekaResult> results, String title) { StringBuilder table = new StringBuilder(); table.append("<table>"); table.append("<caption>"); table.append(title); table.append("</caption>"); table.append("<thead>"); table.append("<tr>"); table.append("<th>"); table.append("Classifier"); table.append("</th>"); table.append("<th>"); table.append("Correct cfd."); table.append("</th>"); table.append("<th>"); table.append("Incorrect cfd."); table.append("</th>"); table.append("</tr>"); table.append("</thead>"); table.append("<tbody>"); for (WekaResult result : results) { double correct = result.mEval.correct(); double incorrect = result.mEval.incorrect(); double sum = correct + incorrect; double correctPerc = sum > 0 ? correct / sum * 100 : 0; double incorrectPerc = sum > 0 ? incorrect / sum * 100 : 0; table.append("<tr>"); table.append("<td>"); table.append(result.mClsName); table.append("</td>"); table.append("<td>"); table.append(correctPerc).append("%"); table.append(" (").append((int) correct).append(")"); table.append("</td>"); table.append("<td>"); table.append(incorrectPerc).append("%"); table.append(" (").append((int) incorrect).append(")"); table.append("</td>"); table.append("</tr>"); } table.append("</tbody>"); table.append("</table>"); return table.toString(); } /** * Generate the graph (data-file, gnuPlot-script and svg-image) that shows * the precision/recall-values for each class. * * @param result * @return */ private int generateClassesGraph(WekaResult result) { int graphNum = mGraphCount++; // generate Data File File dataFile = mResultsFolder.resolve(graphNum + ".graphData") .toFile(); try (FileWriter fw = new FileWriter(dataFile); BufferedWriter bw = new BufferedWriter(fw)) { bw.write("Class Precision Recall\n"); int numClasses = result.mClasses.size(); for (int i = 0; i < numClasses; i++) { bw.write(String.format(Locale.US, "\"%s\" %f %f\n", result.mClasses.get(i), result.mEval.precision(i), result.mEval.recall(i))); } } catch (IOException ex) { Logger.getLogger(DetailedHTMLOutput.class.getName()).log( Level.SEVERE, null, ex); return -1; } // genereate GnuPlot script file File graphFile = mResultsFolder.resolve(graphNum + ".svg").toFile(); File scriptFile = mResultsFolder.resolve(graphNum + ".graphScript") .toFile(); try (FileWriter fw = new FileWriter(scriptFile); BufferedWriter bw = new BufferedWriter(fw)) { bw.write("set title \"" + result.mClsName + "\"\n"); bw.write("set auto x\n"); bw.write("set yrange [0:1]\n"); bw.write("set style data histogram\n"); bw.write("set style histogram cluster gap 1\n"); bw.write("set style fill solid border -1\n"); bw.write("set boxwidth 0.6\n"); bw.write("set xtic scale 0\n"); bw.write("set grid ytics lw 0.5 lc rgb \"#999999\"\n"); bw.write("set key below\n"); bw.write("set lmargin 4\n"); bw.write("set rmargin 0\n"); bw.write("set tmargin 2\n"); bw.write("set bmargin 4\n"); bw.write("set terminal svg size " + (result.mClasses.size() * 125) + ",300\n"); bw.write("set output \"" + graphFile.getAbsolutePath() + "\"\n"); bw.write("plot \"" + dataFile.getAbsolutePath() + "\" using 2:xtic(1) ti col lc rgb \"#8AC62F\", \"\" u 3 ti col lc rgb \"#289ECC\"\n"); } catch (IOException ex) { Logger.getLogger(DetailedHTMLOutput.class.getName()).log( Level.SEVERE, null, ex); return -1; } // run GnuPlot try { runGnuPlot(scriptFile.getAbsolutePath()); } catch (IOException ex) { Logger.getLogger(DetailedHTMLOutput.class.getName()).log( Level.SEVERE, null, ex); return -1; } return graphNum; } /** * Generate the table that shows the precision/recall-values for each class. * * @param result * @return */ private String generateClassesTable(WekaResult result) { StringBuilder table = new StringBuilder(); table.append("<table>"); table.append("<caption>"); table.append(result.mClsName); table.append("</caption>"); table.append("<thead>"); table.append("<tr>"); table.append("<th>"); table.append("Class"); table.append("</th>"); table.append("<th>"); table.append("Precision"); table.append("</th>"); table.append("<th>"); table.append("Recall"); table.append("</th>"); table.append("</tr>"); table.append("</thead>"); table.append("<tbody>"); int numClasses = result.mClasses.size(); for (int i = 0; i < numClasses; i++) { table.append("<tr>"); table.append("<td>"); table.append(result.mClasses.get(i)); table.append("</td>"); table.append("<td>"); table.append(result.mEval.precision(i)); table.append("</td>"); table.append("<td>"); table.append(result.mEval.recall(i)); table.append("</td>"); table.append("</tr>"); } table.append("</tbody>"); table.append("</table>"); return table.toString(); } /** * Run gnuPlot with the specified script. (currently only Linux is * supported) * * @param script * @throws IOException */ private void runGnuPlot(String script) throws IOException { String plotCmd = "/usr/bin/gnuplot"; if (System.getProperty("os.name").contains("Win")){ plotCmd = "C:\\gnuplot\\gnuplot.exe"; } ProcessBuilder pb = new ProcessBuilder(plotCmd, script); pb.directory(mResultsFolder.toFile()); pb.start(); } }