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.image.analysis.algorithm;
031
032import java.io.File;
033import java.io.IOException;
034import java.util.Comparator;
035
036import org.openimaj.image.DisplayUtilities;
037import org.openimaj.image.FImage;
038import org.openimaj.image.ImageUtilities;
039import org.openimaj.image.MBFImage;
040import org.openimaj.image.analyser.ImageAnalyser;
041import org.openimaj.image.colour.RGBColour;
042import org.openimaj.image.pixel.FValuePixel;
043import org.openimaj.image.processing.algorithm.MeanCenter;
044import org.openimaj.math.geometry.shape.Rectangle;
045import org.openimaj.util.queue.BoundedPriorityQueue;
046
047/**
048 * Basic template matching for {@link FImage}s. Template matching is
049 * performed in the spatial domain.
050 *
051 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
052 */
053public class TemplateMatcher implements ImageAnalyser<FImage> {
054        /**
055         * Different algorithms for comparing templates to images.
056         *
057         * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
058         */
059        public enum Mode {
060                /**
061                 * Compute the score at a point as the sum-squared difference between the image
062                 * and the template with the top-left at the given point. The {@link TemplateMatcher}
063                 * will account for the offset to the centre of the template internally.
064                 *
065                 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
066                 */
067                SUM_SQUARED_DIFFERENCE {
068                        @Override
069                        protected float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) {
070                                final float[][] imageData = image.pixels;
071                                final float[][] templateData = template.pixels;
072
073                                return computeMatchScore(imageData, x, y, templateData, 0, 0, template.width, template.height);
074                        }
075
076                        @Override
077                        public final float computeMatchScore(final float[][] img, int x, int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) {
078                                final int stopX1 = templateWidth + x;
079                                final int stopY1 = templateHeight + y;
080                                final int stopX2 = templateWidth + templateX;
081                                final int stopY2 = templateHeight + templateY;
082
083                                float score = 0;
084                                for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) {
085                                        for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) {
086                                                float diff = (img[yy1][xx1] - template[yy2][xx2]);
087
088                                                score += diff*diff;
089                                        }
090                                }
091                                return score;
092                        }
093
094
095                        @Override
096                        public boolean scoresAscending() {
097                                return false; //smaller scores are better
098                        }
099                },
100                /**
101                 * Compute the normalised score at a point as the sum-squared difference between the image
102                 * and the template with the top-left at the given point. The {@link TemplateMatcher}
103                 * will account for the offset to the centre of the template internally.
104                 *
105                 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
106                 */
107                NORM_SUM_SQUARED_DIFFERENCE {
108                        @Override
109                        protected float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) {
110                                float score = 0;
111                                float si = 0;
112                                final float st = (Float)workingSpace;
113
114                                final float[][] imageData = image.pixels;
115                                final float[][] templateData = template.pixels;
116
117                                final int stopX = template.width + x;
118                                final int stopY = template.height + y;
119
120                                for (int yy=y, j=0; yy<stopY; yy++, j++) {
121                                        for (int xx=x, i=0; xx<stopX; xx++, i++) {
122                                                float diff = (imageData[yy][xx] - templateData[j][i]);
123
124                                                score += diff*diff;
125                                                si += (imageData[yy][xx] * imageData[yy][xx]);
126                                        }
127                                }
128
129                                return (float) (score / Math.sqrt(si*st));
130                        }
131
132                        @Override
133                        public final float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) {
134                                final int stopX1 = templateWidth + x;
135                                final int stopY1 = templateHeight + y;
136                                final int stopX2 = templateWidth + templateX;
137                                final int stopY2 = templateHeight + templateY;
138
139                                float s1 = 0;
140                                float s2 = 0;
141                                float score = 0;
142
143                                for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) {
144                                        for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) {
145                                                float diff = (img[yy1][xx1] - template[yy2][xx2]);
146                                                score += diff*diff;
147                                                s1 += (img[yy1][xx1] * img[yy1][xx1]);
148                                                s2 += (template[yy2][xx2] * template[yy2][xx2]);
149                                        }
150                                }
151
152                                return (float) (score / Math.sqrt(s1*s2));
153                        }
154
155
156                        @Override
157                        public boolean scoresAscending() {
158                                return false; //smaller scores are better
159                        }
160
161                        @Override
162                        public Float prepareWorkingSpace(FImage template) {
163                                float sumsq = 0;
164
165                                for (int y=0; y<template.height; y++)
166                                        for (int x=0; x<template.width; x++)
167                                                sumsq += template.pixels[y][x]*template.pixels[y][x];
168
169                                return new Float(sumsq);
170                        }
171                },
172                /**
173                 * Compute the score at a point as the summed product between the image
174                 * and the template with the top-left at the point given. The {@link TemplateMatcher}
175                 * will account for the offset to the centre of the template internally.
176                 *
177                 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
178                 */
179                CORRELATION {
180                        @Override
181                        protected float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) {
182                                final float[][] imageData = image.pixels;
183                                final float[][] templateData = template.pixels;
184
185                                return computeMatchScore(imageData, x, y, templateData, 0, 0, template.width, template.height);
186                        }
187
188                        @Override
189                        public float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) {
190                                float score = 0;
191
192                                final int stopX1 = templateWidth + x;
193                                final int stopY1 = templateHeight + y;
194                                final int stopX2 = templateWidth + templateX;
195                                final int stopY2 = templateHeight + templateY;
196
197                                for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) {
198                                        for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) {
199                                                float prod = (img[yy1][xx1] * template[yy2][xx2]);
200
201                                                score += prod;
202                                        }
203                                }
204
205                                return score;
206                        }
207
208                        @Override
209                        public boolean scoresAscending() {
210                                return true; //bigger scores are better
211                        }
212                },
213                /**
214                 * Compute the normalised score at a point as the summed product between the image
215                 * and the template with the top-left at the point given. The {@link TemplateMatcher}
216                 * will account for the offset to the centre of the template internally.
217                 *
218                 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
219                 */
220                NORM_CORRELATION {
221                        @Override
222                        protected float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) {
223                                float score = 0;
224                                float si = 0;
225                                final float st = (Float)workingSpace;
226
227                                final float[][] imageData = image.pixels;
228                                final float[][] templateData = template.pixels;
229
230                                final int stopX = template.width + x;
231                                final int stopY = template.height + y;
232
233                                for (int yy=y, j=0; yy<stopY; yy++, j++) {
234                                        for (int xx=x, i=0; xx<stopX; xx++, i++) {
235                                                float prod = (imageData[yy][xx] * templateData[j][i]);
236                                                score += prod;
237                                                si += (imageData[yy][xx] * imageData[yy][xx]);
238                                        }
239                                }
240
241                                return (float) (score / Math.sqrt(si*st));
242                        }
243
244                        @Override
245                        public float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) {
246                                float score = 0;
247                                float s1 = 0;
248                                float s2 = 0;
249
250                                final int stopX1 = templateWidth + x;
251                                final int stopY1 = templateHeight + y;
252                                final int stopX2 = templateWidth + templateX;
253                                final int stopY2 = templateHeight + templateY;
254
255                                int xx1,xx2,yy1,yy2;
256                                for (yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) {
257                                        for (xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) {
258                                                float prod = (img[yy1][xx1] * template[yy2][xx2]);
259
260                                                s1 += (img[yy1][xx1] * img[yy1][xx1]);
261                                                s2 += (template[yy2][xx2] * template[yy2][xx2]);
262
263                                                score += prod;
264                                        }
265                                }
266
267                                return (float) (score / Math.sqrt(s1*s2));
268                        }
269
270                        @Override
271                        public boolean scoresAscending() {
272                                return true; //bigger scores are better
273                        }
274
275                        @Override
276                        public Float prepareWorkingSpace(FImage template) {
277                                float sumsq = 0;
278
279                                for (int y=0; y<template.height; y++)
280                                        for (int x=0; x<template.width; x++)
281                                                sumsq += template.pixels[y][x]*template.pixels[y][x];
282
283                                return new Float(sumsq);
284                        }
285                },
286                /**
287                 * Compute the score at a point as the summed product between the mean-centered image patch
288                 * and the mean-centered template with the top-left at the point given. The {@link TemplateMatcher}
289                 * will account for the offset to the centre of the template internally.
290                 *
291                 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
292                 */
293                CORRELATION_COEFFICIENT {
294                        @Override
295                        protected final float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) {
296                                final float[][] imageData = image.pixels;
297                                final float[][] templateData = template.pixels;
298
299                                final float templateMean = (Float)workingSpace;
300                                final float imgMean = MeanCenter.patchMean(imageData);
301
302                                return computeMatchScore(imageData, x, y, imgMean, templateData, 0, 0, template.width, template.height, templateMean);
303                        }
304
305                        @Override
306                        public final float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) {
307                                float imgMean = MeanCenter.patchMean(img, x, y, templateWidth, templateHeight);
308                                float templateMean = MeanCenter.patchMean(template, templateX, templateY, templateWidth, templateHeight);
309
310                                return computeMatchScore(img, x, y, imgMean, template, templateX, templateY, templateWidth, templateHeight, templateMean);
311                        }
312
313                        private final float computeMatchScore(final float[][] img, final int x, final int y, final float imgMean, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight, final float templateMean) {
314                                final int stopX1 = templateWidth + x;
315                                final int stopY1 = templateHeight + y;
316                                final int stopX2 = templateWidth + templateX;
317                                final int stopY2 = templateHeight + templateY;
318
319                                float score = 0;
320                                for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) {
321                                        for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) {
322                                                float prod = ((img[yy1][xx1] - imgMean) * (template[yy2][xx2] - templateMean));
323                                                score += prod;
324                                        }
325                                }
326
327                                return score;
328                        }
329
330                        @Override
331                        public boolean scoresAscending() {
332                                return true; //bigger scores are better
333                        }
334
335                        @Override
336                        public Float prepareWorkingSpace(FImage template) {
337                                return MeanCenter.patchMean(template.pixels);
338                        }
339                },
340                /**
341                 * Compute the normalised score at a point as the summed product between the mean-centered image patch
342                 * and the mean-centered template with the top-left at the point given. The {@link TemplateMatcher}
343                 * will account for the offset to the centre of the template internally.
344                 *
345                 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
346                 */
347                NORM_CORRELATION_COEFFICIENT {
348                        @Override
349                        protected final float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace) {
350                                final int width = template.width;
351                                final int height = template.height;
352
353                                float imgMean = MeanCenter.patchMean(image.pixels, x, y, width, height);
354
355                                float score = 0;
356                                float si = 0;
357                                final float st = (Float)workingSpace;
358
359                                final float[][] imageData = image.pixels;
360                                final float[][] templateData = template.pixels;
361
362                                for (int j=0; j<height; j++) {
363                                        for (int i=0; i<width; i++) {
364                                                float ival = imageData[j+y][i+x] - imgMean;
365
366                                                float prod = (ival * templateData[j][i]);
367
368                                                score += prod;
369
370                                                si += (ival * ival);
371                                        }
372                                }
373
374                                double norm = Math.sqrt(si * st);
375
376                                if (norm == 0) return 0;
377
378                                return (float) (score / norm);
379                        }
380
381                        @Override
382                        public final float computeMatchScore(final float[][] img, final int x, final int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight) {
383                                float imgMean = MeanCenter.patchMean(img, x,y,templateWidth, templateHeight);
384                                float templateMean = MeanCenter.patchMean(template, templateX, templateY, templateWidth, templateHeight);
385
386                                final int stopX1 = templateWidth + x;
387                                final int stopY1 = templateHeight + y;
388                                final int stopX2 = templateWidth + templateX;
389                                final int stopY2 = templateHeight + templateY;
390
391                                float score = 0;
392                                float s1 = 0;
393                                float s2 = 0;
394
395                                for (int yy1=y, yy2=templateY; yy1<stopY1 && yy2 < stopY2; yy1++, yy2++) {
396                                        for (int xx1=x, xx2=templateX; xx1<stopX1 && xx2 < stopX2; xx1++, xx2++) {
397                                                float ival = (img[yy1][xx1] - imgMean);
398                                                float tval = (template[yy2][xx2] - templateMean);
399
400                                                float prod = (ival * tval);
401
402                                                score += prod;
403
404                                                s1 += (ival * ival);
405                                                s2 += (tval * tval);
406                                        }
407                                }
408
409                                double norm = Math.sqrt(s1*s2);
410
411                                if (norm == 0) return 0;
412
413                                return (float) (score / norm);
414                        }
415
416                        @Override
417                        public boolean scoresAscending() {
418                                return true; //bigger scores are better
419                        }
420
421                        @Override
422                        public FImage prepareTemplate(FImage template) {
423                                return template.process(new MeanCenter());
424                        }
425
426                        @Override
427                        public Float prepareWorkingSpace(FImage template) {
428                                float sumsq = 0;
429
430                                for (int y=0; y<template.height; y++)
431                                        for (int x=0; x<template.width; x++)
432                                                sumsq += template.pixels[y][x]*template.pixels[y][x];
433
434                                return sumsq;
435                        }
436                }
437                ;
438
439                /**
440                 * Compute the matching score between the image and template, with the top-left of the
441                 * template at (x, y) in the image.
442                 * @param image The image.
443                 * @param template The template.
444                 * @param x The x-ordinate top-left of the template in the image
445                 * @param y The y-ordinate top-left of the template in the image
446                 * @param workingSpace The working space created by #prepareWorkingSpace()
447                 * @return The match score.
448                 */
449                protected abstract float computeMatchScore(final FImage image, final FImage template, final int x, final int y, final Object workingSpace);
450
451                /**
452                 * Compute the matching score between the image and template, with the top-left of the
453                 * template at (x, y) in the image. The coordinates of the template can be specified,
454                 * so it is possible for the actual template to be a sub-image of the given data.
455                 *
456                 * @param img the image data
457                 * @param x the x-ordinate of the top-left of the template
458                 * @param y the y-ordinate of the top-left of the template
459                 * @param template the template data
460                 * @param templateX the top-left x-ordinate of the template in the template data
461                 * @param templateY the top-left y-ordinate of the template in the template data
462                 * @param templateWidth the width of the template in the template data
463                 * @param templateHeight the height of the template in the template data
464                 * @return the match score.
465                 */
466                public abstract float computeMatchScore(final float[][] img, int x, int y, final float[][] template, final int templateX, final int templateY, final int templateWidth, final int templateHeight);
467
468                /**
469                 * Are the scores ascending (i.e. bigger is better) or descending (smaller is better)?
470                 * @return true is bigger scores are better; false if smaller scores are better.
471                 */
472                public abstract boolean scoresAscending();
473
474                /**
475                 * Prepare the template if necessary. The default implementation
476                 * just returns the template, but subclasses can override.
477                 * @param template the template image
478                 * @return the processed template image
479                 */
480                protected FImage prepareTemplate(FImage template) {
481                        return template;
482                }
483
484                /**
485                 * Prepare an object to hold the working space required by the
486                 * mode during score computation. Most modes don't require
487                 * this, so the default implementation returns null, but
488                 * it can be overridden.
489                 *
490                 * @param template the template
491                 * @return an object representing the required working space for
492                 *              the {@link #computeMatchScore(FImage, FImage, int, int, Object)} method.
493                 */
494                protected Object prepareWorkingSpace(FImage template) {
495                        return null;
496                }
497        }
498
499        private FImage template;
500        private Mode mode;
501        private Object workingSpace;
502        private Rectangle searchBounds;
503        private FImage responseMap;
504
505        /**
506         * Default constructor with the template to match and the mode
507         * with which to estimate template responses. When matching is
508         * performed by {@link #analyseImage(FImage)}, the whole image
509         * will be searched.
510         *
511         * @param template The template
512         * @param mode The mode.
513         */
514        public TemplateMatcher(FImage template, Mode mode) {
515                this.mode = mode;
516                this.template = mode.prepareTemplate(template);
517                this.workingSpace = mode.prepareWorkingSpace(this.template);
518        }
519
520        /**
521         * Construct with the template to match, the mode with which to
522         * estimate template responses and the bounds rectangle in which
523         * to search. The search bounds rectangle is defined with respect
524         * to the centre of the template.
525         *
526         * @param template The template
527         * @param mode The mode.
528         * @param bounds The bounding box for search.
529         */
530        public TemplateMatcher(FImage template, Mode mode, Rectangle bounds) {
531                this.searchBounds = bounds;
532                this.mode = mode;
533                this.template = mode.prepareTemplate(template);
534                this.workingSpace = mode.prepareWorkingSpace(this.template);
535        }
536
537        /**
538         * @return the search bound rectangle
539         */
540        public Rectangle getSearchBounds() {
541                return searchBounds;
542        }
543
544        /**
545         * Set the search bounds rectangle. The search bounds rectangle
546         * is defined with respect to the centre of the template.
547         * Setting to <code>null</code> results in the entire image
548         * being searched.
549         *
550         * @param searchBounds the search bounds to set
551         */
552        public void setSearchBounds(Rectangle searchBounds) {
553                this.searchBounds = searchBounds;
554        }
555
556        /**
557         * Perform template matching. If a bounds rectangle
558         * is has not been set or is null, then the whole
559         * image will be searched. Otherwise the area of the image
560         * which lies in the previously set search bounds will be
561         * searched.
562         *
563         * @see org.openimaj.image.analyser.ImageAnalyser#analyseImage(org.openimaj.image.Image)
564         */
565        @Override
566        public void analyseImage(FImage image) {
567                Rectangle searchSpace = null;
568
569                if (this.searchBounds != null) {
570                        final int halfWidth = template.width / 2;
571                        final int halfHeight = template.height / 2;
572
573                        float x = Math.max(searchBounds.x - halfWidth, 0);
574                        x = Math.min(x, image.width-template.width);
575                        float width = searchBounds.width;
576                        if (searchBounds.x - halfWidth < 0) {
577                                width += (searchBounds.x - halfWidth);
578                        }
579                        if (x + width > image.width - template.width)
580                                width += (image.width - template.width) - (x+width);
581
582                        float y = Math.max(searchBounds.y - halfHeight, 0);
583                        y = Math.min(y, image.height - template.height);
584                        float height = searchBounds.height;
585                        if (searchBounds.y - halfHeight < 0) {
586                                height += (searchBounds.y - halfHeight);
587                        }
588                        if (y + height > image.height - template.height)
589                                height += (image.height - template.height) - (y+height);
590
591                        searchSpace = new Rectangle(
592                                        x,
593                                        y,
594                                        width,
595                                        height
596                        );
597
598                } else {
599                        searchSpace = new Rectangle(
600                                        0,
601                                        0,
602                                        image.width - template.width + 1,
603                                        image.height - template.height + 1
604                        );
605                }
606
607                final int scanX = (int) searchSpace.x;
608                final int scanY = (int) searchSpace.y;
609                final int scanWidth = (int)searchSpace.width;
610                final int scanHeight = (int)searchSpace.height;
611
612                responseMap = new FImage(scanWidth, scanHeight);
613                final float[][] responseMapData = responseMap.pixels;
614
615                for (int y=0; y<scanHeight; y++) {
616                        for (int x=0; x<scanWidth; x++) {
617                                responseMapData[y][x] = mode.computeMatchScore(image, template, x+scanX, y+scanY, workingSpace);
618                        }
619                }
620        }
621
622        /**
623         * Get the top-N "best" responses found by the template matcher.
624         *
625         * @param numResponses The number of responses
626         * @return the best responses found
627         */
628        public FValuePixel[] getBestResponses(int numResponses) {
629                Comparator<FValuePixel> comparator = mode.scoresAscending() ? FValuePixel.ReverseValueComparator.INSTANCE : FValuePixel.ValueComparator.INSTANCE;
630
631                return getBestResponses(numResponses, responseMap, getXOffset(), getYOffset(), comparator);
632        }
633
634        /**
635         * Get the top-N "best" responses found by the template matcher.
636         *
637         * @param numResponses The number of responses
638         * @param responseMap The response map
639         * @param offsetX the amount to shift pixels in the x direction
640         * @param offsetY the amount to shift pixels in the y direction
641         * @param comparator the comparator for determining the "best" responses
642         * @return the best responses found
643         */
644        public static FValuePixel[] getBestResponses(int numResponses, FImage responseMap, int offsetX, int offsetY, Comparator<FValuePixel> comparator) {
645                BoundedPriorityQueue<FValuePixel> bestResponses = new BoundedPriorityQueue<FValuePixel>(numResponses, comparator);
646
647                final float[][] responseMapData = responseMap.pixels;
648
649                final int scanWidth = responseMap.width;
650                final int scanHeight = responseMap.height;
651
652                FValuePixel tmpPixel = new FValuePixel(0, 0, 0);
653                for (int y=0; y<scanHeight; y++) {
654                        for (int x=0; x<scanWidth; x++) {
655                                tmpPixel.x = x + offsetX; //account for offset to centre
656                                tmpPixel.y = y + offsetY; //account for offset to centre
657                                tmpPixel.value = responseMapData[y][x];
658
659                                FValuePixel removed = bestResponses.offerItem(tmpPixel);
660
661                                if (removed == null)
662                                        tmpPixel = new FValuePixel(0, 0, 0);
663                                else
664                                        tmpPixel = removed;
665                        }
666                }
667
668                return bestResponses.toOrderedArray(new FValuePixel[numResponses]);
669        }
670
671        /**
672         * @return The x-offset of the top-left of the response map
673         *              returned by {@link #getResponseMap()} to the original image
674         *              analysed by {@link #analyseImage(FImage)}.
675         */
676        public int getXOffset() {
677                final int halfWidth = template.width / 2;
678
679                if (this.searchBounds == null)
680                        return halfWidth;
681                else
682                        return (int) Math.max(searchBounds.x , halfWidth);
683        }
684
685        /**
686         * @return The y-offset of the top-left of the response map
687         *              returned by {@link #getResponseMap()} to the original image
688         *              analysed by {@link #analyseImage(FImage)}.
689         */
690        public int getYOffset() {
691                final int halfHeight = template.height / 2;
692
693                if (this.searchBounds == null)
694                        return halfHeight;
695                else
696                        return (int) Math.max(searchBounds.y , halfHeight);
697        }
698
699        /**
700         * @return The responseMap generated from the last call to {@link #analyseImage(FImage)}
701         */
702        public FImage getResponseMap() {
703                return responseMap;
704        }
705
706        /**
707         * @return the template held by the matcher; this might be different
708         * to the image used in construction as it might have been pre-processed.
709         */
710        public FImage getTemplate() {
711                return template;
712        }
713
714        /**
715         * Testing
716         * @param args
717         * @throws IOException
718         */
719        public static void main(String[] args) throws IOException {
720                FImage image = ImageUtilities.readF(new File("/Users/jsh2/Desktop/image.png"));
721                FImage template = image.extractROI(100, 100, 100, 100);
722                image.fill(0f);
723                image.drawImage(template, 100, 100);
724
725                TemplateMatcher matcher = new TemplateMatcher(template, Mode.CORRELATION);
726                matcher.setSearchBounds(new Rectangle(100,100,200,200));
727                image.analyseWith(matcher);
728                DisplayUtilities.display(matcher.responseMap.normalise());
729
730                MBFImage cimg = image.toRGB();
731                for (FValuePixel p : matcher.getBestResponses(10)) {
732                        System.out.println(p);
733                        cimg.drawPoint(p, RGBColour.RED, 1);
734                }
735
736                cimg.drawShape(matcher.getSearchBounds(), RGBColour.BLUE);
737                cimg.drawShape(new Rectangle(100,100,100,100), RGBColour.GREEN);
738
739                DisplayUtilities.display(cimg);
740        }
741}