void OptimizePhotometricPanel::runOptimizer(const UIntSet & imgs)
{
    DEBUG_TRACE("");
    int mode = m_mode_cb->GetSelection();

    // check if vignetting and response are linked, display a warning if they are not
    // The variables to check:
    const HuginBase::ImageVariableGroup::ImageVariableEnum vars[] = {
            HuginBase::ImageVariableGroup::IVE_EMoRParams,
            HuginBase::ImageVariableGroup::IVE_ResponseType,
            HuginBase::ImageVariableGroup::IVE_VigCorrMode,
            HuginBase::ImageVariableGroup::IVE_RadialVigCorrCoeff,
            HuginBase::ImageVariableGroup::IVE_RadialVigCorrCenterShift
        };
    // keep a list of commands needed to fix it:
    std::vector<PT::PanoCommand *> commands;
    HuginBase::ConstImageVariableGroup & lenses = variable_groups->getLenses();
    for (size_t i = 0; i < lenses.getNumberOfParts(); i++)
    {
        std::set<HuginBase::ImageVariableGroup::ImageVariableEnum> links_needed;
        links_needed.clear();
        for (int v = 0; v < 5; v++)
        {
            if (!lenses.getVarLinkedInPart(vars[v], i))
            {
                links_needed.insert(vars[v]);
            }
        };
        if (!links_needed.empty())
        {
            commands.push_back(new PT::LinkLensVarsCmd(*m_pano, i, links_needed));
        }
    }
    // if the list of commands is empty, all is good and we don't need a warning.
    if (!commands.empty()) {
        int ok = wxMessageBox(_("The same vignetting and response parameters should\nbe applied for all images of a lens.\nCurrently each image can have different parameters.\nLink parameters?"), _("Link parameters"), wxYES_NO | wxICON_INFORMATION);
        if (ok == wxYES) {
            // perform all the commands we stocked up earilier.
            for (std::vector<PT::PanoCommand *>::iterator it = commands.begin();
                    it != commands.end(); it++)
            {
                GlobalCmdHist::getInstance().addCommand(*it);
            }
        } else {
            // free all the commands, the user doesn't want them used.
            for (std::vector<PT::PanoCommand *>::iterator it = commands.begin();
                    it != commands.end(); it++)
            {
                delete *it;
            }
        }
    }

    Panorama optPano = m_pano->getSubset(imgs);
    PanoramaOptions opts = optPano.getOptions();

    OptimizeVector optvars;
    if(mode==OPT_CUSTOM)
    {
        optvars = getOptimizeVector();
        if (optPano.getNrOfImages() != m_pano->getNrOfImages())
        {
            OptimizeVector o = optvars;
            optvars.clear();
            for (UIntSet::const_iterator it = imgs.begin(); it != imgs.end(); ++it)
            {
                optvars.push_back(o[*it]);
            }
        }
        unsigned int countVar=0;
        for(unsigned int i=0;i<optvars.size();i++)
        {
            countVar+=optvars[i].size();
        };
        if(countVar==0)
        {
            wxMessageBox(_("You selected no parameters to optimize.\nTherefore optimization will be canceled."), _("Exposure optimization"), wxOK | wxICON_INFORMATION);
            return;
        };
    };


    std::vector<vigra_ext::PointPairRGB> m_points;
    // extract points only if not done previously

    long nPoints = 200;
    wxConfigBase::Get()->Read(wxT("/OptimizePhotometric/nRandomPointsPerImage"), & nPoints);
    // get parameters for estimation.
    nPoints = wxGetNumberFromUser(_("The vignetting and exposure correction is determined by analysing color values in the overlapping areas.\nTo speed up the computation, only a random subset of points is used."),
                                    _("Number of points per image"),
                                    _("Photometric optimization"), nPoints, 0, 32000,
                                    this);
    if (nPoints < 0) {
        return;
    }
    wxConfigBase::Get()->Write(wxT("/OptimizePhotometric/nRandomPointsPerImage"),nPoints);

    ProgressReporterDialog progress(5.0, _("Photometric alignment"), _("Loading images"));

    progress.Show();

    nPoints = nPoints * optPano.getNrOfImages();
    // get the small images
    std::vector<vigra::FRGBImage *> srcImgs;
    for (size_t i=0; i < optPano.getNrOfImages(); i++) {
        ImageCache::EntryPtr e = ImageCache::getInstance().getSmallImage(optPano.getImage(i).getFilename());
        vigra::FRGBImage * img = new FRGBImage;
        if (!e) {
            wxMessageBox(_("Error: could not load all images"), _("Error"));
            return;
        }
        if (e->image8 && e->image8->width() > 0) {
            reduceToNextLevel(*(e->image8), *img);
            transformImage(vigra::srcImageRange(*img), vigra::destImage(*img),
                            vigra::functor::Arg1()/vigra::functor::Param(255.0));
        } else if (e->image16 && e->image16->width() > 0) {
            reduceToNextLevel(*(e->image16), *img);
            transformImage(vigra::srcImageRange(*img), vigra::destImage(*img),
                            vigra::functor::Arg1()/vigra::functor::Param(65535.0));
        } else {
            reduceToNextLevel(*(e->imageFloat), *img);
        }
        srcImgs.push_back(img);
    }
    bool randomPoints = true;
    extractPoints(optPano, srcImgs, nPoints, randomPoints, progress, m_points);

    if (m_points.size() == 0) {
        wxMessageBox(_("Error: no overlapping points found, Photometric optimization aborted"), _("Error"));
        return;
    }

    double error = 0;
    try {
        //wxBusyCursor busyc;
        if (mode != OPT_CUSTOM) {
            // run automatic optimisation
            // ensure that we have a valid anchor.
            PanoramaOptions opts = optPano.getOptions();
            if (opts.colorReferenceImage >= optPano.getNrOfImages()) {
                opts.colorReferenceImage = 0;
                optPano.setOptions(opts);
            }
            smartOptimizePhotometric(optPano, PhotometricOptimizeMode(mode),
                                    m_points, progress, error);
        } else {
            // optimize selected parameters
            optimizePhotometric(optPano, optvars,
                                m_points, progress, error);
        }
    } catch (std::exception & error) {
        wxMessageBox(_("Internal error during photometric optimization:\n") + wxString(error.what(), wxConvLocal), _("Internal error"));
    }

    progress.Close();

    // display information about the estimation process:
    int ret = wxMessageBox(wxString::Format(_("Photometric optimization results:\nAverage difference (RMSE) between overlapping pixels: %.2f gray values (0..255)\n\nApply results?"), error*255),
                           _("Photometric optimization finished"), wxYES_NO | wxICON_INFORMATION,this);

    if (ret == wxYES) {
        DEBUG_DEBUG("Applying vignetting corr");
        // TODO: merge into a single update command
        const VariableMapVector & vars = optPano.getVariables();
        GlobalCmdHist::getInstance().addCommand(
                new PT::UpdateImagesVariablesCmd(*m_pano, imgs, vars)
                                               );
        //now update panorama exposure value
        PanoramaOptions opts = m_pano->getOptions();
        opts.outputExposureValue = calcMeanExposure(*m_pano);
        GlobalCmdHist::getInstance().addCommand(
                new PT::SetPanoOptionsCmd(*m_pano, opts)
                                               );
    }
}
Пример #2
0
int main(int argc, char *argv[])
{
    // parse arguments
    const char * optstring = "alho:npqsv:m";
    int c;
    string output;
    bool doPairwise = false;
    bool doAutoOpt = false;
    bool doNormalOpt = false;
    bool doLevel = false;
    bool chooseProj = false;
    bool quiet = false;
    bool doPhotometric = false;
    double hfov = 0.0;
    while ((c = getopt (argc, argv, optstring)) != -1)
    {
        switch (c) {
        case 'o':
            output = optarg;
            break;
        case 'h':
            usage(argv[0]);
            return 0;
        case 'p':
            doPairwise = true;
            break;
        case 'a':
            doAutoOpt = true;
            break;
        case 'n':
            doNormalOpt = true;
            break;
        case 'l':
            doLevel = true;
            break;
        case 's':
            chooseProj = true;
            break;
        case 'q':
            quiet = true;
            break;
        case 'v':
            hfov = atof(optarg);
            break;
        case 'm':
            doPhotometric = true;
            break;
        default:
            abort ();
        }
    }

    if (argc - optind != 1) {
        usage(argv[0]);
        return 1;
    }

    const char * scriptFile = argv[optind];

    Panorama pano;
    if (scriptFile[0] == '-') {
        DocumentData::ReadWriteError err = pano.readData(std::cin);
        if (err != DocumentData::SUCCESSFUL) {
            cerr << "error while reading script file from stdin." << endl;
            cerr << "DocumentData::ReadWriteError code: " << err << endl;
            return 1;
        }
    } else {
        ifstream prjfile(scriptFile);
        if (!prjfile.good()) {
            cerr << "could not open script : " << scriptFile << endl;
            return 1;
        }
        pano.setFilePrefix(hugin_utils::getPathPrefix(scriptFile));
        DocumentData::ReadWriteError err = pano.readData(prjfile);
        if (err != DocumentData::SUCCESSFUL) {
            cerr << "error while parsing panos tool script: " << scriptFile << endl;
            cerr << "DocumentData::ReadWriteError code: " << err << endl;
            return 1;
        }
    }

    if (pano.getNrOfImages() == 0) {
        cerr << "Panorama should consist of at least one image" << endl;
        return 1;
    }

    // for bad HFOV (from autopano-SIFT)
    for (unsigned i=0; i < pano.getNrOfImages(); i++) {
        SrcPanoImage img = pano.getSrcImage(i);
        if (img.getProjection() == SrcPanoImage::RECTILINEAR
            && img.getHFOV() >= 180)
        {
            // something is wrong here, try to read from exif data
            double focalLength = 0;
            double cropFactor = 0;
            cerr << "HFOV of image " << img.getFilename() << " invalid, trying to read EXIF tags" << endl;
            bool ok = img.readEXIF(focalLength, cropFactor, true, false);
            if (! ok) {
                if (hfov) {
                    img.setHFOV(hfov);
                } else {
                    cerr << "EXIF reading failed, please specify HFOV with -v" << endl;
                    return 1;
                }
            }
            pano.setSrcImage(i, img);
        }
    }

    if(pano.getNrOfCtrlPoints()==0 && (doPairwise || doAutoOpt || doNormalOpt))
    {
        cerr << "Panorama have to have control points to optimise positions" << endl;
        return 1;
    };


	
	
	if (doPairwise && ! doAutoOpt) {
        // do pairwise optimisation
        set<string> optvars;
        optvars.insert("r");
        optvars.insert("p");
        optvars.insert("y");
        AutoOptimise::autoOptimise(pano);

        // do global optimisation
        if (!quiet) std::cerr << "*** Pairwise position optimisation" << endl;
        PTools::optimize(pano);
    } else if (doAutoOpt) {
        if (!quiet) std::cerr << "*** Adaptive geometric optimisation" << endl;
        SmartOptimise::smartOptimize(pano);
    } else if (doNormalOpt) {
        if (!quiet) std::cerr << "*** Optimising parameters specified in PTO file" << endl;
        PTools::optimize(pano);
    } else {
        if (!quiet) std::cerr << "*** Geometric parameters not optimized" << endl;
    }

    if (doLevel)
    {
        bool hasVerticalLines=false;
        CPVector allCP=pano.getCtrlPoints();
        if(allCP.size()>0 && (doPairwise || doAutoOpt || doNormalOpt))
        {
            for(size_t i=0;i<allCP.size() && !hasVerticalLines;i++)
            {
                hasVerticalLines=(allCP[i].mode==ControlPoint::X);
            };
        };
        // straighten only if there are no vertical control points
        if(hasVerticalLines)
        {
            cout << "Skipping automatic leveling because of existing vertical control points." << endl;
        }
        else
        {
            StraightenPanorama(pano).run();
            CenterHorizontally(pano).run();
        };
    }

    if (chooseProj) {
        PanoramaOptions opts = pano.getOptions();
        double hfov, vfov;
        CalculateFitPanorama fitPano = CalculateFitPanorama(pano);
        fitPano.run();
        opts.setHFOV(fitPano.getResultHorizontalFOV());
        opts.setHeight(roundi(fitPano.getResultHeight()));
        vfov = opts.getVFOV();
        hfov = opts.getHFOV();
        // avoid perspective projection if field of view > 100 deg
        double mf = 100;
        if (vfov < mf) {
            // cylindrical or rectilinear
            if (hfov < mf) {
                opts.setProjection(PanoramaOptions::RECTILINEAR);
            } else {
                opts.setProjection(PanoramaOptions::CYLINDRICAL);
            }
        }

        // downscale pano a little
        double sizeFactor = 0.7;

        pano.setOptions(opts);
        double w = CalculateOptimalScale::calcOptimalScale(pano);
        opts.setWidth(roundi(opts.getWidth()*w*sizeFactor), true);
        pano.setOptions(opts);
    }

    if(doPhotometric)
    {
        // photometric estimation
        PanoramaOptions opts = pano.getOptions();
        int nPoints = 200;
        int pyrLevel=3;
        bool randomPoints = true;
        nPoints = nPoints * pano.getNrOfImages();
 
        std::vector<vigra_ext::PointPairRGB> points;
        ProgressDisplay *progressDisplay;
        if(!quiet)
            progressDisplay=new StreamProgressDisplay(std::cout);
        else
            progressDisplay=new DummyProgressDisplay();
        try 
        {
            loadImgsAndExtractPoints(pano, nPoints, pyrLevel, randomPoints, *progressDisplay, points, !quiet);
        } 
        catch (std::exception & e)
        {
            cerr << "caught exception: " << e.what() << endl;
            return 1;
        };
        if(!quiet)
            cout << "\rSelected " << points.size() << " points" << endl;

        if (points.size() == 0)
        {
            cerr << "Error: no overlapping points found, exiting" << endl;
            return 1;
        }

        progressDisplay->startSubtask("Photometric Optimization", 0.0);
        // first, ensure that vignetting and response coefficients are linked
        const HuginBase::ImageVariableGroup::ImageVariableEnum vars[] = {
                HuginBase::ImageVariableGroup::IVE_EMoRParams,
                HuginBase::ImageVariableGroup::IVE_ResponseType,
                HuginBase::ImageVariableGroup::IVE_VigCorrMode,
                HuginBase::ImageVariableGroup::IVE_RadialVigCorrCoeff,
                HuginBase::ImageVariableGroup::IVE_RadialVigCorrCenterShift
        };
        HuginBase::StandardImageVariableGroups variable_groups(pano);
        HuginBase::ImageVariableGroup & lenses = variable_groups.getLenses();
        for (size_t i = 0; i < lenses.getNumberOfParts(); i++)
        {
            std::set<HuginBase::ImageVariableGroup::ImageVariableEnum> links_needed;
            links_needed.clear();
            for (int v = 0; v < 5; v++)
            {
                if (!lenses.getVarLinkedInPart(vars[v], i))
                {
                    links_needed.insert(vars[v]);
                }
            };
            if (!links_needed.empty())
            {
                std::set<HuginBase::ImageVariableGroup::ImageVariableEnum>::iterator it;
                for (it = links_needed.begin(); it != links_needed.end(); it++)
                {
                    lenses.linkVariablePart(*it, i);
                }
            }
        }

        HuginBase::SmartPhotometricOptimizer::PhotometricOptimizeMode optmode = 
            HuginBase::SmartPhotometricOptimizer::OPT_PHOTOMETRIC_LDR;
        if (opts.outputMode == PanoramaOptions::OUTPUT_HDR)
        {
            optmode = HuginBase::SmartPhotometricOptimizer::OPT_PHOTOMETRIC_HDR;
        }
        SmartPhotometricOptimizer photoOpt(pano, progressDisplay, pano.getOptimizeVector(), points, optmode);
        photoOpt.run();

        // calculate the mean exposure.
        opts.outputExposureValue = CalculateMeanExposure::calcMeanExposure(pano);
        pano.setOptions(opts);
        progressDisplay->finishSubtask();
        delete progressDisplay;
    };

    // write result
    OptimizeVector optvec = pano.getOptimizeVector();
    UIntSet imgs;
    fill_set(imgs,0, pano.getNrOfImages()-1);
    if (output != "") {
        ofstream of(output.c_str());
        pano.printPanoramaScript(of, optvec, pano.getOptions(), imgs, false, hugin_utils::getPathPrefix(scriptFile));
    } else {
        pano.printPanoramaScript(cout, optvec, pano.getOptions(), imgs, false, hugin_utils::getPathPrefix(scriptFile));
    }
    return 0;
}
Пример #3
0
int main(int argc, char* argv[])
{
    // parse arguments
    const char* optstring = "o:p:f:c:s:lh";

    static struct option longOptions[] =
    {
        {"output", required_argument, NULL, 'o' },
        {"projection", required_argument, NULL, 'p' },
        {"fov", required_argument, NULL, 'f' },
        {"crop", required_argument, NULL, 'c' },
        {"stacklength", required_argument, NULL, 's' },
        {"linkstacks", no_argument, NULL, 'l' },
        {"distortion", no_argument, NULL, 300 },
        {"vignetting", no_argument, NULL, 301 },
        {"help", no_argument, NULL, 'h' },

        0
    };

    int c;
    int optionIndex = 0;
    string output;
    int projection=-1;
    float fov=-1;
    int stackLength=1;
    bool linkStacks=false;
    vigra::Rect2D cropRect(0,0,0,0);
    bool loadDistortion=false;
    bool loadVignetting=false;
    while ((c = getopt_long (argc, argv, optstring, longOptions,&optionIndex)) != -1)
    {
        switch (c)
        {
            case 'o':
                output = optarg;
                break;
            case 'h':
                usage(argv[0]);
                return 0;
            case 'p':
                {
                    projection=atoi(optarg);
                    if((projection==0) && (strcmp(optarg,"0")!=0))
                    {
                        cerr << "Could not parse image number.";
                        return 1;
                    };
                    if(projection<0)
                    {
                        cerr << "Invalid projection number." << endl;
                        return 1;
                    };
                };
                break;
            case 'f':
                fov=atof(optarg);
                if(fov<1 || fov>360)
                {
                    cerr << "Invalid field of view";
                    return 1;
                };
                break;
            case 'c':
                {
                    int left, right, top, bottom;
                    int n=sscanf(optarg, "%d,%d,%d,%d", &left, &right, &top, &bottom);
                    if (n==4)
                    {
                        if(right>left && bottom>top)
                        {
                            cropRect.setUpperLeft(vigra::Point2D(left,top));
                            cropRect.setLowerRight(vigra::Point2D(right,bottom));
                        }
                        else
                        {
                            cerr << "Invalid crop area" << endl;
                            return 1;
                        };
                    }
                    else
                    {
                        cerr << "Could not parse crop values" << endl;
                        return 1;
                    };
                };
                break;
            case 's':
                stackLength=atoi(optarg);
                if(stackLength<1)
                {
                    cerr << "Could not parse stack length." << endl;
                    return 1;
                };
                break;
            case 'l':
                linkStacks=true;
                break;
            case 300:
                loadDistortion=true;
                break;
            case 301:
                loadVignetting=true;
                break;
            case ':':
                cerr <<"Option " << longOptions[optionIndex].name << " requires a number" << endl;
                return 1;
                break;
            case '?':
                break;
            default:
                abort ();
        }
    }

    if (argc - optind < 1)
    {
        usage(argv[0]);
        return 1;
    };

    cout << "Generating pto file..." << endl;
    cout.flush();

    std::vector<string> filelist;
    while(optind<argc)
    {
        string input;
#ifdef _WINDOWS
        //do globbing
        input=GetAbsoluteFilename(argv[optind]);
        char drive[_MAX_DRIVE];
        char dir[_MAX_DIR];
        char fname[_MAX_FNAME];
        char ext[_MAX_EXT];
        char newFile[_MAX_PATH];

        _splitpath(input.c_str(), drive, dir, NULL, NULL);

        struct _finddata_t finddata;
        intptr_t findhandle = _findfirst(input.c_str(), &finddata);
        if (findhandle != -1)
        {
            do
            {
                //ignore folder, can be happen when using *.*
                if((finddata.attrib & _A_SUBDIR)==0)
                {
                    _splitpath(finddata.name, NULL, NULL, fname, ext);
                    _makepath(newFile, drive, dir, fname, ext);
                    //check if valid image file
                    if(vigra::isImage(newFile))
                    {
                        filelist.push_back(std::string(newFile));
                    };
                };
            }
            while (_findnext(findhandle, &finddata) == 0);
            _findclose(findhandle);
        }
#else
        input=argv[optind];
        if(hugin_utils::FileExists(input))
        {
            if(vigra::isImage(input.c_str()))
            {
                filelist.push_back(GetAbsoluteFilename(input));
            };
        };
#endif
        optind++;
    };

    if(filelist.size()==0)
    {
        cerr << "No valid image files given." << endl;
        return 1;
    };

    //sort filenames
    sort(filelist.begin(),filelist.end(),doj::alphanum_less());

    if(projection<0)
    {
        InitLensDB();
    };

    Panorama pano;
    for(size_t i=0; i<filelist.size();i++)
    {
        SrcPanoImage srcImage;
        cout << "Reading " << filelist[i] << "..." << endl;
        srcImage.setFilename(filelist[i]);
        try
        {
            vigra::ImageImportInfo info(filelist[i].c_str());
            if(info.width()==0 || info.height()==0)
            {
                cerr << "ERROR: Could not decode image " << filelist[i] << endl
                     << "Skipping this image." << endl << endl;
                continue;
            }
            srcImage.setSize(info.size());
            std::string pixelType=info.getPixelType();
            if((pixelType=="UINT8") || (pixelType=="UINT16") || (pixelType=="INT16"))
            {
                srcImage.setResponseType(HuginBase::SrcPanoImage::RESPONSE_EMOR);
            }
            else
            {
                srcImage.setResponseType(HuginBase::SrcPanoImage::RESPONSE_LINEAR);
            };
        }
        catch(std::exception & e)
        {
            cerr << "ERROR: caught exception: " << e.what() << endl;
            cerr << "Could not read image information for file " << filelist[i] << endl;
            cerr << "Skipping this image." << endl << endl;
            continue;
        };

        srcImage.readEXIF();
        srcImage.applyEXIFValues();
        if(projection>=0)
        {
            srcImage.setProjection((HuginBase::BaseSrcPanoImage::Projection)projection);
        }
        else
        {
            srcImage.readProjectionFromDB();
        };
        if(fov>0)
        {
            srcImage.setHFOV(fov);
            if(srcImage.getCropFactor()==0)
            {
                srcImage.setCropFactor(1.0);
            };
        }
        else
        {
            //set plausible default value if they could not read from exif
            if(srcImage.getExifFocalLength()==0 || srcImage.getCropFactor()==0)
            {
                cout << "\tNo value for field of view found in EXIF data. " << endl
                     << "\tAssuming a HFOV of 50 degrees. " << endl;
                srcImage.setHFOV(50);
                srcImage.setCropFactor(1.0);
            };
        };
        if(cropRect.width()>0 && cropRect.height()>0)
        {
            if(srcImage.isCircularCrop())
            {
                srcImage.setCropMode(SrcPanoImage::CROP_CIRCLE);
            }
            else
            {
                srcImage.setCropMode(SrcPanoImage::CROP_RECTANGLE);
            };
            srcImage.setAutoCenterCrop(false);
            srcImage.setCropRect(cropRect);
        };
        if(loadDistortion)
        {
            if(srcImage.readDistortionFromDB())
            {
                cout << "\tRead distortion data from lensfun database." << endl;
            }
            else
            {
                cout << "\tNo valid distortion data found in lensfun database." << endl;
            };
        };
        if(loadVignetting)
        {
            if(srcImage.readVignettingFromDB())
            {
                cout << "\tRead vignetting data from lensfun database." << endl;
            }
            else
            {
                cout << "\tNo valid vignetting data found in lensfun database." << endl;
            };
        };

        pano.addImage(srcImage);
    };

    if(pano.getNrOfImages()==0)
    {
        cerr << "Adding images to project files failed." << endl;
        HuginBase::LensDB::LensDB::Clean();
        return 1;
    };

    //link lenses
    if(pano.getNrOfImages()>1)
    {
        double redBalanceAnchor=pano.getImage(pano.getOptions().colorReferenceImage).getExifRedBalance();
        double blueBalanceAnchor=pano.getImage(pano.getOptions().colorReferenceImage).getExifBlueBalance();
        if(fabs(redBalanceAnchor)<1e-2)
        {
            redBalanceAnchor=1;
        };
        if(fabs(blueBalanceAnchor)<1e-2)
        {
            blueBalanceAnchor=1;
        };
        StandardImageVariableGroups variable_groups(pano);
        ImageVariableGroup& lenses = variable_groups.getLenses();

        for(size_t i=1;i<pano.getNrOfImages();i++)
        {
            int image=-1;
            const SrcPanoImage & srcImg=pano.getImage(i);
            for(size_t j=0;j<i;j++)
            {
                const SrcPanoImage & compareImg=pano.getImage(j);
                if(srcImg.getHFOV()==compareImg.getHFOV() &&
                    srcImg.getProjection()==compareImg.getProjection() &&
                    srcImg.getExifModel()==compareImg.getExifModel() &&
                    srcImg.getExifMake()==compareImg.getExifMake() &&
                    srcImg.getSize()==compareImg.getSize())
                {
                    image=j;
                    break;
                };
            };
            if(image!=-1)
            {
                SrcPanoImage img=pano.getSrcImage(i);
                double ev=img.getExposureValue();
                lenses.switchParts(i,lenses.getPartNumber(image));
                lenses.unlinkVariableImage(HuginBase::ImageVariableGroup::IVE_ExposureValue, i);
                img.setExposureValue(ev);
                lenses.unlinkVariableImage(HuginBase::ImageVariableGroup::IVE_WhiteBalanceRed, i);
                lenses.unlinkVariableImage(HuginBase::ImageVariableGroup::IVE_WhiteBalanceBlue, i);
                img.setWhiteBalanceRed(img.getExifRedBalance()/redBalanceAnchor);
                img.setWhiteBalanceBlue(img.getExifBlueBalance()/blueBalanceAnchor);
                pano.setSrcImage(i, img);
            };
        };
        cout << endl << "Assigned " << lenses.getNumberOfParts() << " lenses." << endl;
        if(lenses.getNumberOfParts()>1 && stackLength>1)
        {
            cout << "Project contains more than one lens, but you requested to assign" << endl
                 << "stacks. This is not supported. Therefore stacks will not be" << endl
                 << "assigned." << endl << endl;
            stackLength=1;
        };
    };

    //link stacks
    if(pano.getNrOfImages()>1 && stackLength>1)
    {
        stackLength=std::min<int>(stackLength,pano.getNrOfImages());
        int stackCount=pano.getNrOfImages() / stackLength;
        if(pano.getNrOfImages() % stackLength > 0)
        {
            stackCount++;
        };
        if(stackCount<pano.getNrOfImages())
        {
            for(size_t stackNr=0;stackNr<stackCount;stackNr++)
            {
                size_t firstImgStack=stackNr*stackLength;
                for(size_t i=0;i<stackLength;i++)
                {
                    if(firstImgStack+i<pano.getNrOfImages())
                    {
                        pano.linkImageVariableStack(firstImgStack,firstImgStack+i);
                        if(linkStacks)
                        {
                            pano.linkImageVariableYaw(firstImgStack,firstImgStack+i);
                            pano.linkImageVariablePitch(firstImgStack,firstImgStack+i);
                            pano.linkImageVariableRoll(firstImgStack,firstImgStack+i);
                        };
                    };
                };
            };
            cout << "Assigned " << stackCount << " stacks." << endl;
        };
    };

    //set output exposure value
    PanoramaOptions opt = pano.getOptions();
    opt.outputExposureValue = CalculateMeanExposure::calcMeanExposure(pano);
    pano.setOptions(opt);
    // set optimizer switches
    pano.setOptimizerSwitch(HuginBase::OPT_PAIR);
    pano.setPhotometricOptimizerSwitch(HuginBase::OPT_EXPOSURE | HuginBase::OPT_VIGNETTING | HuginBase::OPT_RESPONSE);

    //output
    if(output=="")
    {
        output=hugin_utils::stripExtension(pano.getImage(0).getFilename());
        if(pano.getNrOfImages()>1)
        {
            output.append("-");
            output.append(hugin_utils::stripExtension(hugin_utils::stripPath(pano.getImage(pano.getNrOfImages()-1).getFilename())));
        };
        output=output.append(".pto");
    };
    output=GetAbsoluteFilename(output);
    //write output
    UIntSet imgs;
    fill_set(imgs,0, pano.getNrOfImages()-1);
    ofstream of(output.c_str());
    pano.printPanoramaScript(of, pano.getOptimizeVector(), pano.getOptions(), imgs, false, hugin_utils::getPathPrefix(output));

    cout << endl << "Written output to " << output << endl;
    HuginBase::LensDB::LensDB::Clean();
    return 0;
}