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.vis;
031
032import gnu.trove.list.array.TIntArrayList;
033import gnu.trove.set.hash.TIntHashSet;
034
035import java.io.File;
036import java.io.IOException;
037import java.util.ArrayList;
038import java.util.List;
039
040import org.kohsuke.args4j.Argument;
041import org.kohsuke.args4j.CmdLineException;
042import org.kohsuke.args4j.CmdLineParser;
043import org.kohsuke.args4j.Option;
044import org.openimaj.feature.local.list.LocalFeatureList;
045import org.openimaj.feature.local.list.MemoryLocalFeatureList;
046import org.openimaj.image.FImage;
047import org.openimaj.image.ImageUtilities;
048import org.openimaj.image.feature.local.keypoints.Keypoint;
049import org.openimaj.image.feature.local.keypoints.KeypointVisualizer;
050import org.openimaj.image.feature.local.keypoints.quantised.QuantisedKeypoint;
051
052/**
053 * Tool to extract prototypical quantised SIFT patches from a set of images and their
054 * corresponding quantised SIFT features.
055 * 
056 * Patches are extracted by blurring the image to the correct scale and then extracting
057 * the patch using its geometry. It is assumed that the patch size is 2 * 2 * 3 * scale
058 * as per the original geometry suggested in Lowe's SIFT paper.
059 * 
060 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
061 *
062 */
063public class QuantisedSIFTPatchExtractor {
064        @Option(name="--path", aliases="-p", required=true, usage="Path to the directory structure containing the quantised SIFT features", metaVar="path")
065        File featurePath;
066        
067        @Option(name="--feature-matches", aliases="-fm", required=true, usage="regex to test whether a file is a feature")
068        String featureMatchesRegex;
069        
070        @Option(name="--image-match", aliases="-im", required=true, usage="regex to apply to feature paths to select required parts")
071        String imageMatchRegex;
072        
073        @Option(name="--image-replace", aliases="-ir", required=true, usage="regex to build image path from feature path using the --image-match")
074        String imageReplaceRegex;
075        
076        @Option(name="--output", aliases="-o", required=false, usage="output directory")
077        File outputDir;
078        
079        @Option(name="--output-file", aliases="-of", required=false, usage="output file for aggregated image")
080        File outputImageFile;
081        
082        @Argument(required=false, usage="required term identifiers")
083        List<Integer> requiredIds;
084        TIntArrayList requiredIdsList;
085        
086        TIntHashSet foundTerms = new TIntHashSet();
087        
088        FImage outputImage;
089        
090        void process() throws IOException {
091                if (outputDir != null) outputDir.mkdirs();
092                
093                if (outputImageFile != null && requiredIdsList != null) {
094                        outputImage = new FImage(128*requiredIdsList.size(), 128);
095                }
096                
097                process(featurePath);
098        }
099        
100        protected boolean process(File file) throws IOException {
101                if (file.isDirectory()) {
102                        for (File f : file.listFiles()) {
103                                boolean ret = process(f);
104                                if (ret) return true;
105                        }
106                } else {
107                        if (file.getAbsolutePath().matches(featureMatchesRegex)) {
108                                return processFeatureFile(file);
109                        }
110                }
111                return false;
112        }
113        
114        protected FImage loadCorrespondingImage(File feature) throws IOException {
115                //get image name/path
116                File imagePath = new File(feature.getAbsolutePath().replaceAll(imageMatchRegex, imageReplaceRegex));
117                System.out.println(feature + " -> " + imagePath);
118                
119                return ImageUtilities.readF(imagePath);
120        }
121        
122        protected boolean processFeatureFile(File featureFile) throws IOException {
123                LocalFeatureList<QuantisedKeypoint> qkeys = MemoryLocalFeatureList.read(featureFile, QuantisedKeypoint.class);
124                FImage image = null;
125                
126                for (QuantisedKeypoint kpt : qkeys) {
127                        if ((requiredIdsList == null || requiredIdsList.contains(kpt.id)) && !foundTerms.contains(kpt.id)) {
128                                if (image == null) image = loadCorrespondingImage(featureFile);
129                                
130                                Keypoint key = new Keypoint();
131                                key.y = kpt.location.y;
132                                key.x = kpt.location.x;
133                                key.scale = kpt.location.scale;
134                                key.ori = kpt.location.orientation;
135
136                                List<Keypoint> keys = new ArrayList<Keypoint>();
137                                keys.add(key);
138
139                                KeypointVisualizer<Float, FImage> viz = new KeypointVisualizer<Float, FImage>(image, keys);
140                                FImage patch = viz.getPatches(128).get(key);
141                                
142                                if (outputDir != null) {
143                                        File patchout = new File(outputDir, "patch-" + kpt.id + ".png");
144                                        ImageUtilities.write(patch, "png", patchout);
145                                }
146                                
147                                if (outputImage != null) {
148                                        System.out.println("term: " + kpt.id + " " + requiredIdsList.indexOf(kpt.id));
149                                        
150                                        outputImage.createRenderer().drawImage(patch, 128 * requiredIdsList.indexOf(kpt.id), 0);
151                                        ImageUtilities.write(outputImage, outputImageFile);
152                                }
153                                
154                                foundTerms.add(kpt.id);
155                                
156                                if (requiredIdsList != null && foundTerms.size() == requiredIdsList.size()) {
157                                        return true;
158                                }
159                        }
160                }
161                
162                return false; 
163        }
164        
165        static QuantisedSIFTPatchExtractor load(String [] args) {
166                QuantisedSIFTPatchExtractor options = new QuantisedSIFTPatchExtractor();
167        CmdLineParser parser = new CmdLineParser( options );
168
169        try {
170                parser.parseArgument( args );
171                if (options.requiredIds != null && options.requiredIds.size() > 0) {
172                        options.requiredIdsList = new TIntArrayList();
173                        for (int i : options.requiredIds) options.requiredIdsList.add(i);
174                }
175        } catch( CmdLineException e ) {
176                System.err.println( e.getMessage() );
177                System.err.println( "java " + QuantisedSIFTPatchExtractor.class.getName() + " options...");
178                parser.printUsage( System.err );
179                System.exit(1);
180        }
181
182        return options;
183        }
184        
185        /**
186         * The main method of the tool.
187         * @param args
188         * @throws IOException
189         */
190        public static void main(String [] args) throws IOException {
191                QuantisedSIFTPatchExtractor extr = QuantisedSIFTPatchExtractor.load(args);
192                extr.process();
193        }
194}