/*- * #%L * Fiji distribution of ImageJ for the life sciences. * %% * Copyright (C) 2007 - 2017 Fiji developers. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-2.0.html>. * #L% */ package mpicbg.pointdescriptor.matcher; import java.util.ArrayList; import mpicbg.models.PointMatch; import mpicbg.pointdescriptor.AbstractPointDescriptor; public class SubsetMatcher implements Matcher { final int subsetSize; final int numNeighbors; final int numCombinations; final int numMatchings; final int[][] neighbors; /** * Matches n out of m neighbor points, the effort increases exponentially(!) * * For example {@link SubsetMatcher}(4,4) is identical to calling {@link SimpleMatcher}(4) * * @param subsetSize (n) - how many neighbors are matched in each try * @param numNeighbors (m) - out of how many neighbors to choose */ public SubsetMatcher( final int subsetSize, final int numNeighbors ) { this.subsetSize = subsetSize; this.numNeighbors = numNeighbors; //this.neighbors = computePDRecursive( numNeighbors - subsetSize, subsetSize, 0 ); this.neighbors = computePD( numNeighbors, subsetSize, 0 ); this.numCombinations = this.neighbors.length; this.numMatchings = this.numCombinations * this.numCombinations; } public int[][] getNeighbors() { return neighbors; } public int getSubsetSize() { return subsetSize; } public int getNumNeighbors() { return numNeighbors; } public int getNumCombinations() { return numCombinations; } public int getNumMatchings() { return numMatchings; } @Override public int getRequiredNumNeighbors() { return numNeighbors; } @Override public ArrayList<ArrayList<PointMatch>> createCandidates( final AbstractPointDescriptor<?, ?> pd1, final AbstractPointDescriptor<?, ?> pd2 ) { final ArrayList<ArrayList<PointMatch>> matchesList = new ArrayList<ArrayList<PointMatch>>(); for ( int a = 0; a < numCombinations; ++a ) for ( int b = 0; b < numCombinations; ++b ) { final ArrayList<PointMatch> matches = new ArrayList<PointMatch>( subsetSize ); for ( int i = 0; i < subsetSize; ++i ) { final PointMatch pointMatch = new PointMatch( pd1.getDescriptorPoint( neighbors[ a ][ i ] ), pd2.getDescriptorPoint( neighbors[ b ][ i ] ) ); matches.add( pointMatch ); } matchesList.add( matches ); } return matchesList; } @Override public double getNormalizationFactor( final ArrayList<PointMatch> matches, final Object fitResult ) { return 1; } private static int factorial( final int n ) { int fact = 1; for ( int i = n; i > 1; i-- ) fact *= i; return fact; } protected static int[][] computePD( final int n, final int k, final int offset ) { final int numCombinations = factorial( n ) / ( factorial( k ) * factorial( n - k ) ); final int[][] combinations = new int[ numCombinations ][ k ]; for ( int i = 0; i < k; ++i ) combinations[ 0 ][ i ] = i + offset; final int finalval = n - k + offset; for ( int ci = 1; ci < numCombinations; ++ci ) { final int[] c = combinations[ci-1]; final int[] nc = combinations[ci]; // find first index i to increase int i = k - 1; while ( c[ i ] == finalval + i ) --i; // copy combination up to i from previous one for ( int j = 0; j < i; ++j ) nc[ j ] = c[ j ]; // increase index i and count up from there int j = c[ i ]; for ( ; i < k; ++i ) nc[ i ] = ++j; } return combinations; } /** * Computes recursively how to create different PointDescriptors of the same Point when some tolerance * is allowed, e.g. there are outliers which should be identified. For 3 neighbors allowing 2 outliers * and starting with the second nearest neigbor (the nearest one is in most cases the one you use as input) * the result looks like that: * * (Each coloum will be one PointDescriptor) * * |1| |1| |1| |2| |1| |1| |2| |1| |2| |3| * |2| |2| |3| |3| |2| |3| |3| |4| |4| |4| * |3| |4| |4| |4| |5| |5| |5| |5| |5| |5| * * @param tolerance - How many tolerance is accepted [0 ... m] * @param n - initialized with the number of neighbors * @param offset - the starting position for the neighbors, usually 1 * @return an array containing the neighbors for each PointDescriptor as array */ protected static int[][] computePDRecursive( final int tolerance, final int n, final int offset ) { if ( tolerance == 0 ) { final int[][] neighbors = new int[1][n]; for (int i = 0; i < n; i++) neighbors[0][i] = i + offset; return neighbors; } else { final ArrayList<int[][]> allneighbors = new ArrayList<int[][]>(); int size = 1; // compute the subgroups for (int k = tolerance + n - 1; k > tolerance - 1; k--) { final int[][] neighbors = computePDRecursive(tolerance - 1, k - tolerance + 1, offset); allneighbors.add(neighbors); size += neighbors.length; } // fill the final array final int[][] neighbors = new int[size][n]; int pos = 0; for (final int[][] subn : allneighbors) { for (int i = 0; i < subn.length; i++) { for (int j = 0; j < subn[i].length; j++) neighbors[i + pos][j] = subn[i][j]; for (int j = subn[i].length; j < n; j++) neighbors[i + pos][j] = j + offset + tolerance; } pos += subn.length; } for (int j = 0; j < n; j++) neighbors[pos][j] = j + offset + tolerance; return neighbors; } } }