
/* autopano-sift, Automatic panorama image creation
 * Copyright (C) 2004 -- Sebastian Nowozin
 *
 * This program is free software released under the GNU General Public
 * License, which is included in this software package (doc/LICENSE).
 */

/* LoweDetector.cs
 *
 * Lowe scale-invariant keypoint feature detector (SIFT) interface.
 *
 * (C) Copyright 2004 -- Sebastian Nowozin (nowozin@cs.tu-berlin.de)
 *
 * Implementation of the SIFT algorithm as specified in this research paper by
 * David Lowe: http://www.cs.ubc.ca/~lowe/papers/ijcv03-abs.html
 *
 * "The University of British Columbia has applied for a patent on the SIFT
 * algorithm in the United States. Commercial applications of this software
 * may require a license from the University of British Columbia."
 * For more information, see the LICENSE file supplied with the distribution.
 */

using System;
using System.Collections;


public class LoweFeatureDetector
{
	OctavePyramid pyr;
	public OctavePyramid Pyr {
		get {
			return (pyr);
		}
	}

	// Detection parameters, suggested by Lowe's research paper.
	// Initial parameters
	double octaveSigma = 1.6;

	// Sigma for gaussian filter applied to double-scaled input image.
	double preprocSigma = 1.5;

	// Once one of the downscaled image's dimension falls below this,
	// downscaling is stopped.
	int minimumRequiredPixelsize = 32;

	// How many DoG levels for each octave.
	int scaleSpaceLevels = 3;

	bool printWarning = true;
	// Disable the patent warning.  Used so far only in autopano's subcalls to
	// the SIFT algorithm in the refining step.
	public bool PrintWarning {
		set {
			printWarning = value;
		}
	}

	bool verbose = true;
	public bool Verbose {
		set {
			verbose = value;
		}
	}

	// *** Peak related parameters
	// Tweak here to reduce/increase keypoint density

	// Minimum absolute DoG value of a pixel to be allowed as minimum/maximum
	// peak. This control how much general non-differing areas, such as the
	// sky is filtered. Higher value = less peaks, lower value = more peaks.
	// Good values from 0.005 to 0.01. Note this is related to
	// 'dValueLowThresh', which should be a bit larger, factor 1.0 to 1.5.
	double dogThresh = 0.0075;

	// D-value filter highcap value, higher = less keypoints, lower = more.
	// Lower: only keep keypoints with good localization properties, i.e.
	//    those that are precisely and easily to localize (high contrast, see
	//    Lowe, page 11. He recommends 0.03, but this seems way too high to
	//    me.)
	double dValueLowThresh = 0.008;

	// Required cornerness ratio level, higher = more keypoints, lower = less.
	double maximumEdgeRatio = 20.0;

	// The exact sub-pixel localization is done on just one DoG plane. Even
	// when the scale adjustment exceeds +/- 0.5, the plane is not changed.
	// With this value you can discard peaks that are localized to be too far
	// from the plane. A high value will allow for peaks to be used that are
	// more far away from the plane used for localization, while a low value
	// will sort out more peaks, that drifted too far away.
	//
	// Be very careful with this value, as a too large value will lead to a
	// high number of keypoints in hard to localize areas such as in photos of
	// the sky.
	//
	// Good values seem to lie between 0.30 and 0.6.
	double scaleAdjustThresh = 0.50;

	// Number of maximum steps a single keypoint can make in its space.
	int relocationMaximum = 4;

	// Results
	ArrayList globalKeypoints;
	public ArrayList GlobalKeypoints {
		get {
			return (globalKeypoints);
		}
	}

	// The Integer-normalized version of the globalKeypoints.
	ArrayList globalNaturalKeypoints = null;
	public ArrayList GlobalNaturalKeypoints {
		get {
			if (globalNaturalKeypoints != null)
				return (globalNaturalKeypoints);

			if (globalKeypoints == null)
				throw (new ArgumentException ("No keypoints generated yet."));

			globalNaturalKeypoints = new ArrayList ();
			foreach (Keypoint kp in globalKeypoints)
				globalNaturalKeypoints.Add (new KeypointN (kp));

			return (globalNaturalKeypoints);
		}
	}

