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}