#!/usr/bin/perl

##
##  created by Daniel Schwarz/daniel.schwarz@topoi.org
##  released under Creative Commons/CC-BY
##  Attribution
##

use File::Basename;
use File::Copy;
use File::Spec;
use Data::Dumper;
use Time::localtime;
use Switch;
use POSIX qw(strftime);
use JSON;

## the defs

chomp($CURRENT_DIR  = `pwd`);
chomp($BIN_PATH_REL = `dirname $0`);
chomp($OS           = `uname -o`);
chomp($CORES        = `ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w`);

if(!File::Spec->file_name_is_absolute($BIN_PATH_REL)){
	$BIN_PATH_ABS = File::Spec->rel2abs($BIN_PATH_REL);
} else {
	$BIN_PATH_ABS = File::Spec->rel2abs($BIN_PATH_REL);
}

sub getCcdWidths{
    local $/;
    my $ccdPath = "$BIN_PATH_ABS/ccd_defs.json";
    open( my $fh, '<', $ccdPath);

    my $ccdWidths = decode_json(<$fh>);
    close($fh);
    undef $/;
    return $ccdWidths;
}

$ccdWidths = getCcdWidths();

$BIN_PATH = $BIN_PATH_ABS."/bin";

my %objectStats    = {
    count           => 0,
    good            => 0,
    bad             => 0,
    minWidth        => 0,
    minHeight       => 0,
    maxWidth        => 0,
    maxHeight       => 0

};

my %jobOptions    = {
    resizeTo        => 0,
    srcDir          => $CURRENT_DIR
};

my %args = {};

$jobOptions{srcDir} = "$CURRENT_DIR";

sub run {
    system($_[0]);
    
    if($? != 0){
        die "\n\nquitting cause: \n\t$_[0]\nreturned with code ".$?."\n";
    }
}
sub now {
	system("echo `date`");
}

