/* * Copyright (c) 2011-2013, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package boofcv.gui.feature; import boofcv.abst.feature.associate.ScoreAssociation; import georegression.struct.point.Point2D_F64; import java.awt.*; import java.awt.event.MouseListener; import java.util.List; /** * Displays relative association scores for different features. When a feature is clicked on in * an image the best fit scores are show in the other image. * * @author Peter Abeles */ public class AssociationScorePanel<D> extends CompareTwoImagePanel implements MouseListener { // adjusts how close to the optimal answer a point needs to be before it is plotted double containmentFraction; // how big circles are drawn in association window int maxCircleRadius = 15; // left and right window information List<D> leftDesc,rightDesc; double associationScore[]; // computes association score ScoreAssociation<D> scorer; // statistical information on score distribution int indexBest; double worst; double best; public AssociationScorePanel( double containmentFraction ) { super(20,false); if( containmentFraction <= 0 ) throw new IllegalArgumentException("containmentFraction must be more than zero"); this.containmentFraction = containmentFraction; } public void setScorer(ScoreAssociation<D> scorer) { this.scorer = scorer; } public void setLocation(List<Point2D_F64> leftPts , List<Point2D_F64> rightPts , List<D> leftDesc, List<D> rightDesc ) { setLocation(leftPts,rightPts); this.leftDesc = leftDesc; this.rightDesc = rightDesc; } protected void computeScore( boolean isTargetLeft , int targetIndex ) { int N = Math.max(leftPts.size(),rightPts.size()); if( associationScore == null || associationScore.length < N ) { associationScore = new double[ N ]; } if( isTargetLeft ) { D t = leftDesc.get(targetIndex); for( int i = 0; i < rightDesc.size(); i++ ) { D d = rightDesc.get(i); associationScore[i] = scorer.score(t,d); } } else { D t = rightDesc.get(targetIndex); for( int i = 0; i < leftDesc.size(); i++ ) { D d = leftDesc.get(i); associationScore[i] = scorer.score(t,d); } } } @Override protected void drawFeatures(Graphics2D g2, double scaleLeft, int leftX, int leftY, double scaleRight, int rightX, int rightY) { if( leftPts == null || rightPts == null ) { System.out.println("is null"); return; } // draw all the found features in both images since nothing has been selected yet if( selected.isEmpty() ) { drawPoints(g2,leftPts,leftX,leftY,scaleLeft); drawPoints(g2,rightPts,rightX,rightY,scaleRight); return; } else if( selected.size() != 1 ) { System.err.println("Selected more than one feature!"); return; } int selectedIndex = selected.get(0); // compute association score computeScore(selectedIsLeft,selectedIndex); // a feature has been selected. In the image it was selected draw an X if( selectedIsLeft ) { drawCrossHair(g2,leftPts.get(selectedIndex),leftX,leftY,scaleLeft); } else { drawCrossHair(g2,rightPts.get(selectedIndex),rightX,rightY,scaleRight); } // draw circles of based on how similar a feature is to the selected one if( selectedIsLeft ) { drawDistribution(g2,rightPts,rightX,rightY,scaleRight); } else { drawDistribution(g2,leftPts,leftX,leftY,scaleLeft); } } /** * Visualizes score distribution. Larger circles mean its closer to the best * fit score. */ private void drawDistribution( Graphics2D g2 , List<Point2D_F64> candidates , int offX, int offY , double scale) { findStatistics(); // draw all the features, adjusting their size based on the first score g2.setColor(Color.RED); g2.setStroke(new BasicStroke(3)); double normalizer; if( scorer.getScoreType().isZeroBest() ) normalizer = best*containmentFraction; else normalizer = Math.abs(best)*(Math.exp(-1.0/containmentFraction)); for( int i = 0; i < candidates.size(); i++ ) { Point2D_F64 p = candidates.get(i); double s = associationScore[i]; // scale the circle based on how bad it is double ratio = 1-Math.abs(s-best)/normalizer; if( ratio < 0 ) continue; int r = maxCircleRadius - (int)(maxCircleRadius*ratio); if( r > 0 ) { int x = (int)(p.x*scale+offX); int y = (int)(p.y*scale+offY); g2.drawOval(x-r,y-r,r*2+1,r*2+1); } } // draw the best feature g2.setColor(Color.GREEN); g2.setStroke(new BasicStroke(10)); int w = maxCircleRadius*2+1; Point2D_F64 p = candidates.get(indexBest); int x = (int)(p.x*scale+offX); int y = (int)(p.y*scale+offY); g2.drawOval(x-maxCircleRadius,y-maxCircleRadius,w,w); } private void drawPoints( Graphics2D g2 , List<Point2D_F64> points , int startX , int startY , double scale ) { for( Point2D_F64 p : points ) { int x1 = (int)(scale*p.x)+startX; int y1 = (int)(scale*p.y)+startY; VisualizeFeatures.drawPoint(g2,x1,y1,Color.BLUE); } } private void drawCrossHair( Graphics2D g2 , Point2D_F64 target , int startX , int startY , double scale) { int x = startX + (int)(target.x*scale); int y = startY + (int)(target.y*scale); int r = 10; g2.setColor(Color.BLACK); g2.setStroke(new BasicStroke(11)); g2.drawLine(x-r,y,x+r,y); g2.drawLine(x,y-r,x,y+r); g2.setColor(Color.RED); g2.setStroke(new BasicStroke(5)); g2.drawLine(x-r,y,x+r,y); g2.drawLine(x,y-r,x,y+r); } @Override protected boolean isValidPoint(int index) { return true; } private void findStatistics( ) { final int N = selectedIsLeft ? rightPts.size() : leftPts.size(); indexBest = -1; worst = -Double.MAX_VALUE; best = Double.MAX_VALUE; for( int i = 0; i < N; i++ ) { double s = associationScore[i]; if( s > worst ) worst = s; if( s < best ) { best = s; indexBest = i; } } } }