#!/usr/bin/perl use strict; use warnings; use Panotools::Script; use Getopt::Long; use Pod::Usage; use File::Temp qw/tempdir/; use File::Spec; my $path_output; my $n = 2; my $fast = 0; my $verbose = 0; my $help = 0; GetOptions ('o|output=s' => \$path_output, 'n|amount=f' => \$n, 'f|fast' => \$fast, 'v|verbose' => \$verbose, 'h|help' => \$help); pod2usage (-verbose => 2) if $help; my $tempdir = tempdir (CLEANUP => 1); my @report; my $path_pto = shift || pod2usage; die "Can't find $path_pto" unless -e $path_pto; my $p = new Panotools::Script; $p->Read ($path_pto); my $control_new = []; for my $index_a (0 .. scalar @{$p->Image} -1) { for my $index_b ($index_a +1 .. scalar @{$p->Image} -1) { # construct list of points relating to this image pair my $control_temp = []; for my $control (@{$p->Control}) { push @{$control_temp}, $control if ($control->{n} == $index_a and $control->{N} == $index_b); push @{$control_temp}, $control if ($control->{n} == $index_b and $control->{N} == $index_a); } next unless scalar @{$control_temp}; # create a temporary project using only relevant points my $p_temp = $p->Clone; $p_temp->{control} = $control_temp; unless ($fast or scalar @{$p_temp->Control} == 0) { $p_temp->{variable} = new Panotools::Script::Line::Variable; $p_temp->Variable->{$index_b} = {r => 1, p => 1, y => 1}; # FIXME, assumes 0 is an anchor image $p_temp->Variable->{0}->{v} = 1 if (scalar @{$control_temp} > 4); $p_temp->Variable->{0}->{b} = 1 if (scalar @{$control_temp} > 6); $p_temp->Variable->{0}->{c} = 1 if (scalar @{$control_temp} > 8); $p_temp->Image->[0]->Set (a => 0, b => 0, c => 0, d => 0, e => 0); $p_temp->Image->[$index_a]->Set (a => "=0", b => "=0", c => "=0", d => "=0", e => "=0") unless ($index_a == 0); $p_temp->Image->[$index_b]->Set (a => "=0", b => "=0", c => "=0", d => "=0", e => "=0"); my $pto_in_tmp = File::Spec->catfile ($tempdir, "$index_a-$index_b.in.pto"); my $pto_out_tmp = File::Spec->catfile ($tempdir, "$index_a-$index_b.out.pto"); $p_temp->Write ($pto_in_tmp); system ('autooptimiser', '-q', '-n', '-o', $pto_out_tmp, $pto_in_tmp); $p_temp->Read ($pto_out_tmp); } # prune points using local threshold my ($total, $min, $max, $average, $sigma) = $p_temp->Stats; my $threshold = $average + ($n * $sigma); my $pruned = $p_temp->Prune ($threshold); push @{$control_new}, @{$p_temp->Control}; next unless $verbose; print STDERR "Pair $index_a,$index_b: "; print STDERR scalar (@{$pruned}) ." points cleaned using threshold: $threshold\n"; } } my ($total, $min, $max, $average, $sigma) = map {sprintf ('%.3f', $_)} $p->Stats; push @report, "Before, Min: $min Max: $max Average: $average Sigma: $sigma"; $p->{control} = $control_new; my $p_temp = $p->Clone; unless ($fast) { my $pto_in_tmp = File::Spec->catfile ($tempdir, "all.in.pto"); my $pto_out_tmp = File::Spec->catfile ($tempdir, "all.out.pto"); $p_temp->Write ($pto_in_tmp); system ('autooptimiser', '-q', '-n', '-o', $pto_out_tmp, $pto_in_tmp); $p_temp->Read ($pto_out_tmp); } ($total, $min, $max, $average, $sigma) = $p_temp->Stats; my $threshold = $average + ($n * $sigma); my $pruned = $p_temp->Prune ($threshold); $p->{control} = $p_temp->{control}; $p->Write ($path_output); ($total, $min, $max, $average, $sigma) = map {sprintf ('%.3f', $_)} $p->Stats; push @report, ("After, Min: $min Max: $max Average: $average Sigma: $sigma",''); print STDERR join "\n", @report if $verbose; __END__ =head1 NAME ptoclean - prune improbable control points =head1 SYNOPSIS ptoclean [options] --output better.pto notgood.pto Options: -o | --output Filename of pruned project (can be the the same as the input) -n | --amount Distance factor for pruning (default=2) -f | --fast Don't run the optimiser for each image pair (similar to APClean) -v | --verbose Report some statistics -h | --help Outputs help documentation =head1 DESCRIPTION B takes a hugin .pto project and removes inconsistent control-points. 'Bad' points are determined by calculating the average error distance and standard deviation (Sigma), then removing any points with an error distance greater than the average by n * sigma. This pruning is performed on each pair of images in turn and then finally on the project as a whole. Additionally the autooptimiser tool is run on each pair of images separately before each calculation, so there is no need for the project to be 'nearly aligned' beforehand. NOTE: although optimisation plays a part in the pruning process, the output project is exactly the same as the input except with 'bad' points removed, you probably want to optimise the geometry of this project with hugin or autooptimiser afterward. This tool is heavily inspired by APClean, a similar tool for PTGui project files by Fulvio Senore. If you want a similar behaviour to APClean without the optimisation steps, use the --fast option. =head1 LICENSE 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. =head1 SEE ALSO L L =head1 AUTHOR Bruno Postle - August 2008. =cut