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.feature.local.matcher;
031
032import java.awt.event.KeyEvent;
033import java.awt.event.KeyListener;
034import java.awt.event.MouseEvent;
035import java.awt.event.MouseMotionListener;
036import java.util.ArrayList;
037import java.util.List;
038
039import javax.swing.JFrame;
040
041import org.openimaj.image.DisplayUtilities;
042import org.openimaj.image.Image;
043import org.openimaj.image.feature.local.keypoints.Keypoint;
044import org.openimaj.image.renderer.ImageRenderer;
045import org.openimaj.math.geometry.line.Line2d;
046import org.openimaj.math.geometry.point.Point2d;
047import org.openimaj.math.geometry.point.Point2dImpl;
048import org.openimaj.util.pair.IndependentPair;
049import org.openimaj.util.pair.Pair;
050
051
052/**
053 * Drawing utility useful for drawing two images and the matches between their feature points
054 *
055 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
056 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
057 *
058 */
059public class MatchingUtilities {
060        /**
061         * Draw matches between two images in the given colour.
062         * Places the images side-by-side and draws a line
063         * for each match.
064         *
065         * @param <T> Pixel type
066         * @param <I> Image type
067         * @param im1 first image
068         * @param im2 second image
069         * @param matches matched features between images
070         * @param col the colour to draw in
071         * @return image drawn on
072         */
073        public static <T, I extends Image<T,I>> I drawMatches(I im1, I im2, List<? extends Pair<? extends Point2d>> matches, T col) {
074                int newwidth = im1.getWidth() + im2.getWidth();
075                int newheight = Math.max(im1.getHeight(), im2.getHeight());
076
077                I out = im1.newInstance(newwidth, newheight);
078                ImageRenderer<T, I> renderer = out.createRenderer();
079                renderer.drawImage(im1, 0, 0);
080                renderer.drawImage(im2, im1.getWidth(), 0);
081
082                if (matches!=null) {
083                        for (Pair<? extends Point2d> p : matches) {
084                                renderer.drawLine(      (int)p.firstObject().getX() + im1.getWidth(),
085                                                                (int)p.firstObject().getY(),
086                                                                (int)p.secondObject().getX(),
087                                                                (int)p.secondObject().getY(),
088                                                                1,col);
089                        }
090                }
091
092                return out;
093        }
094
095        /**
096         * Draw two sets of matches between two images in the given colours.
097         * Places the images side-by-side and draws a line
098         * for each match.
099         *
100         * @param <T> Pixel type
101         * @param <I> Image type
102         * @param im1 first image
103         * @param im2 second image
104         * @param matches first set of matched features between images
105         * @param col the colour to draw first set of matches in
106         * @param matches2 second set of matched features between images
107         * @param col2 the colour to draw second set of matches in
108         * @return image drawn on
109         */
110        public static <T, I extends Image<T,I>> I drawMatches(I im1, I im2, List<? extends Pair<? extends Point2d>> matches, T col, List<? extends Pair<? extends Point2d>> matches2, T col2) {
111                int newwidth = im1.getWidth() + im2.getWidth();
112                int newheight = Math.max(im1.getHeight(), im2.getHeight());
113
114                I out = im1.newInstance(newwidth, newheight);
115                ImageRenderer<T, I> renderer = out.createRenderer();
116                renderer.drawImage(im1, 0, 0);
117                renderer.drawImage(im2, im1.getWidth(), 0);
118
119                if (matches!=null) {
120                        for (Pair<? extends Point2d> p : matches) {
121                                renderer.drawLine(      (int)p.firstObject().getX() + im1.getWidth(),
122                                                                (int)p.firstObject().getY(),
123                                                                (int)p.secondObject().getX(),
124                                                                (int)p.secondObject().getY(),
125                                                                col);
126                        }
127                }
128
129                if (matches2!=null) {
130                        for (Pair<? extends Point2d> p : matches2) {
131                                renderer.drawLine(      (int)p.firstObject().getX() + im1.getWidth(),
132                                                                (int)p.firstObject().getY(),
133                                                                (int)p.secondObject().getX(),
134                                                                (int)p.secondObject().getY(),
135                                                                col2);
136                        }
137                }
138
139                return out;
140        }
141
142        /**
143         * Draw matches between two images in the given colour. The
144         * lines representing the matches are drawn on a copy of the
145         * input image. The positions of the matches should represent
146         * valid points in the input image.
147         *
148         * @param <T> Pixel type
149         * @param <I> Image type
150         * @param image first image
151         * @param matches matched features between images
152         * @param col the colour to draw in
153         * @return image drawn on
154         */
155        public static <T, I extends Image<T,I>> I drawMatches(I image, List<IndependentPair<Point2d, Point2d>> matches, T col) {
156                I out = image.clone();
157                ImageRenderer<T, I> renderer = out.createRenderer();
158
159                if (matches!=null) {
160                        for (IndependentPair<? extends Point2d, ? extends Point2d> p  : matches) {
161                                renderer.drawLine(      (int)p.firstObject().getX(),
162                                                                (int)p.firstObject().getY(),
163                                                                (int)p.secondObject().getX(),
164                                                                (int)p.secondObject().getY(),
165                                                                col);
166                        }
167                }
168
169                return out;
170        }
171
172        static class MouseOverFeatureListener<T, I extends Image<T,I>> implements MouseMotionListener, KeyListener{
173                private JFrame frame;
174                private List<Pair<Keypoint>> matches;
175                private T colour;
176                private I im1;
177                private I im2;
178                private boolean allMode;
179
180                public MouseOverFeatureListener(I im1,I im2, JFrame frame,List<Pair<Keypoint>> matches, T colour) {
181                        this.im1 = im1;
182                        this.im2 = im2;
183                        this.frame = frame;
184                        this.matches = matches;
185                        this.colour = colour;
186                        this.allMode = false;
187                }
188
189                @Override
190                public void mouseDragged(MouseEvent arg0) {
191                        // Do nothing
192                }
193
194                @Override
195                public void mouseMoved(MouseEvent arg0) {
196                        List<Pair<Keypoint>> toDisplay = null;
197                        if(allMode) {
198                                toDisplay = this.matches;
199                        } else {
200                                Point2d mousePoint = new Point2dImpl(arg0.getX()-im1.getWidth(),arg0.getY());
201                                toDisplay = new ArrayList<Pair<Keypoint>>();
202                                for(Pair<Keypoint> kpair : matches){
203                                        Keypoint toCompare = kpair.firstObject();
204                                        if(Line2d.distance(mousePoint, toCompare) < 10){
205                                                toDisplay.add(kpair);
206                                        }
207                                }
208                        }
209
210                        I image = MatchingUtilities.drawMatches(im1, im2, toDisplay, this.colour);
211                        DisplayUtilities.display(image,frame);
212                }
213
214                @Override
215                public void keyPressed(KeyEvent key) {
216                        keyTyped(key);
217                }
218
219                @Override
220                public void keyReleased(KeyEvent arg0) {
221                        // Do nothing
222                }
223
224                @Override
225                public void keyTyped(KeyEvent key) {
226                        if(key.getKeyCode() == KeyEvent.VK_SPACE) allMode = !allMode;
227                }
228        }
229
230        /**
231         * Create an interactive display of matches between two images.
232         *
233         * @param <T> Pixel type
234         * @param <I> Image type
235         * @param im1 first image
236         * @param im2 second image
237         * @param matches matched features between images
238         * @param col the colour to draw in
239         */
240        public static <T, I extends Image<T,I>> void displayMouseOverMatches(I im1, I im2,List<Pair<Keypoint>> matches, T col) {
241                int newwidth = im1.getWidth() + im2.getWidth();
242                int newheight = Math.max(im1.getHeight(), im2.getHeight());
243
244                I out = im1.newInstance(newwidth, newheight);
245                ImageRenderer<T, I> renderer = out.createRenderer();
246                renderer.drawImage(im1, 0, 0);
247                renderer.drawImage(im2, im1.getWidth(), 0);
248
249                JFrame frame = DisplayUtilities.display(out);
250                MouseOverFeatureListener<T, I> mofl = new MouseOverFeatureListener<T,I>(im1, im2, frame, matches, col);
251                frame.addKeyListener(mofl);
252                frame.getContentPane().addMouseMotionListener(mofl);
253        }
254}