/*
 * Copyright (c) 2012-2013, Peter Abeles. All Rights Reserved.
 *
 * This file is part of DDogleg (http://ddogleg.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 org.ddogleg.fitting.modelset.distance;

import org.ddogleg.fitting.modelset.*;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;


/**
 * 

* Outliers are removed by first fitting a model to all the data points. Points which * deviate the most are removed and the best fit parameters are recomputed. This is done * until some error metric changes very little or the maximum number of iterations has been * exceeded. Works well when the inlier set is much greater than the noise. *

* *

* Anyone have a good reference paper for this approach? *

* * @author Peter Abeles */ public class StatisticalDistanceModelMatcher implements ModelMatcher { // maximum number of times it will perform the pruning process private int maxIterations; // if the error changes by less than this amount it stops iterating private double minChange; // current best fit parameters protected Model param; protected Model currParam; // what computes the error metrics private StatisticalFit errorAlg; // error in previous iteration protected double oldCenter; // the error for the current fit parameters protected double centerError; // if the center error is less than this value it stops iterating immediately private double exitCenterError; // if the error is more than this amount it failed private double failError; // the minimum number of points that can be left over before it is considered a failure private int minFitPoints; // computes a set of model parameters from a list of points private ModelFitter modelFitter; // computes the difference between the model and a point private DistanceFromModel modelError; // converts the model into an array parameter format private ModelCodec codec; // list containing points that are to be pruned LinkedList> pruneList = new LinkedList>(); // set of points which fit the model private List inliers = new ArrayList(); // list of indexes converting it from match set to input list private int []matchToInput = new int[1]; /** * Creates a new model matcher. The type of statistics it uses is specified by "statistics". * 0 for mean and standard deviation and 1 for median and percentile. If statistics=0 is set then the threshold * correspond to the number of standard deviations a point can be away from the mean before it is * pruned. If statistics=1 then points which have a percentile error more than that value are pruned. * * @param maxIterations The maximum number of iterations it will perform. * @param minChange It will stop iterating of the change in error is less than this amount. * @param exitCenterError If the error is less than this value it will stop iterating. * @param failError If the final error is more than this amount it failed. * @param minFitPoints If fewer than this number of points remain, then it failed. * @param statistics 0 = mean statistics and 1 = percentile statistics * @param pruneThreshold Points which exceed this statistic are pruned. See {@link StatisticalDistance} for details. * @param modelFitter Fits a model to a set of points * @param modelError Computes the error between a point and the model. */ public StatisticalDistanceModelMatcher(int maxIterations, double minChange, double exitCenterError, double failError, int minFitPoints, StatisticalDistance statistics, double pruneThreshold, ModelManager modelManager , ModelFitter modelFitter, DistanceFromModel modelError, ModelCodec codec ) { this.maxIterations = maxIterations; this.minChange = minChange; this.exitCenterError = exitCenterError; this.failError = failError; this.minFitPoints = minFitPoints; this.modelFitter = modelFitter; this.modelError = modelError; this.codec = codec; param = modelManager.createModelInstance(); currParam = modelManager.createModelInstance(); switch (statistics) { case MEAN: errorAlg = new FitByMeanStatistics(pruneThreshold); break; case PERCENTILE: errorAlg = new FitByMedianStatistics(pruneThreshold); break; default: throw new IllegalArgumentException("Unknown statistics selected"); } } @Override public boolean process(List dataSet ) { // there must be at least the minFitPoints for it to run if (dataSet.size() < minFitPoints) return false; if( dataSet.size() > matchToInput.length ) matchToInput = new int[ dataSet.size() ]; pruneList.clear(); inliers.clear(); for( int i = 0; i < dataSet.size(); i++ ) pruneList.add( new PointIndex(dataSet.get(i),i)); inliers.clear(); errorAlg.init(modelError, pruneList); oldCenter = Double.MAX_VALUE; boolean converged = false; // iterate until it converges or the maximum number of iterations has been exceeded int i = 0; for (; i < maxIterations && !converged && pruneList.size() >= minFitPoints; i++) { inliers.clear(); for( PointIndex p : pruneList) { inliers.add(p.data); } if (!modelFitter.fitModel(inliers, null, currParam)) { // failed to fit the model, so stop before it screws things up break; } modelError.setModel(currParam); errorAlg.computeStatistics(); centerError = errorAlg.getErrorMetric(); // see if the error is so small that it no longer needs to run if (centerError < exitCenterError) converged = true; // if the model did not significantly change then stop iterating else if (computeDiff(currParam, param) <= minChange) { converged = true; } Model temp = param; param = currParam; currParam = temp; if (!converged) { errorAlg.prune(); oldCenter = centerError; } } boolean ret = centerError < failError && pruneList.size() >= minFitPoints; if( ret ) { inliers.clear(); int index = 0; for( PointIndex p : pruneList) { inliers.add(p.data); matchToInput[index++] = p.index; } } return ret; } /** * Computes the difference between the two parameters. */ protected double computeDiff(Model modelA, Model modelB ) { double paramA[] = new double[ codec.getParamLength() ]; double paramB[] = new double[ codec.getParamLength() ]; codec.encode(modelA,paramA); codec.encode(modelB,paramB); double total = 0; for (int i = 0; i < paramA.length; i++) { total += Math.abs(paramA[i] - paramB[i]); } return total / paramA.length; } @Override public Model getModelParameters() { return param; } @Override public List getMatchSet() { return inliers; } @Override public int getInputIndex(int matchIndex) { return matchToInput[matchIndex]; } @Override public double getFitQuality() { return centerError; } @Override public int getMinimumSize() { return minFitPoints; } }

Ads help maintain this website.