001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.experiment.evaluation.retrieval.analysers; 031 032import java.io.BufferedReader; 033import java.io.File; 034import java.io.FileOutputStream; 035import java.io.IOException; 036import java.io.InputStream; 037import java.io.InputStreamReader; 038import java.io.PrintStream; 039import java.util.ArrayList; 040import java.util.List; 041import java.util.Map; 042import java.util.Set; 043import java.util.TreeMap; 044 045import org.lemurproject.ireval.RetrievalEvaluator.Document; 046import org.lemurproject.ireval.RetrievalEvaluator.Judgment; 047import org.openimaj.data.identity.Identifiable; 048import org.openimaj.experiment.evaluation.retrieval.RetrievalAnalyser; 049 050/** 051 * A {@link RetrievalAnalyser} that uses the trec_eval commandline tool to 052 * perform the analysis. 053 * 054 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 055 * 056 * @param <QUERY> 057 * Type of query 058 * @param <DOCUMENT> 059 * Type of document 060 */ 061public class TRECEvalAnalyser<QUERY, DOCUMENT extends Identifiable> 062 implements 063 RetrievalAnalyser<TRECResult, QUERY, DOCUMENT> 064{ 065 String additionalOptions = "-q -c"; 066 String toolPath; 067 068 /** 069 * Default constructor 070 */ 071 public TRECEvalAnalyser() { 072 if (System.getenv("TREC_EVAL") != null) { 073 toolPath = System.getenv("TREC_EVAL"); 074 } else if (System.getProperty("TRECEval.path") != null) { 075 toolPath = System.getProperty("TRECEval.path"); 076 } else if (new File("/usr/local/bin/trec_eval").exists()) { 077 toolPath = "/usr/local/bin/trec_eval"; 078 } else if (new File("/usr/bin/trec_eval").exists()) { 079 toolPath = "/usr/bin/trec_eval"; 080 } else { 081 toolPath = "trec_eval"; 082 } 083 } 084 085 @Override 086 public TRECResult analyse(Map<QUERY, List<DOCUMENT>> results, Map<QUERY, Set<DOCUMENT>> relevant) { 087 try { 088 final File qrels = File.createTempFile("openimaj_trec_eval", ".qrels"); 089 writeQRELS(relevant, new PrintStream(new FileOutputStream(qrels))); 090 091 final File top = File.createTempFile("trec_eval", ".top"); 092 writeTop(results, new PrintStream(new FileOutputStream(top))); 093 094 ProcessBuilder pb; 095 if (additionalOptions != null) 096 pb = new ProcessBuilder(toolPath, additionalOptions, qrels.getAbsolutePath(), top.getAbsolutePath()); 097 else 098 pb = new ProcessBuilder(toolPath, qrels.getAbsolutePath(), top.getAbsolutePath()); 099 100 final Process proc = pb.start(); 101 102 final StreamReader sysout = new StreamReader(proc.getInputStream()); 103 final StreamReader syserr = new StreamReader(proc.getErrorStream()); 104 105 sysout.start(); 106 syserr.start(); 107 108 final int rc = proc.waitFor(); 109 110 final TRECResult analysis = new TRECResult(sysout.builder.toString()); 111 112 if (rc != 0) { 113 System.err.println(pb.command()); 114 throw new RuntimeException("An error occurred running trec_eval: " + syserr.builder.toString()); 115 } 116 117 qrels.delete(); 118 top.delete(); 119 120 return analysis; 121 } catch (final Exception e) { 122 throw new RuntimeException("An error occurred running trec_eval.", e); 123 } 124 } 125 126 /** 127 * Write retrieval results in TREC TOP format. 128 * 129 * @param <Q> 130 * Type of query 131 * @param <D> 132 * Type of Document 133 * @param results 134 * the ranked results. 135 * @param os 136 * stream to write to 137 */ 138 public static <Q, D extends Identifiable> void writeTop(Map<Q, List<D>> results, PrintStream os) { 139 final TreeMap<String, ArrayList<Document>> converted = IREvalAnalyser.convertResults(results); 140 141 for (final String query : converted.keySet()) { 142 for (final Document d : converted.get(query)) { 143 // qid iter docno rank sim run_id 144 os.format("%s %d %s %d %f %s\n", query, 0, d.documentNumber, d.rank, d.score, "runid"); 145 } 146 } 147 } 148 149 /** 150 * Write the ground-truth data in TREC QRELS format. 151 * 152 * @param <Q> 153 * Type of query 154 * @param <D> 155 * Type of Document 156 * @param relevant 157 * the relevant docs for each query 158 * @param os 159 * stream to write to 160 */ 161 public static <Q, D extends Identifiable> void writeQRELS(Map<Q, Set<D>> relevant, PrintStream os) { 162 final TreeMap<String, ArrayList<Judgment>> converted = IREvalAnalyser.convertRelevant(relevant); 163 164 for (final String query : converted.keySet()) { 165 for (final Judgment j : converted.get(query)) { 166 os.format("%s %d %s %d\n", query, 0, j.documentNumber, j.judgment); 167 } 168 } 169 } 170 171 static class StreamReader extends Thread { 172 private StringBuilder builder = new StringBuilder(); 173 private BufferedReader br; 174 175 public StreamReader(InputStream is) { 176 br = new BufferedReader(new InputStreamReader(is)); 177 } 178 179 @Override 180 public void run() { 181 try { 182 String line; 183 while ((line = br.readLine()) != null) { 184 builder.append(line); 185 builder.append("\n"); 186 } 187 } catch (final IOException e) { 188 throw new RuntimeException(e); 189 } finally { 190 try { 191 br.close(); 192 } catch (final IOException e) { 193 } 194 } 195 } 196 } 197}