	public LoweFeatureDetector ()
	{
	}

	// Return the number of detected features.
	public int DetectFeatures (ImageMap img)
	{
		return (DetectFeaturesDownscaled (img, -1, 1.0));
	}

	// Scale down the images down so that both dimensions are smaller than
	// 'bothDimHi'. If 'bothDimHi' is < 0, the image is doubled before
	// processing, if it is zero, nothing is done to the image.
	public int DetectFeaturesDownscaled (ImageMap img, int bothDimHi,
		double startScale)
	{
		globalKeypoints = globalNaturalKeypoints = null;

		if (printWarning) {
			// Print license restriction
			Console.Error.WriteLine ("");
			Console.Error.WriteLine ("===============================================================================");
			Console.Error.WriteLine ("The use of this software is restricted by certain conditions.");
			Console.Error.WriteLine ("See the \"LICENSE\" file distributed with the program for details.");
			Console.Error.WriteLine ("");
			Console.Error.WriteLine ("The University of British Columbia has applied for a patent on the SIFT");
			Console.Error.WriteLine ("algorithm in the United States.  Commercial applications of this software may");
			Console.Error.WriteLine ("require a license from the University of British Columbia.");
			Console.Error.WriteLine ("===============================================================================");
			Console.Error.WriteLine ("");
		}

		// Double the image size, as this way more features are detected. The
		// scale is reduced to 0.5.
		if (bothDimHi < 0) {
			img = img.ScaleDouble ();
			startScale *= 0.5;
		} else if (bothDimHi > 0) {
			while (img.XDim > bothDimHi || img.YDim > bothDimHi) {
				img = img.ScaleHalf ();
				startScale *= 2.0;
			}
		}

		// XXX: Maybe the blurring has to be before double-sizing?
		// better not, if we would lose more information then?

		// (Lowe03, p10, "We assume that the original image has a blur of at
		// least \sigma = 0.5 ...")
		// So, do one initial image smoothing pass.
		if (preprocSigma > 0.0) {
			GaussianConvolution gaussianPre =
				new GaussianConvolution (preprocSigma);
			img = gaussianPre.Convolve (img);
		}

		pyr = new OctavePyramid ();
		pyr.Verbose = verbose;
		pyr.BuildOctaves (img, startScale, scaleSpaceLevels,
			octaveSigma, minimumRequiredPixelsize);

		globalKeypoints = new ArrayList ();

		// Generate keypoints from each scalespace.
		for (int on = 0 ; on < pyr.Count ; ++on) {
			DScaleSpace dsp = pyr[on];

			ArrayList peaks = dsp.FindPeaks (dogThresh);
			if (verbose)
				Console.WriteLine ("Octave {0} has {1} raw peaks",
					on, peaks.Count);

			int oldCount = peaks.Count;
			ArrayList peaksFilt = dsp.FilterAndLocalizePeaks (peaks,
				maximumEdgeRatio, dValueLowThresh, scaleAdjustThresh,
				relocationMaximum);

			if (verbose) {
				Console.WriteLine ("  filtered: {0} remaining from {1}, thats % {2:N2}",
					peaksFilt.Count, oldCount, (100.0 * peaksFilt.Count) / oldCount);

				Console.WriteLine ("generating keypoints from peaks");
			}

			// Generate the actual keypoint descriptors, using pre-computed
			// values for the gradient magnitude and direction.
			dsp.GenerateMagnitudeAndDirectionMaps ();
			ArrayList keypoints = dsp.GenerateKeypoints (peaksFilt,
				scaleSpaceLevels, octaveSigma);
			dsp.ClearMagnitudeAndDirectionMaps ();

			globalKeypoints.AddRange (keypoints);
		}

		return (globalKeypoints.Count);
	}
}


