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.tools.clusterquantiser;
031
032import java.io.BufferedReader;
033import java.io.File;
034import java.io.FileInputStream;
035import java.io.FileNotFoundException;
036import java.io.FileReader;
037import java.io.IOException;
038import java.io.ObjectInputStream;
039import java.util.ArrayList;
040import java.util.List;
041
042import org.kohsuke.args4j.CmdLineException;
043import org.kohsuke.args4j.Option;
044import org.kohsuke.args4j.ProxyOptionHandler;
045import org.openimaj.ml.clustering.SpatialClusters;
046import org.openimaj.tools.clusterquantiser.ClusterType.ClusterTypeOp;
047import org.openimaj.util.array.ByteArrayConverter;
048
049/**
050 * Options for {@link ClusterQuantiser} tool.
051 * 
052 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
053 * 
054 */
055public class ClusterQuantiserOptions extends AbstractClusterQuantiserOptions {
056
057        @Option(
058                        name = "--print-time-taken",
059                        aliases = "-ptt",
060                        required = false,
061                        usage = "Print to the standard output the time taken to extract features")
062        boolean printTime = false;
063
064        /**
065         * Construct with arguments
066         * 
067         * @param args
068         */
069        public ClusterQuantiserOptions(String[] args) {
070                super(args);
071        }
072
073        /**
074         * Construct with defaults
075         */
076        public ClusterQuantiserOptions() {
077                super(null);
078        }
079
080        @Option(
081                        name = "--create",
082                        aliases = "-c",
083                        required = false,
084                        usage = "Create a new vocabulary and save as FILE.",
085                        metaVar = "String ")
086        private String createFile;
087        private boolean create_mode = false;
088
089        @Option(
090                        name = "--batched-samples",
091                        aliases = "-bs",
092                        required = false,
093                        usage = "Batched sample mode.",
094                        metaVar = "BOOLEAN")
095        private boolean batchedSampleMode = false;
096
097        @Option(
098                        name = "--cluster-type",
099                        aliases = "-ct",
100                        required = false,
101                        usage = "Specify the type of file to be read.",
102                        handler = ProxyOptionHandler.class)
103        private ClusterType clusterType = ClusterType.HKMEANS;
104        protected ClusterTypeOp clusterTypeOp = (ClusterTypeOp) ClusterType.HKMEANS.getOptions();
105
106        protected Class<? extends SpatialClusters<?>> clusterClass = clusterTypeOp.getClusterClass();
107        protected Class<? extends SpatialClusters<?>> otherClusterClass = clusterTypeOp.getClusterClass();
108
109        @Option(
110                        name = "--samples",
111                        aliases = "-s",
112                        required = false,
113                        usage = "Use NUMBER samples from the input.",
114                        metaVar = "NUMBER")
115        private int samples = -1;
116
117        @Option(
118                        name = "--samples-file",
119                        aliases = "-sf",
120                        required = false,
121                        usage = "Save the samples to a file. Load them from this file if it exists",
122                        metaVar = "FILE")
123        protected File samplesFile = null;
124        protected boolean samplesFileMode = false;
125        private byte[][] sampleKeypoints = null;
126
127        @Option(
128                        name = "--input-file",
129                        aliases = "-f",
130                        required = false,
131                        usage = "Read the input from those specified in FILE.",
132                        metaVar = "FILE")
133        protected File input_file = null;
134
135        @Option(
136                        name = "--output-folder",
137                        aliases = "-o",
138                        required = false,
139                        usage = "Where to output all the quantised loc files",
140                        metaVar = "FILE")
141        private File output_file = null;
142        private ClusterTypeOp otherClusterType;
143
144        /**
145         * @return true if using a samples file
146         */
147        public boolean isSamplesFileMode() {
148                return samplesFileMode;
149        }
150
151        /**
152         * @return the sample points
153         */
154        public byte[][] getSampleKeypoints() {
155                return sampleKeypoints;
156        }
157
158        /**
159         * @return number of samples
160         */
161        public int getSamples() {
162                return samples;
163        }
164
165        /**
166         * @return the samples file
167         */
168        public File getSamplesFile() {
169                return this.samplesFile;
170        }
171
172        @Override
173        public ClusterTypeOp getClusterType() {
174                return this.clusterTypeOp;
175        }
176
177        /**
178         * load the samples file
179         */
180        public void loadSamplesFile() {
181                if (this.sampleKeypoints != null)
182                        return;
183
184                System.err.println("Loading samples file...");
185                ObjectInputStream ois = null;
186                try {
187                        ois = new ObjectInputStream(new FileInputStream(this.getSamplesFile()));
188                        final Object read = ois.readObject();
189                        if (read instanceof byte[][]) {
190                                this.sampleKeypoints = (byte[][]) read;
191                        } else {
192                                this.sampleKeypoints = ByteArrayConverter.intToByte((int[][]) read);
193                        }
194                } catch (final FileNotFoundException e) {
195                        // TODO Auto-generated catch block
196                        e.printStackTrace();
197                } catch (final IOException e) {
198                        // TODO Auto-generated catch block
199                        e.printStackTrace();
200                } catch (final ClassNotFoundException e) {
201                        // TODO Auto-generated catch block
202                        e.printStackTrace();
203                } finally {
204                        try {
205                                ois.close();
206                        } catch (final IOException e) {
207                        }
208                }
209        }
210
211        @Override
212        public void validate() throws CmdLineException {
213                if (createFile != null) {
214                        create_mode = true;
215                }
216                if (infoFile != null) {
217                        if (create_mode)
218                                throw new CmdLineException(null,
219                                                "--info and --create are mutually exclusive.");
220                        this.clusterTypeOp = ClusterType.sniffClusterType(new File(infoFile));
221                        this.clusterClass = this.clusterTypeOp.getClusterClass();
222
223                        if (otherInfoFile != null) {
224                                this.otherClusterType = ClusterType.sniffClusterType(new File(otherInfoFile));
225                                this.otherClusterClass = this.otherClusterType.getClusterClass();
226                        }
227                        info_mode = true;
228                }
229                File quantFile = null;
230                if (quantLocation != null) {
231                        quantFile = new File(quantLocation);
232
233                        if (create_mode)
234                                throw new CmdLineException(null, "--quant and --create are mutually exclusive.");
235                        if (info_mode)
236                                throw new CmdLineException(null, "--quant and --info are mutually exclusive.");
237
238                        quant_mode = true;
239                        this.clusterTypeOp = ClusterType.sniffClusterType(quantFile);
240                        this.clusterClass = this.clusterTypeOp.getClusterClass();
241                }
242                if (samplesFile != null && samplesFile.exists()) {
243                        samplesFileMode = true;
244                        if (!this.batchedSampleMode)
245                                loadSamplesFile();
246                }
247
248                if (!create_mode && !info_mode && !quant_mode && samplesFile == null) {
249                        throw new CmdLineException(null, "");
250                }
251
252                if (samplesFile == null && !info_mode && fileType == null) {
253                        throw new CmdLineException(
254                                        null,
255                                        "--file-type must be specified with --create and --quant arguments. Or you must provied a --samples-file");
256                }
257
258                if (input_file != null && inputFiles.size() > 0)
259                        throw new CmdLineException(
260                                        null,
261                                        "Input files from the commandline arguments not supported with --input-file argument.");
262                if (input_file != null && input_file.exists() != true) {
263                        throw new CmdLineException(
264                                        null,
265                                        "--input-file input source does not exist");
266                }
267                if (this.getCountMode()) {
268                        if (this.extension.equals(".loc"))
269                                this.extension = ".counts";
270                }
271        }
272
273        @Override
274        public String getTreeFile() throws IOException {
275                if (create_mode) {
276                        final File createFileParent = new File(createFile).getAbsoluteFile().getParentFile();
277                        if (!createFileParent.exists()) {
278                                if (!createFileParent.mkdirs()) {
279                                        throw new IOException("Invalid quant file");
280                                }
281                        } else {
282                                if (!createFileParent.isDirectory())
283                                        throw new IOException("Invalid quant file");
284                        }
285                        return createFile;
286                }
287                return super.getTreeFile();
288        }
289
290        /**
291         * @return true if in create mode
292         */
293        public boolean isCreateMode() {
294                return create_mode;
295        }
296
297        /**
298         * @return true if using batched samples
299         */
300        public boolean isBatchedSampleMode() {
301                return this.batchedSampleMode;
302        }
303
304        /**
305         * @return the input files
306         * @throws IOException
307         */
308        public List<File> getInputFiles() throws IOException {
309                final List<File> files = new ArrayList<File>();
310
311                if (inputFiles.size() > 0) {
312                        return inputFiles;
313                } else if (input_file != null) {
314                        BufferedReader br = null;
315                        try {
316                                br = new BufferedReader(new FileReader(input_file));
317
318                                String line;
319                                while ((line = br.readLine()) != null) {
320                                        files.add(new File(line.trim()));
321                                }
322                        } finally {
323                                try {
324                                        br.close();
325                                } catch (final IOException e) {
326                                        e.printStackTrace();
327                                }
328                        }
329                }
330
331                return files;
332        }
333
334        /**
335         * @return the output file
336         * @throws IOException
337         */
338        public synchronized File getOutputFile() throws IOException {
339                if (this.output_file != null) {
340                        if (!output_file.exists())
341                                if (!output_file.mkdirs())
342                                        throw new IOException("Invalid output file");
343                        if (output_file.exists())
344                                if (!output_file.isDirectory())
345                                        throw new IOException("Invalid output file");
346                }
347                return this.output_file;
348        }
349
350        @Override
351        public String getInputFileString() {
352                String inputFiles = "";
353                try {
354                        for (final File f : this.getInputFiles()) {
355                                inputFiles += f.getAbsolutePath() + " ";
356                        }
357                } catch (final IOException e) {
358                }
359                inputFiles = inputFiles.trim();
360                return inputFiles;
361        }
362
363        @Override
364        public String getOutputFileString() {
365                try {
366                        return this.getOutputFile().getAbsolutePath();
367                } catch (final IOException e) {
368                        return null;
369                }
370        }
371
372        @Override
373        public String getOtherInfoFile() {
374                return this.otherInfoFile;
375        }
376
377        @Override
378        public ClusterTypeOp getOtherInfoType() {
379                return this.otherClusterType;
380        }
381
382        @Override
383        public Class<? extends SpatialClusters<?>> getClusterClass() {
384                return this.clusterClass;
385        }
386
387        @Override
388        public Class<? extends SpatialClusters<?>> getOtherInfoClass() {
389                return this.otherClusterClass;
390        }
391
392        /**
393         * Set the input files
394         * 
395         * @param files
396         */
397        public void setInputFiles(List<File> files) {
398                this.inputFiles = files;
399        }
400
401        /**
402         * Set the cluster type
403         * 
404         * @param clusterType
405         */
406        public void setClusterType(ClusterType clusterType) {
407                this.clusterType = clusterType;
408                this.clusterTypeOp = (ClusterTypeOp) clusterType.getOptions();
409        }
410
411        /**
412         * Set the clusterTypeOp
413         * 
414         * @param clusterTypeOp
415         */
416        public void setClusterTypeOp(ClusterTypeOp clusterTypeOp) {
417                this.clusterTypeOp = clusterTypeOp;
418        }
419
420        /**
421         * @return set the root directory
422         * @throws IOException
423         */
424        public String getInputFileCommonRoot() throws IOException {
425                char[] shortestString = null;
426                int currentLongest = 0;
427                for (final File input : this.getInputFiles()) {
428                        final char[] current = input.getAbsolutePath().toCharArray();
429                        if (shortestString == null) {
430                                shortestString = current;
431                                currentLongest = shortestString.length;
432                                continue;
433                        }
434                        int i = 0;
435                        for (; i < currentLongest; i++) {
436                                if (shortestString[i] != current[i])
437                                        break;
438                        }
439                        currentLongest = i;
440                }
441
442                final String substring = new String(shortestString).substring(0, currentLongest);
443                final File ret = new File(substring);
444                if (ret.isDirectory() || substring.endsWith("/"))
445                        return substring;
446                else
447                        return ret.getParent();
448        }
449
450        /**
451         * @return true if timing info should be printed
452         */
453        public boolean printTiming() {
454                return this.printTime;
455        }
456
457        /**
458         * Set the number of samples
459         * 
460         * @param nsamples
461         *            the number of samples
462         */
463        public void setSamples(int nsamples) {
464                this.samples = nsamples;
465        }
466}