Diff of /AutoPano.c [000000] .. [26cac0] Maximize Restore

  Switch to side-by-side view

--- a
+++ b/AutoPano.c
@@ -0,0 +1,707 @@
+
+/* 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).
+ */
+
+/* Autopano.cs
+ *
+ * Keypoint file correlation and hugin panorama file creation utility.
+ *
+ * (C) Copyright 2004 -- Sebastian Nowozin (nowozin@cs.tu-berlin.de)
+ *
+ * "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.
+ */
+
+#include "AutoPanoSift.h"
+
+void Usage ()
+{
+	WriteLine ("usage: autopano.exe [options] output.pto keys1.xml keys2.xml [..]\n");
+	WriteLine ("Options");
+	WriteLine ("  --ransac <on|off|1|0>   Switch RANSAC filtration on or off (default: on)");
+	WriteLine ("  --maxmatches <matches>  Use no more than the given number of matches");
+	WriteLine ("                          (default: 16, use zero for unlimited)");
+						       
+	WriteLine ("  --disable-areafilter    Do not use max-area filtration, which is default.");
+	WriteLine ("                          See manpage for details.");
+	WriteLine ("  --integer-coordinates   Truncate match coordinates to integer numbers.");
+	WriteLine ("  --absolute-pathnames <on|off|1|0>   Use the absolute pathname of the image");
+	WriteLine ("                          file in the PTO output file. Disabled by default.");
+	WriteLine ("");
+	
+	WriteLine ("Alignment options");
+	WriteLine ("  --align                 Automatically pre-align images in PTO file.");
+	WriteLine ("  --bottom-is-left");
+	WriteLine ("  --bottom-is-right       Use in case the automatic algorithm fails.");
+	WriteLine ("  --generate-horizon <c>  Generate up to 'c' horizon lines.");
+	WriteLine ("");
+	
+	WriteLine ("Refinement options");
+	WriteLine ("  --refine                Refine the found control points using the");
+	WriteLine ("                          original images.");
+	WriteLine ("  --refine-by-middle      Use the best middle point to refine (default).");
+	WriteLine ("  --refine-by-mean        Use the mean of the patches control points.");
+	WriteLine ("  --keep-unrefinable <on|off|1|0>");
+	WriteLine ("                          Keep unrefinable matches (default: on).");
+										
+	WriteLine ("output.pto: The output PTO panorama project file.");
+	WriteLine ("    The filename can be \"-\", then stdout is used");
+	WriteLine ("key<n>.xml: The keypoint input files.");
+	WriteLine ("    The input files can be gzip compressed, but require the \".gz\" extension\n    then.");
+	WriteLine ("");
+	WriteLine ("Notice: for the aligning to work, the input images shall be");
+	WriteLine ("  1. All of the same dimension and scale");
+	WriteLine ("  2. The first images must be an ordered row. See manpage.");
+	WriteLine ("");
+}
+
+typedef struct Resolution Resolution;
+struct Resolution
+{
+	int x, y;
+};
+
+Resolution* Resolution_new0()
+{
+	Resolution* self = (Resolution*)malloc(sizeof(Resolution));
+	return self;
+}
+
+Resolution* Resolution_new(int x, int y) 
+{
+	Resolution* self = Resolution_new0();
+	self->x = x;
+	self->y = y;
+	return self;
+}
+
+void Resolution_delete(Resolution* self) 
+{
+	if (self) {
+		free(self);
+	}
+}
+
+int Resolution_CompareTo (Resolution* self, int x, int y)
+{
+	
+	if (self->x == x && self->y == y)
+		return (0);
+	
+	return (1);
+}
+
+
+
+
+// The maximum radius to consider around a keypoint that is refined.  That
+// is, at most a patch of a maximum size of twice this value in both
+// horizontal and vertical direction is extracted.
+int RefinementRadiusMaximum = 96;
+int (*refineHandler)(int index, int total);
+
+
+void RefineKeypoints (ArrayList* msList,
+		      bool selectMiddlePoint, bool neverLosePoints);
+DisplayImage* ExtractPatch (DisplayImage* large,
+			    int px, int py, double scale, int* radius);
+ArrayList* ExtractKeypoints (DisplayImage* pic);
+bool YesNoOption (char* optionName, char* val);
+
+// selectMiddlePoint: if true, select the middle point in the patch,
+//   otherwise build the mean
+// neverLosePoints: if true, and if we cannot do the refinement, still use
+//   the control point.
+void RefineKeypoints (ArrayList* msList,
+		bool selectMiddlePoint, bool neverLosePoints)
+{
+	DisplayImage* pic1 = NULL;
+	DisplayImage* pic2 = NULL;
+	char* pic1Name = NULL;
+	char* pic2Name = NULL;
+
+	/* Keep stats for the refineHandler delegate
+	 */
+	int totalRefines = 0;
+	int doneRefines = 0;
+	int i;
+	for(i=0; i<ArrayList_Count(msList); i++) {
+		MatchSet* ms = ArrayList_GetItem(msList, i);
+		int j;
+		for(j=0; j<ArrayList_Count(ms->matches); j++) {
+			ArrayList_GetItem(ms->matches, j);
+			totalRefines += 1;
+		}
+	}
+
+ 
+	for(i=0; i<ArrayList_Count(msList); i++) {
+		MatchSet* ms = ArrayList_GetItem(msList, i);
+		WriteLine ("  between \"%s\" and \"%s\"",
+			   ms->file1, ms->file2);
+		
+		if (pic1Name != ms->file1) {
+			pic1Name = ms->file1;
+			pic1 = DisplayImage_new (ms->file1);
+		}
+		if (pic2Name != ms->file2) {
+			pic2Name = ms->file2;
+			pic2 = DisplayImage_new (ms->file2);
+		}
+		/*WriteLine ("pair: %s, %s, %d keypoint matches",
+		  ms->file1, ms->file2, ArrayList_Count(ms->Matches));*/
+		
+		ArrayList* refinedMatches = ArrayList_new0 (NULL);
+
+		int j;
+		for(j=0; j<ArrayList_Count(ms->matches); j++) {
+			Match* m = ArrayList_GetItem(ms->matches, j);
+
+			int p1x = (int) (m->kp1->x + 0.5);
+			int p1y = (int) (m->kp1->y + 0.5);
+			int p1radius;
+			DisplayImage* patch1 = ExtractPatch (pic1, p1x, p1y,
+							    m->kp1->scale, &p1radius);
+
+			int p2x = (int) (m->kp2->x + 0.5);
+			int p2y = (int) (m->kp2->y + 0.5);
+			int p2radius;
+			DisplayImage* patch2 = ExtractPatch (pic2, p2x, p2y,
+							     m->kp2->scale, &p2radius);
+
+			/* Call the refine handler delegate in case there is one to
+			 * inform the callee of a single refining step (for progress
+			 * bar displays and such).
+			 */
+			doneRefines += 1;
+			if (refineHandler != NULL)
+				refineHandler (doneRefines, totalRefines);
+
+			// Skip over keypoint matches we cannot refine as part of the
+			// image lies outside.
+			if (patch1 == NULL || patch2 == NULL) {
+				if (neverLosePoints)
+					ArrayList_AddItem(refinedMatches, m);
+                                DisplayImage_delete(patch1);
+                                DisplayImage_delete(patch2);
+				continue;
+			}
+
+			// Otherwise, run the SIFT algorithm on both small patches.
+			ArrayList* p1kp = ExtractKeypoints (patch1);
+			ArrayList* p2kp = ExtractKeypoints (patch2);
+			/*WriteLine ("p1kp = %d, p2kp = %d", ArrayList_Count(p1kp),
+			  ArrayList_Count(p2kp));*/
+			
+			// Apply the matching, RANSAC enabled.
+			MultiMatch* mm = MultiMatch_new0 ();
+			mm->verbose = false;
+
+			ArrayList* matches = NULL;
+			matches = MultiMatch_TwoPatchMatch (mm, p1kp,
+							    patch1->width, patch1->height, p2kp, patch2->width,
+							    patch2->height, true);
+                        DisplayImage_delete(patch1);
+                        DisplayImage_delete(patch2);
+
+			/* In case there are less than three keypoints in the
+			 * two patches, we ignore them all.
+			 */
+			if (0 /*was exception ???*/ ) { 
+				matches = NULL;
+			}
+
+			if (matches == NULL || ArrayList_Count(matches) != 1) {
+				if (neverLosePoints)
+					ArrayList_AddItem(refinedMatches, m);
+
+				continue;
+			}
+
+			MatchSet* pSet = ArrayList_GetItem(matches, 0);
+
+			// Now get the real new control point coordinates from the
+			// patches.  We have two options and assume all points are
+			// equal quality-wise:
+			//   a) Select the one that is most in the middle
+			//      (selectMiddlePoint == true)
+			//   b) Build the mean of all the control point matches in the
+			//      patches (selectMiddlePoint == false).
+			double kp1X = 0.0;
+			double kp1Y = 0.0;
+			double kp2X = 0.0;
+			double kp2Y = 0.0;
+			double kpMidDist = Double_PositiveInfinity;
+
+			int k;
+			for(k=0; k<ArrayList_Count(pSet->matches); k++) {
+				Match* pM = ArrayList_GetItem(pSet->matches, k);
+				if (selectMiddlePoint) {
+					double dist = sqrt (
+						pow (pM->kp1->x - p1radius, 2.0) +
+						pow (pM->kp1->y - p1radius, 2.0));
+					
+					if (dist < kpMidDist) {
+						kpMidDist = dist;
+						
+						kp1X = pM->kp1->x;
+						kp1Y = pM->kp1->y;
+						
+						kp2X = pM->kp2->x;
+						kp2Y = pM->kp2->y;
+					}
+				} else {
+					kp1X += pM->kp1->x;
+					kp1Y += pM->kp1->y;
+					
+					kp2X += pM->kp2->x;
+					kp2Y += pM->kp2->y;
+				}
+
+				/*WriteLine ("(%g, %g) matches (%g, %g)",
+				  pM->kp1->x, pM->kp1->y, pM->kp2->x, pM->kp2->y);*/
+			}
+
+			if (selectMiddlePoint == false) {
+				kp1X /= (double) ArrayList_Count(pSet->matches);
+				kp1Y /= (double) ArrayList_Count(pSet->matches);
+				kp2X /= (double) ArrayList_Count(pSet->matches);
+				kp2Y /= (double) ArrayList_Count(pSet->matches);
+			}
+
+			kp1X += p1x - p1radius;
+			kp1Y += p1y - p1radius;
+
+			kp2X += p2x - p2radius;
+			kp2Y += p2y - p2radius;
+
+			Match* mn = Match_clone (m);
+
+			// Adjust the original keypoints location to be the mean of
+			// all the highly precise superresolution points.
+			mn->kp1->x = kp1X;
+			mn->kp1->y = kp1Y;
+
+			mn->kp2->x = kp2X;
+			mn->kp2->y = kp2Y;
+
+			/*WriteLine ("MASTER POINT MATCH: (%g,%g) to (%g,%g)",
+			  kp1X, kp1Y, kp2X, kp2Y);*/
+
+			ArrayList_AddItem(refinedMatches, mn);
+			/*
+			  DisplayImage_Save (patch1, "patch-1.jpg");
+			  DisplayImage_Save (patch2, "patch-2.jpg");
+			  exit (0);
+			*/
+		}
+
+		ms->matches = refinedMatches;
+	}
+}
+
+/** Extract a small image patch from a larger image, centered at the given
+ * coordinates.
+ */
+DisplayImage* ExtractPatch (DisplayImage* large,
+			    int px, int py, double scale, int* radius)
+{
+	*radius = (int) (9.0 * scale + 0.5);
+	if (*radius > RefinementRadiusMaximum)
+		*radius = RefinementRadiusMaximum;
+
+	/*WriteLine ("patch centered at (%d,%d), scale %g, radius = %d",
+	  px, py, scale, *radius);*/
+
+	int pxe = px + *radius;
+	int pye = py + *radius;
+	px -= *radius;
+	py -= *radius;
+
+	if (px < 0 || py < 0 || pxe >= large->width || pye >= large->height) {
+		/*WriteLine ("   (%d,%d)-(%d,%d) out of (0,0)-(%d,%d)",
+		  px, py, pxe, pye, large->width, large->height);*/
+
+		return (NULL);
+	} else {
+		//WriteLine ("   extracting patch");
+	}
+	DisplayImage* patch = DisplayImage_Carve (large, px, py, *radius*2, *radius*2);
+
+	return (patch);
+}
+
+/** Produce keypoints for a small image patch.
+ */
+ArrayList* ExtractKeypoints (DisplayImage* pic)
+{
+	ImageMap* picMap = DisplayImage_ConvertToImageMap (pic);
+	
+	LoweFeatureDetector* lf = LoweFeatureDetector_new0 ();
+	LoweFeatureDetector_SetPrintWarning(false);
+	LoweFeatureDetector_SetVerbose(false);
+	LoweFeatureDetector_DetectFeatures (lf, picMap);
+	
+        ArrayList* res = LoweFeatureDetector_GlobalNaturalKeypoints(lf);
+        lf->globalNaturalKeypoints = NULL; // Make sure res won't get deleted.
+        LoweFeatureDetector_delete(lf);
+        return res;
+}
+
+bool YesNoOption (char* optionName, char* val)
+{
+    if (strcmp (val, "1") == 0 || strcmp (val, "on") == 0)
+	return (true);
+    else if (strcmp (val, "0") == 0 || strcmp (val, "off") == 0)
+	return (false);
+    
+    FatalError ("'%s' is no valid truth value for option '%s'. Please see manpage for help.",
+		val, optionName);
+    return false;
+}
+
+void WritePTOFile (FILE* pto, MultiMatch* mm,
+		   ArrayList* msList, BondBall* bb, int generateHorizon, bool integerCoordinates,
+		   bool useAbsolutePathnames)
+{
+	fprintf(pto, "# Hugin project file\n");
+	fprintf(pto, "# automatically generated by autopano-sift, available at\n");
+	fprintf(pto, "# http://cs.tu-berlin.de/~nowozin/autopano-sift/\n\n");
+	fprintf(pto, "p f2 w3000 h1500 v360  n\"JPEG q90\"\n");
+	fprintf(pto, "m g1 i0\n\n");
+	
+	int imageIndex = 0;
+	HashTable* imageNameTab = HashTable_new0 (NULL, NULL);
+	ArrayList* resolutions = ArrayList_new0 (Resolution_delete);
+	int i;
+	for(i=0; i<ArrayList_Count(mm->keySets); i++) {
+		KeypointXMLList* kx = ArrayList_GetItem(mm->keySets, i);
+		HashTable_AddItem(imageNameTab, kx->imageFile, (void*)imageIndex);
+		ArrayList_AddItem(resolutions, Resolution_new (kx->xDim, kx->yDim));
+		
+		char*  imageFile = kx->imageFile;
+		
+		// If the resolution was already there, use the first image with
+		// the exact same resolution as reference for camera-related
+		// values.
+		
+		int refIdx;
+		for (refIdx = 0 ; refIdx < (ArrayList_Count(resolutions) - 1) ; ++refIdx) {
+			if (Resolution_CompareTo(ArrayList_GetItem(resolutions, refIdx), kx->xDim, kx->yDim) == 0)
+				break;
+		}
+		if (refIdx == (ArrayList_Count(resolutions) - 1))
+			refIdx = -1;
+		
+		Position* pos = bb == NULL ? NULL :
+			HashTable_GetItem(bb->positions, imageFile);
+		/*
+		  if (pos != NULL) {
+		  WriteLine ("yaw %g, pitch %g, rotation %g",
+		  pos->yaw, pos->pitch, pos->rotation);
+		  }*/
+
+		double yaw = 0.0, pitch = 0.0, rotation = 0.0;
+		if (pos != NULL) {
+			yaw = pos->yaw;
+			pitch = pos->pitch;
+			rotation = pos->rotation;
+		}
+		
+		if (imageIndex == 0 || refIdx == -1) {
+			fprintf(pto, "i w%d h%d f0 a0 b-0.01 c0 d0 e0 p%g r%g v180 y%g  u10 n\"%s\"\n",
+				kx->xDim, kx->yDim, pitch, rotation, yaw, imageFile);
+		} else {
+			fprintf(pto, "i w%d h%d f0 a=%d b=%d c=%d d0 e0 p%g r%g v=%d y%g  u10 n\"%s\"\n",
+				kx->xDim, kx->yDim, refIdx, refIdx, refIdx, pitch, rotation, refIdx, yaw, imageFile);
+		}
+		imageIndex += 1;
+	}
+	
+	fprintf(pto, "\nv p1 r1 y1\n\n");
+	
+	fprintf(pto, "# match list automatically generated\n");
+	int j;
+	for(j=0; j<ArrayList_Count(msList); j++) {
+		MatchSet* ms = ArrayList_GetItem(msList, j);
+		int k;
+		for(k=0; k<ArrayList_Count(ms->matches); k++) {
+			Match* m = ArrayList_GetItem(ms->matches, k);
+			if (integerCoordinates == false) {
+				fprintf(pto, "c n%d N%d x%.6f y%.6f X%.6f Y%.6f t0\n",
+					(int)HashTable_GetItem(imageNameTab, ms->file1), (int)HashTable_GetItem(imageNameTab, ms->file2),
+					m->kp1->x, m->kp1->y, m->kp2->x, m->kp2->y);
+			} else {
+				fprintf(pto, "c n%d N%d x%d y%d X%d Y%d t0\n",
+					(int)HashTable_GetItem(imageNameTab, ms->file1), (int)HashTable_GetItem(imageNameTab, ms->file2),
+					(int) (m->kp1->x + 0.5), (int) (m->kp1->y + 0.5),
+					(int) (m->kp2->x + 0.5), (int) (m->kp2->y + 0.5));
+			}
+		}
+	}
+	
+	// Generate horizon if we should
+	if (bb != NULL && generateHorizon > 0) {
+		WriteLine ("Creating horizon...");
+		
+		int kMain = 2;
+		int hPoints = generateHorizon;
+		int horizonPointsMade = 0;
+
+		bool hasGood = true;
+		while (hPoints > 0 && hasGood) {
+			hasGood = false;
+			int kStep = 2 * kMain;
+
+			int p;
+			for (p = 0 ; hPoints > 0 && p < kMain ; ++p) {
+				double stepSize = ((double) ArrayList_Count(bb->firstRow)) / ((double) kStep);
+				double beginIndex = p * stepSize;
+				double endIndex = (((double) ArrayList_Count(bb->firstRow)) / (double) kMain) +
+					p * stepSize;
+
+// Round to next integer and check if their image distance
+// is larger than 1. If its not, we skip over this useless
+// horizon point.
+				int bi = (int) (beginIndex + 0.5);
+				int ei = (int) (endIndex + 0.5);
+				if ((ei - bi) <= 1)
+					continue;
+
+				hasGood = true;
+
+				bi %= ArrayList_Count(bb->firstRow);
+				ei %= ArrayList_Count(bb->firstRow);
+				fprintf(pto, "c n%s N%s x%d y%d X%d Y%d t2\n",
+					(char*)HashTable_GetItem(imageNameTab, ArrayList_GetItem(bb->firstRow, bi)),
+					(char*)HashTable_GetItem(imageNameTab, ArrayList_GetItem(bb->firstRow, ei)),
+					((Resolution*) ArrayList_GetItem(resolutions,bi))->x / 2,
+					((Resolution*) ArrayList_GetItem(resolutions,bi))->y / 2,
+					((Resolution*) ArrayList_GetItem(resolutions,ei))->x / 2,
+					((Resolution*) ArrayList_GetItem(resolutions,ei))->y / 2);
+
+				horizonPointsMade += 1;
+				hPoints -= 1;
+			}
+
+// Increase density for next generation lines
+			kMain *= 2;
+		}
+		WriteLine ("  made %d horizon lines.\n", horizonPointsMade);
+	}
+
+	fprintf(pto, "\n# :-)\n\n");
+	
+	WriteLine ("\nYou can now load the output file into hugin.");
+	WriteLine ("Notice: You absolutely must adjust the field-of-view value for the images");
+
+        ArrayList_delete(resolutions);
+        HashTable_delete(imageNameTab);
+}
+
+
+int main (int argc, char* argv[])
+{
+	WriteLine ("autopano-sift, Automatic panorama generation program\n");
+  
+	if (argc+1 < 3) {
+		Usage ();
+		exit (1);
+	}
+
+	// Automatic pre-aligning of images
+	bool preAlign = false;
+	int bottomDefault = -1;
+	int generateHorizon = 0;
+  
+	// Use RANSAC algorithm match filtration.
+	bool useRansac = true;
+  
+	// Use area based weighting for final match selection.
+	bool useAreaFiltration = true;
+  
+	// Truncate match coordinates to integer numbers.
+	bool useIntegerCoordinates = false;
+  
+	// Use the absolute pathname of the image files in the output PTO
+	// file.
+	bool useAbsolutePathnames = false;
+  
+	// Use "keep-best" filtration, keep the maxMatches best.
+	int maxMatches = 16;	// default: 16
+  
+	// Refinement options
+	bool refine = false;
+	bool refineMiddle = true;
+	bool keepUnrefinable = true;
+  
+	int optionCount = 0;
+	int optionN = 1;
+	while (optionN < argc &&
+	       strlen(argv[optionN]) >= 2 && argv[optionN][0] == '-')
+		{
+			char* optionStr = argv[optionN];
+
+			if (optionStr[1] != '-') {
+				Usage ();
+				exit (1);
+			}
+
+			if (strcmp (optionStr, "--ransac") == 0) {
+				useRansac = YesNoOption ("--ransac", argv[optionN + 1]);
+				optionN += 2;
+			} else if (strcmp (optionStr, "--maxmatches") == 0) {
+				if (sscanf(argv[optionN + 1], "%d",  &maxMatches) != 1) {
+					WriteLine ("Parameter to maxmatches option invalid. See the usage help.");
+					exit (1);
+				}
+				if (maxMatches < 0) {
+					WriteLine ("Maximum number of matches must be positive or zero (unlimited).");
+					exit (1);
+				}
+				optionN += 2;
+			} else if (strcmp (optionStr, "--disable-areafilter") == 0) {
+				useAreaFiltration = false;
+				optionN += 1;
+			} else if (strcmp (optionStr, "--integer-coordinates") == 0) {
+				useIntegerCoordinates = true;
+				optionN += 1;
+			} else if (strcmp (optionStr, "--absolute-pathnames") == 0) {
+				useAbsolutePathnames = YesNoOption ("--absolute-pathnames", argv[optionN + 1]);
+				optionN += 2;
+			} else if (strcmp (optionStr, "--align") == 0) {
+				preAlign = true;
+				optionN += 1;
+			} else if (strcmp (optionStr, "--bottom-is-left") == 0) {
+				bottomDefault = 0;
+				optionN += 1;
+			} else if (strcmp (optionStr, "--bottom-is-right") == 0) {
+				bottomDefault = 1;
+				optionN += 1;
+			} else if (strcmp (optionStr, "--generate-horizon") == 0) {
+				if (sscanf(argv[optionN + 1], "%d", &generateHorizon) != 1) {
+					WriteLine ("Parameter to generate-horizon option invalid. See the usage help.");
+					exit (1);
+				}
+				if (generateHorizon < 0) {
+					WriteLine ("The number of horizon lines to generate must be positive.");
+					exit (1);
+				}
+
+				optionN += 2;
+			} else if (strcmp (optionStr, "--refine") == 0) {
+				refine = true;
+				optionN += 1;
+			} else if (strcmp (optionStr, "--refine-by-middle") == 0) {
+				refineMiddle = true;
+				optionN += 1;
+			} else if (strcmp (optionStr, "--refine-by-mean") == 0) {
+				refineMiddle = false;
+				optionN += 1;
+			} else if (strcmp (optionStr, "--keep-unrefinable") == 0) {
+				keepUnrefinable = YesNoOption ("--keep-unrefinable", argv[optionN + 1]);
+				optionN += 2;
+			} else {
+				WriteLine ("Usage error. Run \"autopano.exe\" without arguments for help.");
+				exit (1);
+			}
+		}
+	optionCount = optionN;
+
+	if (bottomDefault != -1 && preAlign == false) {
+		WriteLine ("Please enable automatic alignment (\"--align\") before using the");
+		WriteLine ("--bottom-is-* options. Thank you. Run \"autopano.exe\" without");
+		WriteLine ("arguments for usage help.");
+
+		exit (1);
+	}
+
+	if (generateHorizon > 0 && preAlign == false) {
+		WriteLine ("Please enable automatic alignment (\"--align\") before using the");
+		WriteLine ("--generate-horizon option. Thank you. Run \"autopano.exe\" without");
+		WriteLine ("arguments for usage help.");
+
+		exit (1);
+	}
+
+	MultiMatch* mm = MultiMatch_new0 ();
+	ArrayList* keyfiles = ArrayList_new0(NULL);
+	int i;
+	for( i=0; i<argc - 1 - optionCount; i++) {
+		ArrayList_AddItem(keyfiles, argv[i+optionCount+1]);
+	}
+
+	WriteLine ("Loading keyfiles");
+	MultiMatch_LoadKeysets (mm, keyfiles);
+
+	WriteLine ("\nMatching...%s", useRansac == true ? " RANSAC enabled" : "");
+	ArrayList* msList = MultiMatch_LocateMatchSets (mm, 3, maxMatches,
+							useRansac, useAreaFiltration);
+
+	// Connected component check
+	WriteLine ("\nConnected component check...");
+	ArrayList* components = MultiMatch_ComponentCheck (mm, msList);
+	WriteLine ("Connected component identification resulted in %d component%s:",
+		   ArrayList_Count(components), ArrayList_Count(components) > 1 ? "s" : "");
+
+	int compN = 1;
+	int j;
+	for(j=0; j<ArrayList_Count(components); j++) {
+		Component* comp = ArrayList_GetItem(components, j);
+                char* compstr = Component_ToString(comp);
+                WriteLine ("component %d: %s", compN++, compstr);
+                free(compstr);
+	}
+
+	if (ArrayList_Count(components) > 1) {
+		WriteLine ("");
+		WriteLine ("Warning: There is one or more components that are not connected through control");
+		WriteLine ("         points. An optimization of the resulting PTO will not be possible");
+		WriteLine ("         without prior adding of control points between the components listed");
+		WriteLine ("         above. Please see the manual page for autopano(1) for details.");
+		WriteLine ("");
+	} else
+		WriteLine ("");
+
+	// BondBall algorithm
+	BondBall* bb = NULL;
+	if (preAlign) {
+		bb = MultiMatch_BuildBondBall (mm, msList, bottomDefault);
+
+		if (bb == NULL) {
+			WriteLine ("WARNING: Failed to build bondball as requested. No pre-aligning of images");
+			WriteLine ("         takes place.\n");
+		}
+	}
+
+	if (refine) {
+		WriteLine ("Refining keypoints");
+		RefineKeypoints (msList, refineMiddle, keepUnrefinable);
+	}
+
+	FILE* pto;
+	if (argv[optionCount] == "-") {
+		pto = stdout;
+	} else {
+		WriteLine ("Creating output file \"%s\"", argv[optionCount]);
+		pto = fopen(argv[optionCount], "w");
+	}
+
+	WritePTOFile (pto, mm, msList, bb, generateHorizon, useIntegerCoordinates,
+		      useAbsolutePathnames);
+
+	ArrayList_delete(keyfiles);
+	ArrayList_delete(components);
+	BondBall_delete(bb);
+	MultiMatch_delete(mm);
+	if (argv[optionCount] != "-")
+		fclose(pto);
+	return 0;
+}
+