sub parseArgs {

 ## defaults
    $args{"--match-size"}            = "200";

    $args{"--resize-to"}             = "2400";
    
    $args{"--start-with"}            = "resize";
    $args{"--end-with"}              = "odm_orthophoto";
    
    $args{"--cmvs-maxImages"}        = 500;
	
    $args{"--matcher-ratio"}         = 0.6;
    $args{"--matcher-threshold"}     = 2.0;
    
    $args{"--pmvs-level"}            = 1;
    $args{"--pmvs-csize"}            = 2;
    $args{"--pmvs-threshold"}        = 0.7;
    $args{"--pmvs-wsize"}            = 7;
    $args{"--pmvs-minImageNum"}      = 3;

    $args{"--odm_meshing-maxVertexCount"}   = 100000;
    $args{"--odm_meshing-octreeDepth"}      = 9;
    $args{"--odm_meshing-samplesPerNode"}   = 1;
    $args{"--odm_meshing-solverDivide"}     = 9;

    $args{"--odm_texturing-textureResolution"}   = 4096;
    $args{"--odm_texturing-textureWithSize"}     = 3600;

    $args{"--odm_georeferencing-gcpFile"}	= "gcp_list.txt";
    $args{"--odm_georeferencing-useGcp"}	= "true";

    $args{"--zip-results"}           = "true";
    
    for($i = 0; $i <= $#ARGV; $i++) {
        if($ARGV[$i] =~ /^--[^a-z\-]*/){
            $args{"$ARGV[$i]"} = true;
            
            if(!($ARGV[$i+1] =~ /^--[^a-z\-]*/)) {
                if($ARGV[$i] eq "--resize-to"){
                    if($ARGV[$i+1] eq "orig" || $ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--start-with"){
                    if($ARGV[$i+1] eq "resize" || $ARGV[$i+1] eq "getKeypoints" || $ARGV[$i+1] eq "match" || $ARGV[$i+1] eq "bundler" || $ARGV[$i+1] eq "cmvs" || $ARGV[$i+1] eq "pmvs" 
			|| $ARGV[$i+1] eq "odm_meshing" || $ARGV[$i+1] eq "odm_texturing" || $ARGV[$i+1] eq "odm_georeferencing" || $ARGV[$i+1] eq "odm_orthophoto"){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {    
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1]."\n\t valid values are \"resize\", \"getKeypoints\", \"match\", \"bundler\", \"cmvs\", \"pmvs\",
			\"odm_meshing\", \"odm_texturing\", \"odm_georeferencing\", \"odm_orthophoto\"";    
                    }
                }
                if($ARGV[$i] eq "--end-with"){
                    if($ARGV[$i+1] eq "resize" || $ARGV[$i+1] eq "getKeypoints" || $ARGV[$i+1] eq "match" || $ARGV[$i+1] eq "bundler" || $ARGV[$i+1] eq "cmvs" || $ARGV[$i+1] eq "pmvs" 
			|| $ARGV[$i+1] eq "odm_meshing" || $ARGV[$i+1] eq "odm_texturing" || $ARGV[$i+1] eq "odm_georeferencing" || $ARGV[$i+1] eq "odm_orthophoto"){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {    
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1]."\n\t valid values are \"resize\", \"getKeypoints\", \"match\", \"bundler\", \"cmvs\", \"pmvs\",
			\"odm_meshing\", \"odm_texturing\", \"odm_georeferencing\", \"odm_orthophoto\"";
                    }
                }
                if($ARGV[$i] eq "--run-only"){
                    if($ARGV[$i+1] eq "resize" || $ARGV[$i+1] eq "getKeypoints" || $ARGV[$i+1] eq "match" || $ARGV[$i+1] eq "bundler" || $ARGV[$i+1] eq "cmvs" || $ARGV[$i+1] eq "pmvs" 
			|| $ARGV[$i+1] eq "odm_meshing" || $ARGV[$i+1] eq "odm_texturing" || $ARGV[$i+1] eq "odm_georeferencing" || $ARGV[$i+1] eq "odm_orthophoto"){
                        $args{"--start-with"} = $ARGV[$i+1];
                        $args{"--end-with"} = $ARGV[$i+1];
                    } else {    
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1]."\n\t valid values are \"resize\", \"getKeypoints\", \"match\", \"bundler\", \"cmvs\", \"pmvs\",
			\"odm_meshing\", \"odm_texturing\", \"odm_georeferencing\", \"odm_orthophoto\"";
                    }
                }
	            if($ARGV[$i] eq "--matcher-threshold"){
                    if($ARGV[$i+1] =~ /^-?[0-9]*\.?[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
	            if($ARGV[$i] eq "--matcher-ratio"){
                    if($ARGV[$i+1] =~ /^-?[0-9]*\.?[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--cmvs-maxImages"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--pmvs-level"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--pmvs-csize"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--pmvs-threshold"){
                    if($ARGV[$i+1] =~ /^-?[0-9]*\.?[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--pmvs-wsize"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--pmvs-minImageNum"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                
                if($ARGV[$i] eq "--force-focal"){
                    if($ARGV[$i+1] =~ /^[0-9]*\.?[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--force-ccd"){
                    if($ARGV[$i+1] =~ /^[0-9]*\.?[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--odm_meshing-maxVertexCount"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--odm_meshing-octreeDepth"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--odm_meshing-samplesPerNode"){
                    if($ARGV[$i+1] =~ /^[0-9]*\.?[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--odm_meshing-solverDivide"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
                if($ARGV[$i] eq "--odm_texturing-textureResolution"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
		if($ARGV[$i] eq "--odm_texturing-textureWithSize"){
                    if($ARGV[$i+1] =~ /^[0-9]*$/){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
		if($ARGV[$i] eq "--odm_georeferencing-gcpFile"){
                    $args{$ARGV[$i]} = $ARGV[$i+1];
                }
		if($ARGV[$i] eq "--odm_georeferencing-useGcp"){
                    if($ARGV[$i+1] eq "true" || $ARGV[$i+1] eq "false"){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
		if($ARGV[$i] eq "--zip-results"){
                    if($ARGV[$i+1] eq "true" || $ARGV[$i+1] eq "false"){
                        $args{$ARGV[$i]} = $ARGV[$i+1];
                    } else {
                        die "\n invalid parameter for \"".$ARGV[$i]."\": ".$ARGV[$i+1];
                    }
                }
            }
        }
    }
   

    if($args{"--help"}){        
        print "\nusage: run.pl [options]";
        print "\nit should be run from the folder contining the images to which should reconstructed";
        print "\n";
        print "\noptions:";
        print "\n              --help: ";
        print "\n                      prints this screen";
        print "\n  ";
                   
        print "\n         --resize-to: <positive integer|\"orig\">";
        print "\n             default: 2400";
        print "\n                      will resize the images so that the maximum width/height of the images are smaller or equal to the specified number";
        print "\n                      if \"--resize-to orig\" is used it will use the images without resizing";
        print "\n  ";
                   
        print "\n        --start-with: <\"resize\"|\"getKeypoints\"|\"match\"|\"bundler\"|\"cmvs\"|\"pmvs\">";
        print "\n             default: resize";
        print "\n                      will start the sript at the specified step";
        print "\n  ";
                   
        print "\n          --end-with: <\"resize\"|\"getKeypoints\"|\"match\"|\"bundler\"|\"cmvs\"|\"pmvs\">";
        print "\n             default: pmvs";
        print "\n                      will stop the sript after the specified step";
        print "\n  ";
                   
        print "\n          --run-only: <\"resize\"|\"getKeypoints\"|\"match\"|\"bundler\"|\"cmvs\"|\"pmvs\">";
        print "\n                      will only execute the specified step";
        print "\n                      equal to --start-with <step> --end-with <step>";
        print "\n  ";
                   
        print "\n       --force-focal: <positive float>";
        print "\n                      override the focal length information for the images";
        print "\n  ";
                   
        print "\n         --force-ccd: <positive float>";
        print "\n                      override the ccd width information for the images";
        print "\n  ";
                   
        print "\n --matcher-threshold: <float> (percent)";
        print "\n             default: 2.0";
        print "\n                      ignore matched keypoints if the two images share less then <float> percent of keypoints";
        print "\n  ";
                   
        print "\n     --matcher-ratio: <float";
        print "\n             default: 0.6";
        print "\n                      ratio of the distance to the next best matched keypoint";
        print "\n  ";
		
		
                   
        print "\n    --cmvs-maxImages: <positive integer>";
        print "\n             default: 100";
        print "\n                      the maximum number of images per cluster";
        print "\n  ";
                   
        print "\n        --pmvs-level: <positive integer>";
        print "\n             default: 1";
                   
        print "\n        --pmvs-csize: <positive integer>";
        print "\n             default: 2";
                   
        print "\n    --pmvs-threshold: <float: -1.0 <= x <= 1.0>";
        print "\n             default: 0.7";
                   
        print "\n        --pmvs-wsize: <positive integer>";
        print "\n             default: 7";
                   
        print "\n  --pmvs-minImageNum: <positive integer>";
        print "\n             default: 3";
        print "\n                      see http://grail.cs.washington.edu/software/pmvs/documentation.html for an explanation of these parameters";
        print "\n";
        
        print "\n     --odm_meshing-maxVertexCount: <positive integer>";
        print "\n                       default: 100000";
        print "\n                                The maximum vertex count of the output mesh.";
                   
        print "\n     --odm_meshing-octreeDepth: <positive integer>";
        print "\n                       default: 9";
        print "\n                                Octree depth used in the mesh reconstruction, increase to get more vertices, recommended values are 8-12";
                   
        print "\n  --odm_meshing-samplesPerNode: <float: 1.0 <= x>";
        print "\n                       default: 1";
        print "\n                                Number of points per octree node, recommended value: 1.0";

        print "\n  --zip-results: <true|false>";
        print "\n        default: true";
        print "\n                 Set to false if you do not want to have gunzipped tarball of the results.";

        exit;
    }
    
    print "\n  - configuration:";
    
    foreach $args_key (sort keys %args) {
        if($args{$args_key} ne ""){
            print "\n    $args_key: $args{$args_key}";
        }
    }
    
    print "\n";
    print "\n";
}

sub prepareObjects {
 ## get the source list    
    @source_files = `ls -1 | egrep "\.[jJ]{1}[pP]{1}[eE]{0,1}[gG]{1}"`;
    
    print "\n  - source files - "; now(); print "\n";

    foreach $file (@source_files) {
        chomp($file);
        
        chomp($file_make        = `jhead \"$file\" | grep "Camera make"`);
        chomp($file_model       = `jhead \"$file\" | grep "Camera model"`);
        chomp($file_focal       = `jhead \"$file\" | grep "Focal length"`);
        chomp($file_ccd         = `jhead \"$file\" | grep "CCD width"`);
        chomp($file_resolution  = `jhead \"$file\" | grep "Resolution"`);
    
        my %fileObject = {};
    
        chomp(($fileObject{src})        = $file);
        chomp(($fileObject{base})       = $file);
        $fileObject{base}               =~ s/\.[^\.]*$//;
    
        chomp(($fileObject{make})       = $file_make =~ /: ([^\n\r]*)/);
        chomp(($fileObject{model})      = $file_model =~ /: ([^\n\r]*)/);
        
        $fileObject{make} =~ s/^\s+//;		$fileObject{make} =~  s/\s+$//;
        $fileObject{model} =~ s/^\s+//;		$fileObject{model} =~  s/\s+$//;
    
        $fileObject{id}                 = $fileObject{make}." ".$fileObject{model};
    
        ($fileObject{width}, $fileObject{height})    = $file_resolution =~ /: ([0-9]*) x ([0-9]*)/;
            
        if(!$args{"--force-focal"}){
            ($fileObject{focal})      = $file_focal =~ /:[\ ]*([0-9\.]*)mm/;
        } else {
            $fileObject{focal}        = $args{"--force-focal"};
        }
        
        if(!$args{"--force-ccd"}){
            ($fileObject{ccd})        = $file_ccd =~ /:[\ ]*([0-9\.]*)mm/;
            
            if(!$fileObject{ccd}){;
                $fileObject{ccd}      = $ccdWidths->{$fileObject{id}};
            }
        } else {
            $fileObject{ccd}          = $args{"--force-ccd"};
        }
    
        if($fileObject{ccd} && $fileObject{focal} && $fileObject{width} && $fileObject{height}){
            if($fileObject{width} > $fileObject{height}){
                $fileObject{focalpx} = $fileObject{width} * ($fileObject{focal} / $fileObject{ccd});
            } else {
                $fileObject{focalpx} = $fileObject{height} * ($fileObject{focal} / $fileObject{ccd});
            }
            
            $fileObject{isOk} = true;
            $objectStats{good}++;
            
            print "\n     using $fileObject{src}     dimensions: $fileObject{width}x$fileObject{height} / focal: $fileObject{focal}mm / ccd: $fileObject{ccd}mm";
        } else {
            $fileObject{isOk} = false;
            $objectStats{bad}++;
            
            print "\n    no CCD width or focal length found for $fileObject{src} - camera: \"$fileObject{id}\"";
        }
    
        $objectStats{count}++;
    
        if($objectStats{minWidth} == 0)  { $objectStats{minWidth}  = $fileObject{width}; }
        if($objectStats{minHeight} == 0) { $objectStats{minHeight} = $fileObject{height}; }
    
        $objectStats{minWidth}  = $objectStats{minWidth}  < $fileObject{width}  ? $objectStats{minWidth}  : $fileObject{width};
        $objectStats{minHeight} = $objectStats{minHeight} < $fileObject{height} ? $objectStats{minHeight} : $fileObject{height};
        $objectStats{maxWidth}  = $objectStats{maxWidth}  > $fileObject{width}  ? $objectStats{maxWidth}  : $fileObject{width};
        $objectStats{maxHeight} = $objectStats{maxHeight} > $fileObject{height} ? $objectStats{maxHeight} : $fileObject{height};
            
        push(@objects, \%fileObject);
    }
    
    
    
    if(!$objectStats{good}){
        print "\n\n    found no usable images - quitting\n";
        die;
    } else {
        print "\n\n    found $objectStats{good} usable images";
    }
    
    print "\n";
    
    $jobOptions{resizeTo} = $args{"--resize-to"};

    print "\n  using max image size of $jobOptions{resizeTo} x $jobOptions{resizeTo}";    
    
    $jobOptions{jobDir} = "$jobOptions{srcDir}/reconstruction-with-image-size-$jobOptions{resizeTo}";
    
    $jobOptions{step_1_convert}         = "$jobOptions{jobDir}/_convert.templist.txt";
    $jobOptions{step_1_vlsift}          = "$jobOptions{jobDir}/_vlsift.templist.txt";
    $jobOptions{step_1_gzip}            = "$jobOptions{jobDir}/_gzip.templist.txt";
    
    $jobOptions{step_2_filelist}        = "$jobOptions{jobDir}/_filelist.templist.txt";
    $jobOptions{step_2_macthes_jobs}    = "$jobOptions{jobDir}/_matches_jobs.templist.txt";
    $jobOptions{step_2_matches_dir}  	= "$jobOptions{jobDir}/matches";
    $jobOptions{step_2_matches}         = "$jobOptions{jobDir}/matches.init.txt";
    
    $jobOptions{step_3_filelist}        = "$jobOptions{jobDir}/list.txt";
    $jobOptions{step_3_bundlerOptions}  = "$jobOptions{jobDir}/options.txt";
    
    mkdir($jobOptions{jobDir});
        
    foreach $fileObject (@objects) {
        if($fileObject->{isOk}){
            $fileObject->{step_0_resizedImage}  = "$jobOptions{jobDir}/$fileObject->{base}.jpg";
            
            $fileObject->{step_1_pgmFile}       = "$jobOptions{jobDir}/$fileObject->{base}.pgm";
            $fileObject->{step_1_keyFile}       = "$jobOptions{jobDir}/$fileObject->{base}.key";
            $fileObject->{step_1_gzFile}        = "$jobOptions{jobDir}/$fileObject->{base}.key.gz";
        }
    }
    
#    exit
}

sub resize {
    print "\n";
    print "\n  - preparing images - "; now(); print "\n";
    print "\n";

    chdir($jobOptions{jobDir});
    
    foreach $fileObject (@objects) {
        if($fileObject->{isOk}){
			unless (-e "$fileObject->{step_0_resizedImage}"){
          	  if($jobOptions{resizeTo} != "orig" && (($fileObject->{width} > $jobOptions{resizeTo}) || ($fileObject->{height} > $jobOptions{resizeTo}))){
	                print "\n    resizing $fileObject->{src} \tto $fileObject->{step_0_resizedImage}";
                
	                run("convert -resize $jobOptions{resizeTo}x$jobOptions{resizeTo} -quality 100 \"$jobOptions{srcDir}/$fileObject->{src}\" \"$fileObject->{step_0_resizedImage}\"");

	            } else {
	                print "\n     copying $fileObject->{src} \tto $fileObject->{step_0_resizedImage}";
                
	                copy("$CURRENT_DIR/$fileObject->{src}", "$fileObject->{step_0_resizedImage}");
	            }
			} else {	
	          	print "\n     using existing $fileObject->{src} \tto $fileObject->{step_0_resizedImage}";
			}
            
            chomp($file_resolution    = `jhead \"$fileObject->{step_0_resizedImage}\" | grep "Resolution"`);
            ($fileObject->{width}, $fileObject->{height})    = $file_resolution =~ /: ([0-9]*) x ([0-9]*)/;
            print "\t ($fileObject->{width} x $fileObject->{height})";
        }
    }
    
    if($args{"--end-with"} ne "resize"){
        getKeypoints();
    }
}

sub getKeypoints {
    print "\n";
    print "\n  - finding keypoints - "; now(); print "\n";
    print "\n\n";
    
    chdir($jobOptions{jobDir});
    
    $vlsiftJobs    = "";
    
	$c = 0;

    foreach $fileObject (@objects) {
		$c = $c+1;
	
        if($fileObject->{isOk}){
            if($args{"--lowe-sift"}){
                $vlsiftJobs    .= "echo -n \"$c/$objectStats{good} - \" && convert -format pgm \"$fileObject->{step_0_resizedImage}\" \"$fileObject->{step_1_pgmFile}\"";
                $vlsiftJobs    .= " && \"$BIN_PATH/sift\" < \"$fileObject->{step_1_pgmFile}\" > \"$fileObject->{step_1_keyFile}\"";
                $vlsiftJobs    .= " && gzip -f \"$fileObject->{step_1_keyFile}\"";
                $vlsiftJobs    .= " && rm -f \"$fileObject->{step_1_pgmFile}\"";
                $vlsiftJobs    .= " && rm -f \"$fileObject->{step_1_keyFile}.sift\"\n";
            } else {
				unless (-e "$jobOptions{jobDir}/$fileObject->{base}.key.bin") {
	                $vlsiftJobs    .= "echo -n \"$c/$objectStats{good} - \" && convert -format pgm \"$fileObject->{step_0_resizedImage}\" \"$fileObject->{step_1_pgmFile}\"";
	                $vlsiftJobs    .= " && \"$BIN_PATH/vlsift\" \"$fileObject->{step_1_pgmFile}\" -o \"$fileObject->{step_1_keyFile}.sift\" > /dev/null && perl \"$BIN_PATH/../convert_vlsift_to_lowesift.pl\" \"$jobOptions{jobDir}/$fileObject->{base}\"";
	                $vlsiftJobs    .= " && gzip -f \"$fileObject->{step_1_keyFile}\"";
	                $vlsiftJobs    .= " && rm -f \"$fileObject->{step_1_pgmFile}\"";
	                $vlsiftJobs    .= " && rm -f \"$fileObject->{step_1_keyFile}.sift\"\n";
				} else {
					print "using existing $jobOptions{jobDir}/$fileObject->{base}.key.bin\n";
				}
            }
        }
    }
    
    open (SIFT_DEST, ">$jobOptions{step_1_vlsift}");
    print SIFT_DEST $vlsiftJobs;
    close(SIFT_DEST);
    
    run("\"$BIN_PATH/parallel\" --halt-on-error 1 -j+0 < \"$jobOptions{step_1_vlsift}\"");
    
    if($args{"--end-with"} ne "getKeypoints"){
        match();
    }
}

sub match {
    print "\n";
    print "\n  - matching keypoints - "; now(); print "\n";
    print "\n";

    chdir($jobOptions{jobDir});
    mkdir($jobOptions{step_2_matches_dir});
    
    $matchesJobs = "";

	my $c = 0;
	my $t = ($objectStats{good}-1) * ($objectStats{good}/2);
	
	for (my $i = 0; $i < $objectStats{good}; $i++) {
		for (my $j = $i+1; $j < $objectStats{good}; $j++) {
			$c++;
			unless (-e "$jobOptions{step_2_matches_dir}/$i-$j.txt"){ 
				
			   
				$matchesJobs        .=  "echo -n \".\" && touch \"$jobOptions{step_2_matches_dir}/$i-$j.txt\" && \"$BIN_PATH/KeyMatch\" \"@objects[$i]->{step_1_keyFile}\" \"@objects[$j]->{step_1_keyFile}\" \"$jobOptions{step_2_matches_dir}/$i-$j.txt\" $args{'--matcher-ratio'} $args{'--matcher-threshold'}\n";
			}
		}
	}
	
    open (MATCH_DEST, ">$jobOptions{step_2_macthes_jobs}");
    print MATCH_DEST $matchesJobs;
    close(MATCH_DEST);
	
    run("\"$BIN_PATH/parallel\" --halt-on-error 1 -j+0 < \"$jobOptions{step_2_macthes_jobs}\"");
	
	run("rm -f \"$jobOptions{step_2_matches}\"");
	
	for (my $i = 0; $i < $objectStats{good}; $i++) {
		for (my $j = $i+1; $j < $objectStats{good}; $j++) {
			$c++;
			
			if (-e "$jobOptions{step_2_matches_dir}/$i-$j.txt" && (-s "$jobOptions{step_2_matches_dir}/$i-$j.txt") > 0) {
				run("echo \"$i $j\" >> \"$jobOptions{step_2_matches}\" && cat \"$jobOptions{step_2_matches_dir}/$i-$j.txt\" >> \"$jobOptions{step_2_matches}\"");
			}
		}
	}
	
    foreach $fileObject (@objects) {
        if($fileObject->{isOk}){
            if($fileObject->{isOk}){
                $filesList        .= "$fileObject->{step_1_keyFile}\n";
            }
        }
    }
	
    open (MATCH_DEST, ">$jobOptions{step_2_filelist}");
    print MATCH_DEST $filesList;
    close(MATCH_DEST);
    
 #   run("\"$BIN_PATH/KeyMatchFull\" \"$jobOptions{step_2_filelist}\" \"$jobOptions{step_2_matches}\"    ");
    
    if($args{"--end-with"} ne "match"){
        bundler();
    }
}

sub bundler {
    print "\n";
    print "\n  - running bundler - "; now(); print "\n";
    print "\n";
	
    chdir($jobOptions{jobDir});
    
    mkdir($jobOptions{jobDir}."/bundle");
    mkdir($jobOptions{jobDir}."/pmvs");
    mkdir($jobOptions{jobDir}."/pmvs/txt");
    mkdir($jobOptions{jobDir}."/pmvs/visualize");
    mkdir($jobOptions{jobDir}."/pmvs/models");
    
    $filesList = "";
    
    foreach $fileObject (@objects) {
        if($fileObject->{isOk}){
            if($fileObject->{isOk}){
                $filesList        .= sprintf("\./%s.jpg 0 %0.5f\n", $fileObject->{base}, $fileObject->{focalpx});
            }
        }
    }
    
    chomp($filesList);
    
    $bundlerOptions  = "--match_table matches.init.txt\n";
    $bundlerOptions .= "--output bundle.out\n";
    $bundlerOptions .= "--output_all bundle_\n";
    $bundlerOptions .= "--output_dir bundle\n";
    $bundlerOptions .= "--variable_focal_length\n";
    $bundlerOptions .= "--use_focal_estimate\n";
    $bundlerOptions .= "--constrain_focal\n";
    $bundlerOptions .= "--constrain_focal_weight 0.0\n";
    $bundlerOptions .= "--estimate_distortion\n";
    $bundlerOptions .= "--use_ceres\n";
    $bundlerOptions .= "--run_bundle";
        
    system("echo \"$bundlerOptions\" > \"$jobOptions{step_3_bundlerOptions}\"");

    open (BUNDLER_DEST, ">$jobOptions{step_3_filelist}");
    print BUNDLER_DEST $filesList;
    close(BUNDLER_DEST);
    
    run("\"$BIN_PATH/bundler\" \"$jobOptions{step_3_filelist}\" --options_file \"$jobOptions{step_3_bundlerOptions}\" > bundle/out");
    run("\"$BIN_PATH/Bundle2PMVS\" \"$jobOptions{step_3_filelist}\" bundle/bundle.out");
    run("\"$BIN_PATH/RadialUndistort\" \"$jobOptions{step_3_filelist}\" bundle/bundle.out pmvs");
    
    $i = 0;
    
    foreach $fileObject (@objects) {
        if($fileObject->{isOk}){
            if($fileObject->{isOk}){
                if (-e "pmvs/$fileObject->{base}.rd.jpg"){
                    $nr = sprintf("%08d", $i++);
                
                    system("mv pmvs/$fileObject->{base}.rd.jpg pmvs/visualize/$nr.jpg");
                    system("mv pmvs/$nr.txt pmvs/txt/$nr.txt");
                }
            }
        }
    }
    
    system("\"$BIN_PATH/Bundle2Vis\" pmvs/bundle.rd.out pmvs/vis.dat");
    
    if($args{"--end-with"} ne "bundler"){
        cmvs();
    }
}

sub cmvs {
    print "\n";
    print "\n  - running cmvs - "; now(); print "\n";
    print "\n";

    chdir($jobOptions{jobDir});
    
    run("\"$BIN_PATH/cmvs\" pmvs/ $args{'--cmvs-maxImages'} $CORES");
    run("\"$BIN_PATH/genOption\" pmvs/ $args{'--pmvs-level'} $args{'--pmvs-csize'} $args{'--pmvs-threshold'} $args{'--pmvs-wsize'} $args{'--pmvs-minImageNum'} $CORES");
    
    if($args{"--end-with"} ne "cmvs"){
        pmvs();
    }
}

sub pmvs {
    print "\n";
    print "\n  - running pmvs - "; now(); print "\n";
    print "\n";

    chdir($jobOptions{jobDir});
    
    run("\"$BIN_PATH/pmvs2\" pmvs/ option-0000");
    
    system("cp -Rf \"$jobOptions{jobDir}/pmvs/models\" \"$jobOptions{jobDir}-results\"");
    
    if($args{"--end-with"} ne "pmvs"){
        odm_meshing();
    }
}

sub odm_meshing {
    print "\n";
    print "\n  - running meshing - "; now(); print "\n";
    print "\n";
    
    chdir($jobOptions{jobDir});
    mkdir($jobOptions{jobDir}."/odm_meshing");    


    run("\"$BIN_PATH/odm_meshing\" -inputFile $jobOptions{jobDir}-results/option-0000.ply -outputFile $jobOptions{jobDir}-results/odm_mesh-0000.ply -logFile $jobOptions{jobDir}/odm_meshing/odm_meshing_log.txt -maxVertexCount $args{'--odm_meshing-maxVertexCount'} -octreeDepth $args{'--odm_meshing-octreeDepth'} -samplesPerNode $args{'--odm_meshing-samplesPerNode'}" );
    
    if($args{"--end-with"} ne "odm_meshing"){
        odm_texturing();
    }
}

sub odm_texturing {
    print "\n";
    print "\n  - running texturing - "; now(); print "\n";
    print "\n";
    
    chdir($jobOptions{jobDir});
    mkdir($jobOptions{jobDir}."/odm_texturing");   
    mkdir("$jobOptions{jobDir}-results/odm_texturing"); 

	
    run("\"$BIN_PATH/odm_texturing\" -bundleFile $jobOptions{jobDir}/pmvs/bundle.rd.out -imagesPath $jobOptions{srcDir}/ -imagesListPath $jobOptions{jobDir}/pmvs/list.rd.txt -inputModelPath $jobOptions{jobDir}-results/odm_mesh-0000.ply -outputFolder $jobOptions{jobDir}-results/odm_texturing/ -textureResolution $args{'--odm_texturing-textureResolution'} -bundleResizedTo $jobOptions{resizeTo} -textureWithSize $args{'--odm_texturing-textureWithSize'} -logFile $jobOptions{jobDir}/odm_texturing/odm_texturing_log.txt" );
    
    if($args{"--end-with"} ne "odm_texturing"){
        odm_georeferencing();
    }

}

sub odm_georeferencing {
    print "\n";
    print "\n  - running georeferencing - "; now(); print "\n";
    print "\n";
    
    chdir($jobOptions{jobDir});
    mkdir($jobOptions{jobDir}."/odm_georeferencing");

	if($args{"--odm_georeferencing-useGcp"} ne "true") {
		run("\"$BIN_PATH/odm_extract_utm\" -imagesPath $jobOptions{srcDir}/ -imageListFile $jobOptions{jobDir}/pmvs/list.rd.txt -outputCoordFile $jobOptions{jobDir}/odm_georeferencing/coordFile.txt");  
		run("\"$BIN_PATH/odm_georef\" -bundleFile $jobOptions{jobDir}/pmvs/bundle.rd.out -coordFile $jobOptions{jobDir}/odm_georeferencing/coordFile.txt -inputFile $jobOptions{jobDir}-results/odm_texturing/odm_textured_model.obj -outputFile $jobOptions{jobDir}-results/odm_texturing/odm_textured_model_geo.obj -logFile $jobOptions{jobDir}/odm_georeferencing/odm_georeferencing_log.txt");
	} elsif (-e "$jobOptions{srcDir}/$args{'--odm_georeferencing-gcpFile'}") {
		run("\"$BIN_PATH/odm_georef\" -bundleFile $jobOptions{jobDir}/pmvs/bundle.rd.out -gcpFile $jobOptions{srcDir}/$args{'--odm_georeferencing-gcpFile'} -imagesPath $jobOptions{srcDir}/ -imagesListPath $jobOptions{jobDir}/pmvs/list.rd.txt -bundleResizedTo $jobOptions{resizeTo} -inputFile $jobOptions{jobDir}-results/odm_texturing/odm_textured_model.obj -outputFile $jobOptions{jobDir}-results/odm_texturing/odm_textured_model_geo.obj -logFile $jobOptions{jobDir}/odm_georeferencing/odm_georeferencing_log.txt");
	} else {
		print "Warning: No GCP file. Consider rerunning with argument --odm_georeferencing-useGcp false --start-with odm_georeferencing\n";
		print "Skipping orthophoto\n";
		$args{"--end-with"} = "odm_georeferencing";
	}
    
    if($args{"--end-with"} ne "odm_georeferencing"){
        odm_orthophoto();
    }

}


sub odm_orthophoto {
    print "\n";
    print "\n  - running orthophoto generation - "; now(); print "\n";
    print "\n";
    
    chdir($jobOptions{jobDir});
    mkdir($jobOptions{jobDir}."/odm_orthophoto");


    run("\"$BIN_PATH/odm_orthophoto\" -inputFile $jobOptions{jobDir}-results/odm_texturing/odm_textured_model_geo.obj -logFile $jobOptions{jobDir}/odm_orthophoto/odm_orthophoto_log.txt -outputFile $jobOptions{jobDir}-results/odm_orthphoto.png -resolution 20.0");

}

parseArgs();
prepareObjects();
    
chdir($jobOptions{jobDir});
    
switch ($args{"--start-with"}) {
    case "resize"              { resize();              }
    case "getKeypoints"        { getKeypoints();        }    
    case "match"               { match();               }
    case "bundler"             { bundler();             }
    case "cmvs"                { cmvs();                }
    case "pmvs"                { pmvs();                }
    case "odm_meshing"         { odm_meshing();         }
    case "odm_texturing"       { odm_texturing();       }
    case "odm_georeferencing"  { odm_georeferencing();  }
    case "odm_orthophoto"      { odm_orthophoto();      }
}

if($args{"--zip-results"} eq "true") { 
    print "\nCompressing results - "; now(); print "\n";
    print "\n";
    run("cd $jobOptions{jobDir}-results/ && tar -czf $jobOptions{jobDir}-results.tar.gz *");
}

print "\n";
print "\n  - done - "; now(); print "\n";
print "\n";
