1 // -*- c-basic-offset: 4 -*-
2 
3 /** @file fulla.cpp
4  *
5  *  @brief a tool to perform distortion, vignetting and chromatic abberation correction.
6  *
7  *  @author Pablo d'Angelo <pablo.dangelo@web.de>
8  *
9  *  $Id$
10  *
11  *  This program is free software; you can redistribute it and/or
12  *  modify it under the terms of the GNU General Public
13  *  License as published by the Free Software Foundation; either
14  *  version 2 of the License, or (at your option) any later version.
15  *
16  *  This software is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  *  General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public
22  *  License along with this software. If not, see
23  *  <http://www.gnu.org/licenses/>.
24  *
25  */
26 
27 #include <hugin_config.h>
28 #include <fstream>
29 #include <sstream>
30 
31 #include <vigra/error.hxx>
32 #include <vigra/impex.hxx>
33 #include <vigra/codec.hxx>
34 #include <vigra_ext/impexalpha.hxx>
35 #include <getopt.h>
36 
37 #include <appbase/ProgressDisplay.h>
38 #include <nona/SpaceTransform.h>
39 #include <photometric/ResponseTransform.h>
40 
41 #include <hugin_basic.h>
42 #include <lensdb/LensDB.h>
43 
44 #include <tiffio.h>
45 #include <vigra_ext/ImageTransforms.h>
46 
47 template <class SrcImgType, class AlphaImgType, class FlatImgType, class DestImgType>
48 void correctImage(SrcImgType& srcImg,
49                   const AlphaImgType& srcAlpha,
50                   const FlatImgType& srcFlat,
51                   HuginBase::SrcPanoImage src,
52                   vigra_ext::Interpolator interpolator,
53                   double maxValue,
54                   DestImgType& destImg,
55                   AlphaImgType& destAlpha,
56                   bool doCrop,
57                   AppBase::ProgressDisplay* progress);
58 
59 template <class PIXELTYPE>
60 void correctRGB(HuginBase::SrcPanoImage& src, vigra::ImageImportInfo& info, const char* outfile,
61                 bool crop, const std::string& compression, AppBase::ProgressDisplay* progress);
62 
usage(const char * name)63 static void usage(const char* name)
64 {
65     std::cout << name << ": correct lens distortion, vignetting and chromatic abberation" << std::endl
66          << "fulla version " << hugin_utils::GetHuginVersion() << endl
67          << std::endl
68          << "Usage: " << name  << " [options] inputfile(s) " << std::endl
69          << "   option are: " << std::endl
70          << "      --green=db|a:b:c:d  Correct radial distortion for all channels" << std::endl
71          << "                            Specify 'db' for database lookup or" << std::endl
72          << "                            the 4 coefficients a:b:c:d" << std::endl
73          << "      --blue=db|a:b:c:d   Correct radial distortion for blue channel," << std::endl
74          << "                            this is applied on top of the --green" << std::endl
75          << "                            distortion coefficients, use for TCA corr" << std::endl
76          << "                            Specify 'db' for database lookup or" << std::endl
77          << "                            the 4 coefficients a:b:c:d" << std::endl
78          << "      --red=db|a:b:c:d    Correct radial distortion for red channel," << std::endl
79          << "                            this is applied on top of the --green" << std::endl
80          << "                            distortion coefficients, use for TCA corr" << std::endl
81          << "                            Specify 'db' for database lookup or" << std::endl
82          << "                            the 4 coefficients a:b:c:d" << std::endl
83          << "      --camera-maker=Maker Camera manufacturer, for database query" << std::endl
84          << "      --camera-model=Cam Camera name, for database query" << std::endl
85          << "      --lensname=Lens    Lens name, for database query" << std::endl
86          << "                            Specify --camera-maker and --camera-model" << std::endl
87          << "                            for fixed lens cameras or --lensname" << std::endl
88          << "                            for interchangeable lenses." << std::endl
89          << "      --focallength=50   Specify focal length in mm, for database query" << std::endl
90          << "      --aperture=3.5     Specify aperture for vignetting data database query" << std::endl
91          << "      --dont-rescale     Do not rescale the image to avoid black borders." << std::endl
92          << std::endl
93          << "      --flatfield=filename  Vignetting correction by flatfield division" << std::endl
94          << "                              I = I / c, c = flatfield / mean(flatfield)" << std::endl
95          << "      --vignetting=db|a:b:c:d  Correct vignetting (by division)" << std::endl
96          << "                            Specify db for database look up or the " << std::endl
97          << "                            the 4 coefficients a:b:c:d" << std::endl
98          << "                              I = I / ( a + b*r^2 + c*r^4 + d*r^6)" << std::endl
99          << "      --linear           Do vignetting correction in linear color space" << std::endl
100          << "      --gamma=value      Gamma of input data. used for gamma correction" << std::endl
101          << "                           before and after flatfield correction" << std::endl
102          << "      --help             Display help (this text)" << std::endl
103          << "      --output=name      Set output filename. If more than one image is given," << std::endl
104          << "                            the name will be uses as suffix" << std::endl
105          << "                            (default suffix: _corr)" << std::endl
106          << "      --compression=value Compression of the output files" << std::endl
107          << "                            For jpeg output: 0-100" << std::endl
108          << "                            For tiff output: PACKBITS, DEFLATE, LZW" << std::endl
109          << "      --offset=X:Y       Horizontal and vertical shift" << std::endl
110          << "      --verbose          Verbose" << std::endl;
111 }
112 
113 
main(int argc,char * argv[])114 int main(int argc, char* argv[])
115 {
116     // parse arguments
117     const char* optstring = "e:g:b:r:m:n:l:d:sf:c:i:t:ho:x:va:";
118     int o;
119     enum
120     {
121         LINEAR_RESPONSE = 1000
122     };
123     static struct option longOptions[] =
124     {
125         { "linear", no_argument, NULL, LINEAR_RESPONSE },
126         { "green", required_argument, NULL, 'g' },
127         { "blue", required_argument, NULL, 'b' },
128         { "red", required_argument, NULL, 'r' },
129         { "lensname", required_argument, NULL, 'l' },
130         { "camera-maker", required_argument, NULL, 'm' },
131         { "camera-model", required_argument, NULL, 'n' },
132         { "aperture", required_argument, NULL, 'a' },
133         { "focallength", required_argument, NULL, 'd' },
134         { "flatfield", required_argument, NULL, 'f' },
135         { "dont-rescale", no_argument, NULL, 's' },
136         { "vignetting", required_argument, NULL, 'c' },
137         { "gamma", required_argument, NULL, 'i' },
138         { "threads", required_argument, NULL, 't' },
139         { "output", required_argument, NULL, 'o' },
140         { "compression", required_argument, NULL, 'e' },
141         { "offset", required_argument, NULL, 'x' },
142         { "verbose", no_argument, NULL, 'v' },
143         { "help", no_argument, NULL, 'h' },
144         0
145     };
146 
147     std::vector<double> vec4(4);
148     bool doFlatfield = false;
149     bool doVigRadial = false;
150     bool doCropBorders = true;
151     unsigned verbose = 0;
152 
153     std::string batchPostfix("_corr");
154     std::string outputFile;
155     std::string compression;
156     bool doLookupDistortion = false;
157     bool doLookupTCA = false;
158     bool doLookupVignetting = false;
159     std::string cameraMaker;
160     std::string cameraName;
161     std::string lensName;
162     float focalLength=0;
163     float aperture = 0;
164     double gamma = 1.0;
165     double shiftX = 0;
166     double shiftY = 0;
167     std::string argument;
168 
169     HuginBase::SrcPanoImage srcImg;
170     while ((o = getopt_long(argc, argv, optstring, longOptions, nullptr)) != -1)
171         switch (o)
172         {
173             case 'e':
174                 compression = optarg;
175                 break;
176             case 'r':
177                 argument = optarg;
178                 argument = hugin_utils::tolower(argument);
179                 if (argument == "db")
180                 {
181                     doLookupTCA = true;
182                 }
183                 else
184                 {
185                     if (sscanf(optarg, "%lf:%lf:%lf:%lf", &vec4[0], &vec4[1], &vec4[2], &vec4[3]) != 4)
186                     {
187                         std::cerr << hugin_utils::stripPath(argv[0]) << ": invalid -r argument" << std::endl;
188                         return 1;
189                     }
190                     srcImg.setRadialDistortionRed(vec4);
191                 };
192                 break;
193             case 'g':
194                 argument = optarg;
195                 argument = hugin_utils::tolower(argument);
196                 if (argument == "db")
197                 {
198                     doLookupDistortion = true;
199                 }
200                 else
201                 {
202                     if (sscanf(optarg, "%lf:%lf:%lf:%lf", &vec4[0], &vec4[1], &vec4[2], &vec4[3]) != 4)
203                     {
204                         std::cerr << hugin_utils::stripPath(argv[0]) << ": invalid -g argument" << std::endl;
205                         return 1;
206                     }
207                     srcImg.setRadialDistortion(vec4);
208                 };
209                 break;
210             case 'b':
211                 argument = optarg;
212                 argument = hugin_utils::tolower(argument);
213                 if (argument == "db")
214                 {
215                     doLookupTCA = true;
216                 }
217                 else
218                 {
219                     if (sscanf(optarg, "%lf:%lf:%lf:%lf", &vec4[0], &vec4[1], &vec4[2], &vec4[3]) != 4)
220                     {
221                         std::cerr << hugin_utils::stripPath(argv[0]) << ": invalid -b argument" << std::endl;
222                         return 1;
223                     }
224                     srcImg.setRadialDistortionBlue(vec4);
225                 };
226                 break;
227             case 's':
228                 doCropBorders = false;
229                 break;
230             case 'f':
231                 srcImg.setFlatfieldFilename(optarg);
232                 doFlatfield = true;
233                 break;
234             case 'i':
235                 gamma = atof(optarg);
236                 srcImg.setGamma(gamma);
237                 break;
238             case 'm':
239                 cameraMaker = optarg;
240                 break;
241             case 'n':
242                 cameraName = optarg;
243                 break;
244             case 'l':
245                 lensName = optarg;
246                 break;
247             case 'd':
248                 focalLength = atof(optarg);
249                 break;
250             case 'a':
251                 aperture = atof(optarg);
252                 break;
253             case 'c':
254                 argument = optarg;
255                 argument = hugin_utils::tolower(argument);
256                 if (argument == "db")
257                 {
258                     doLookupVignetting = true;
259                 }
260                 else
261                 {
262                     if (sscanf(optarg, "%lf:%lf:%lf:%lf", &vec4[0], &vec4[1], &vec4[2], &vec4[3]) != 4)
263                     {
264                         std::cerr << hugin_utils::stripPath(argv[0]) << ": invalid -c argument" << std::endl;
265                         return 1;
266                     }
267                     srcImg.setRadialVigCorrCoeff(vec4);
268                 };
269                 doVigRadial = true;
270                 break;
271             case 'h':
272                 usage(hugin_utils::stripPath(argv[0]).c_str());
273                 return 0;
274             case 't':
275                 std::cout << "WARNING: Switch --threads is deprecated. Set environment variable OMP_NUM_THREADS instead" << std::endl;
276                 break;
277             case 'o':
278                 outputFile = optarg;
279                 break;
280             case 'x':
281                 if (sscanf(optarg, "%lf:%lf", &shiftX, &shiftY) != 2)
282                 {
283                     std::cerr << hugin_utils::stripPath(argv[0]) << ": invalid -x argument" << std::endl;
284                     return 1;
285                 }
286                 srcImg.setRadialDistortionCenterShift(hugin_utils::FDiff2D(shiftX, shiftY));
287                 break;
288             case 'v':
289                 verbose++;
290                 break;
291             case LINEAR_RESPONSE:
292                 srcImg.setResponseType(HuginBase::BaseSrcPanoImage::RESPONSE_LINEAR);
293                 break;
294             case ':':
295             case '?':
296                 // missing argument or invalid switch
297                 return 1;
298                 break;
299             default:
300                 // this should not happen
301                 abort();
302         }
303 
304     if (doVigRadial && doFlatfield)
305     {
306         std::cerr << hugin_utils::stripPath(argv[0]) << ": cannot use -f and -c at the same time" << std::endl;
307         return 1;
308     }
309 
310     HuginBase::SrcPanoImage::VignettingCorrMode vm = HuginBase::SrcPanoImage::VIGCORR_NONE;
311 
312     if (doVigRadial)
313     {
314         vm = HuginBase::SrcPanoImage::VIGCORR_RADIAL;
315     }
316     if (doFlatfield)
317     {
318         vm = HuginBase::SrcPanoImage::VIGCORR_FLATFIELD;
319     }
320 
321     vm = (HuginBase::SrcPanoImage::VignettingCorrMode) (vm | HuginBase::SrcPanoImage::VIGCORR_DIV);
322     srcImg.setVigCorrMode(vm);
323 
324     unsigned nFiles = argc - optind;
325     if (nFiles == 0)
326     {
327         std::cerr << hugin_utils::stripPath(argv[0]) << ": No input file(s) specified" << std::endl;
328         return 1;
329     }
330 
331     // get input images.
332     std::vector<std::string> inFiles;
333     std::vector<std::string> outFiles;
334     if (nFiles == 1)
335     {
336         if (outputFile.length() !=0)
337         {
338             inFiles.push_back(std::string(argv[optind]));
339             outFiles.push_back(outputFile);
340         }
341         else
342         {
343             std::string name(argv[optind]);
344             inFiles.push_back(name);
345             std::string basen = hugin_utils::stripExtension(name);
346             outFiles.push_back(basen.append(batchPostfix.append(".").append(hugin_utils::getExtension(name))));
347         }
348     }
349     else
350     {
351         // multiple files
352         bool withExtension = false;
353         if (outputFile.length() != 0)
354         {
355             batchPostfix = outputFile;
356             withExtension = (batchPostfix.find('.') != std::string::npos);
357         }
358         for (int i = optind; i < argc; i++)
359         {
360             std::string name(argv[i]);
361             inFiles.push_back(name);
362             if (withExtension)
363             {
364                 outFiles.push_back(hugin_utils::stripExtension(name) + batchPostfix);
365             }
366             else
367             {
368                 outFiles.push_back(hugin_utils::stripExtension(name) + batchPostfix + "." + hugin_utils::getExtension(name));
369             };
370         }
371     }
372 
373     if (doLookupDistortion || doLookupVignetting || doLookupTCA)
374     {
375         if (lensName.empty())
376         {
377             if (!cameraMaker.empty() && !cameraName.empty())
378             {
379                 lensName = cameraMaker;
380                 lensName.append("|");
381                 lensName.append(cameraName);
382             };
383         };
384     };
385 
386     // suppress tiff warnings
387     TIFFSetWarningHandler(0);
388 
389     AppBase::ProgressDisplay* pdisp;
390     if (verbose > 0)
391     {
392         pdisp = new AppBase::StreamProgressDisplay(std::cout);
393     }
394     else
395     {
396         pdisp = new AppBase::DummyProgressDisplay();
397     };
398 
399     HuginBase::LensDB::LensDB& lensDB = HuginBase::LensDB::LensDB::GetSingleton();
400     try
401     {
402         std::vector<std::string>::iterator outIt = outFiles.begin();
403         for (std::vector<std::string>::iterator inIt = inFiles.begin(); inIt != inFiles.end() ; ++inIt, ++outIt)
404         {
405             if (verbose > 0)
406             {
407                 std::cerr << "Correcting " << *inIt << " -> " << *outIt << endl;
408             }
409             HuginBase::SrcPanoImage currentImg(srcImg);
410             currentImg.setFilename(*inIt);
411 
412             // load the input image
413             vigra::ImageImportInfo info(inIt->c_str());
414             const char* pixelType = info.getPixelType();
415             int bands = info.numBands();
416             int extraBands = info.numExtraBands();
417 
418             // do database lookup
419             if (doLookupDistortion || doLookupVignetting || doLookupTCA)
420             {
421                 currentImg.readEXIF();
422                 if (lensName.empty())
423                 {
424                     if (currentImg.getDBLensName().empty())
425                     {
426                         std::cerr << "Not enough data for database lookup" << std::endl
427                             << "Specify lensname (--lensname) or camera maker and model " << std::endl
428                             << "(--camera-maker and --camera-model) as parameter." << std::endl;
429                         continue;
430                     };
431                 }
432                 else
433                 {
434                     currentImg.setExifLens(lensName);
435                 }
436                 if (fabs(focalLength) < 0.1)
437                 {
438                     if (fabs(currentImg.getExifFocalLength()) < 0.1)
439                     {
440                         std::cerr << "Could not determine focal length." << std::endl
441                             << "Specify focal length (--focallength) as parameter." << std::endl;
442                         continue;
443                     };
444                 }
445                 else
446                 {
447                     currentImg.setExifFocalLength(focalLength);
448                 };
449                 if (verbose > 1)
450                 {
451                     std::cout << "Lookup in database for " << currentImg.getDBLensName() << " @ " << currentImg.getExifFocalLength() << " mm" << std::endl;
452                 };
453                 if (doLookupDistortion)
454                 {
455                     if (!currentImg.readDistortionFromDB())
456                     {
457                         std::cerr << "No suitable distortion data found in database." << std::endl
458                             << "Skipping image." << std::endl;
459                         continue;
460                     };
461                     if (verbose > 1)
462                     {
463                         std::vector<double> dist = currentImg.getRadialDistortion();
464                         std::cout << "Read distortion data: " << dist[0] << ", " << dist[1] << ", " << dist[2] << ", " << dist[3] << std::endl;
465                     };
466                 };
467                 if (doLookupVignetting)
468                 {
469                     if (fabs(aperture) < 0.1)
470                     {
471                         if (fabs(currentImg.getExifAperture()) < 0.1)
472                         {
473                             std::cerr << "Could not determine aperture." << std::endl
474                                 << "Specify aperture (--aperture) as parameter." << std::endl;
475                             continue;
476                         };
477                     }
478                     else
479                     {
480                         currentImg.setExifAperture(aperture);
481                     };
482                     if (!currentImg.readVignettingFromDB())
483                     {
484                         std::cerr << "No suitable vignetting data found in database." << std::endl
485                             << "Skipping image." << std::endl;
486                         continue;
487                     };
488                     if (verbose > 1)
489                     {
490                         std::vector<double> vig = currentImg.getRadialVigCorrCoeff();
491                         std::cout << "Read vigneting data: " << vig[1] << ", " << vig[2] << ", " << vig[3] << std::endl;
492                     };
493                 };
494 
495                 if (doLookupTCA)
496                 {
497                     std::vector<double> tcaRed, tcaBlue;
498                     if (lensDB.GetTCA(currentImg.getDBLensName(), currentImg.getExifFocalLength(), tcaRed, tcaBlue))
499                     {
500                         currentImg.setRadialDistortionRed(tcaRed);
501                         currentImg.setRadialDistortionBlue(tcaBlue);
502                     }
503                     else
504                     {
505                         std::cerr << "No suitable tca data found in database." << std::endl
506                             << "Skipping image." << std::endl;
507                         continue;
508                     };
509                     if (verbose>1)
510                     {
511                         std::cout << "Read tca data red:  " << tcaRed[0] << ", " << tcaRed[1] << ", " << tcaRed[2] << ", " << tcaRed[3] << std::endl;
512                         std::cout << "Read tca data blue: " << tcaBlue[0] << ", " << tcaBlue[1] << ", " << tcaBlue[2] << ", " << tcaBlue[3] << std::endl;
513                     };
514                 };
515             };
516             currentImg.setSize(info.size());
517             // stitch the pano with a suitable image type
518             if (bands == 3 || (bands == 4 && extraBands == 1))
519             {
520                 // TODO: add more cases
521                 if (strcmp(pixelType, "UINT8") == 0)
522                 {
523                     correctRGB<vigra::RGBValue<vigra::UInt8> >(currentImg, info, outIt->c_str(), doCropBorders, compression, pdisp);
524                 }
525                 else if (strcmp(pixelType, "UINT16") == 0)
526                 {
527                     correctRGB<vigra::RGBValue<vigra::UInt16> >(currentImg, info, outIt->c_str(), doCropBorders, compression, pdisp);
528                 }
529                 else if (strcmp(pixelType, "INT16") == 0)
530                 {
531                     correctRGB<vigra::RGBValue<vigra::Int16> >(currentImg, info, outIt->c_str(), doCropBorders, compression, pdisp);
532                 }
533                 else if (strcmp(pixelType, "UINT32") == 0)
534                 {
535                     correctRGB<vigra::RGBValue<vigra::UInt32> >(currentImg, info, outIt->c_str(), doCropBorders, compression, pdisp);
536                 }
537                 else if (strcmp(pixelType, "FLOAT") == 0)
538                 {
539                     correctRGB<vigra::RGBValue<float> >(currentImg, info, outIt->c_str(), doCropBorders, compression, pdisp);
540                 }
541                 else if (strcmp(pixelType, "DOUBLE") == 0)
542                 {
543                     correctRGB<vigra::RGBValue<double> >(currentImg, info, outIt->c_str(), doCropBorders, compression, pdisp);
544                 }
545             }
546             else
547             {
548                 DEBUG_ERROR("unsupported depth, only 3 channel images are supported");
549                 delete pdisp;
550                 HuginBase::LensDB::LensDB::Clean();
551                 throw std::runtime_error("unsupported depth, only 3 channels images are supported");
552                 return 1;
553             }
554         }
555     }
556     catch (std::exception& e)
557     {
558         std::cerr << "caught exception: " << e.what() << std::endl;
559         delete pdisp;
560         HuginBase::LensDB::LensDB::Clean();
561         return 1;
562     }
563     delete pdisp;
564     HuginBase::LensDB::LensDB::Clean();
565     return 0;
566 }
567 
568 class NullTransform
569 {
570 public:
transformImgCoord(double & x_dest,double & y_dest,double x_src,double y_src) const571     bool transformImgCoord(double & x_dest, double & y_dest, double x_src, double y_src) const
572     {
573         x_dest = x_src;
574         y_dest = y_src;
575         return true;
576     };
577 };
578 
579 
580 /** remap a single image
581  *
582  *  Be careful, might modify srcImg (vignetting and brightness correction)
583  *
584  */
585 template <class SrcImgType, class AlphaImgType, class FlatImgType, class DestImgType>
correctImage(SrcImgType & srcImg,const AlphaImgType & srcAlpha,const FlatImgType & srcFlat,HuginBase::SrcPanoImage src,vigra_ext::Interpolator interpolator,double maxValue,DestImgType & destImg,AlphaImgType & destAlpha,bool doCrop,AppBase::ProgressDisplay * progress)586 void correctImage(SrcImgType& srcImg,
587                   const AlphaImgType& srcAlpha,
588                   const FlatImgType& srcFlat,
589                   HuginBase::SrcPanoImage src,
590                   vigra_ext::Interpolator interpolator,
591                   double maxValue,
592                   DestImgType& destImg,
593                   AlphaImgType& destAlpha,
594                   bool doCrop,
595                   AppBase::ProgressDisplay* progress)
596 {
597     typedef typename SrcImgType::value_type SrcPixelType;
598     typedef typename DestImgType::value_type DestPixelType;
599 
600     // prepare some information required by multiple types of vignetting correction
601     progress->setMessage("correcting image");
602 
603     vigra::Diff2D shiftXY(- hugin_utils::roundi(src.getRadialDistortionCenterShift().x),
604                           - hugin_utils::roundi(src.getRadialDistortionCenterShift().y));
605 
606     if ((src.getVigCorrMode() & HuginBase::SrcPanoImage::VIGCORR_FLATFIELD)
607         || (src.getVigCorrMode() & HuginBase::SrcPanoImage::VIGCORR_RADIAL))
608     {
609         HuginBase::Photometric::InvResponseTransform<SrcPixelType, SrcPixelType> invResp(src);
610         if (src.getResponseType() == HuginBase::BaseSrcPanoImage::RESPONSE_EMOR)
611         {
612             std::vector<double> outLut;
613             vigra_ext::EMoR::createEMoRLUT(src.getEMoRParams(), outLut);
614             vigra_ext::enforceMonotonicity(outLut);
615             invResp.setOutput(1.0 / pow(2.0, src.getExposureValue()), outLut, maxValue);
616             if (maxValue != 1.0)
617             {
618                 // transform to range 0..1 for vignetting correction
619                 vigra::transformImage(srcImageRange(srcImg), destImage(srcImg), vigra::functor::Arg1()/vigra::functor::Param(maxValue));
620             };
621         };
622 
623         invResp.enforceMonotonicity();
624         if (src.getVigCorrMode() & HuginBase::SrcPanoImage::VIGCORR_FLATFIELD)
625         {
626             invResp.setFlatfield(&srcFlat);
627         }
628         NullTransform transform;
629         // dummy alpha channel
630         vigra::BImage alpha(destImg.size());
631         vigra_ext::transformImage(srcImageRange(srcImg), destImageRange(srcImg), destImage(alpha), vigra::Diff2D(0, 0), transform, invResp, false, vigra_ext::INTERP_SPLINE_16, progress);
632     }
633 
634     // radial distortion correction
635     if (doCrop)
636     {
637         double scaleFactor = HuginBase::Nona::estScaleFactorForFullFrame(src);
638         DEBUG_DEBUG("Black border correction scale factor: " << scaleFactor);
639         double sf=scaleFactor;
640         std::vector<double> radGreen = src.getRadialDistortion();
641         for (int i=0; i < 4; i++)
642         {
643             radGreen[3-i] *=sf;
644             sf *=scaleFactor;
645         }
646         src.setRadialDistortion(radGreen);
647     }
648 
649     vigra_ext::PassThroughFunctor<typename SrcPixelType::value_type> ptf;
650 
651     if (src.getCorrectTCA())
652     {
653         /*
654         DEBUG_DEBUG("Final distortion correction parameters:" << endl
655                 << "r: " << radRed[0] << " " << radRed[1] << " " << radRed[2] << " " << radRed[3]  << endl
656                 << "g: " << radGreen[0] << " " << radGreen[1] << " " << radGreen[2] << " " << radGreen[3] << endl
657                 << "b: " << radBlue[0] << " " << radBlue[1] << " " << radBlue[2] << " " << radBlue[3] << endl);
658         */
659         // remap individual channels
660         HuginBase::Nona::SpaceTransform transfr;
661         transfr.InitRadialCorrect(src, 0);
662         AlphaImgType redAlpha(destAlpha.size());
663         if (transfr.isIdentity())
664         {
665             vigra::copyImage(srcIterRange(srcImg.upperLeft(), srcImg.lowerRight(), vigra::RedAccessor<SrcPixelType>()),
666                              destIter(destImg.upperLeft(), vigra::RedAccessor<DestPixelType>()));
667             vigra::copyImage(srcImageRange(srcAlpha), destImage(redAlpha));
668         }
669         else
670         {
671             vigra_ext::transformImageAlpha(srcIterRange(srcImg.upperLeft(), srcImg.lowerRight(), vigra::RedAccessor<SrcPixelType>()),
672                                       srcImage(srcAlpha),
673                                       destIterRange(destImg.upperLeft(), destImg.lowerRight(), vigra::RedAccessor<DestPixelType>()),
674                                       destImage(redAlpha),
675                                       shiftXY,
676                                       transfr,
677                                       ptf,
678                                       false,
679                                       vigra_ext::INTERP_SPLINE_16,
680                                       progress);
681         }
682 
683         HuginBase::Nona::SpaceTransform transfg;
684         transfg.InitRadialCorrect(src, 1);
685         AlphaImgType greenAlpha(destAlpha.size());
686         if (transfg.isIdentity())
687         {
688             vigra::copyImage(srcIterRange(srcImg.upperLeft(), srcImg.lowerRight(), vigra::GreenAccessor<SrcPixelType>()),
689                              destIter(destImg.upperLeft(), vigra::GreenAccessor<DestPixelType>()));
690             vigra::copyImage(srcImageRange(srcAlpha), destImage(greenAlpha));
691         }
692         else
693         {
694             transformImageAlpha(srcIterRange(srcImg.upperLeft(), srcImg.lowerRight(), vigra::GreenAccessor<SrcPixelType>()),
695                            srcImage(srcAlpha),
696                            destIterRange(destImg.upperLeft(), destImg.lowerRight(), vigra::GreenAccessor<DestPixelType>()),
697                            destImage(greenAlpha),
698                            shiftXY,
699                            transfg,
700                            ptf,
701                            false,
702                            vigra_ext::INTERP_SPLINE_16,
703                            progress);
704         }
705 
706         HuginBase::Nona::SpaceTransform transfb;
707         transfb.InitRadialCorrect(src, 2);
708         AlphaImgType blueAlpha(destAlpha.size());
709         if (transfb.isIdentity())
710         {
711             vigra::copyImage(srcIterRange(srcImg.upperLeft(), srcImg.lowerRight(), vigra::BlueAccessor<SrcPixelType>()),
712                              destIter(destImg.upperLeft(), vigra::BlueAccessor<DestPixelType>()));
713             vigra::copyImage(srcImageRange(srcAlpha), destImage(blueAlpha));
714 
715         }
716         else
717         {
718             transformImageAlpha(srcIterRange(srcImg.upperLeft(), srcImg.lowerRight(), vigra::BlueAccessor<SrcPixelType>()),
719                            srcImage(srcAlpha),
720                            destIterRange(destImg.upperLeft(), destImg.lowerRight(), vigra::BlueAccessor<DestPixelType>()),
721                            destImage(blueAlpha),
722                            shiftXY,
723                            transfb,
724                            ptf,
725                            false,
726                            vigra_ext::INTERP_SPLINE_16,
727                            progress);
728         }
729         vigra::combineThreeImages(srcImageRange(redAlpha), srcImage(greenAlpha), srcImage(blueAlpha), destImage(destAlpha),
730             vigra::functor::Arg1() & vigra::functor::Arg2() & vigra::functor::Arg3());
731     }
732     else
733     {
734         // remap with the same coefficient.
735         HuginBase::Nona::SpaceTransform transf;
736         transf.InitRadialCorrect(src, 1);
737         std::vector <double> radCoeff = src.getRadialDistortion();
738         if (transf.isIdentity() ||
739                 (radCoeff[0] == 0.0 && radCoeff[1] == 0.0 && radCoeff[2] == 0.0 && radCoeff[3] == 1.0))
740         {
741             vigra::copyImage(srcImageRange(srcImg), destImage(destImg));
742             vigra::copyImage(srcImageRange(srcAlpha), destImage(destAlpha));
743         }
744         else
745         {
746             vigra_ext::PassThroughFunctor<SrcPixelType> ptfRGB;
747             transformImageAlpha(srcImageRange(srcImg),
748                            srcImage(srcAlpha),
749                            destImageRange(destImg),
750                            destImage(destAlpha),
751                            shiftXY,
752                            transf,
753                            ptfRGB,
754                            false,
755                            vigra_ext::INTERP_SPLINE_16,
756                            progress);
757         }
758     }
759 }
760 
761 
762 //void correctRGB(SrcImageInfo & src, ImageImportInfo & info, const char * outfile)
763 template <class PIXELTYPE>
correctRGB(HuginBase::SrcPanoImage & src,vigra::ImageImportInfo & info,const char * outfile,bool crop,const std::string & compression,AppBase::ProgressDisplay * progress)764 void correctRGB(HuginBase::SrcPanoImage& src, vigra::ImageImportInfo& info, const char* outfile,
765                 bool crop, const std::string& compression, AppBase::ProgressDisplay* progress)
766 {
767     vigra::BasicImage<vigra::RGBValue<double> > srcImg(info.size());
768     vigra::BasicImage<PIXELTYPE> output(info.size());
769     vigra::BImage alpha(info.size(), 255);
770     vigra::BImage outputAlpha(output.size());
771     if (info.numBands() == 3)
772     {
773         vigra::importImage(info, destImage(srcImg));
774     }
775     else
776     {
777         importImageAlpha(info, destImage(srcImg), destImage(alpha));
778     };
779     vigra::FImage flatfield;
780     if (src.getVigCorrMode() & HuginBase::SrcPanoImage::VIGCORR_FLATFIELD)
781     {
782         vigra::ImageImportInfo finfo(src.getFlatfieldFilename().c_str());
783         flatfield.resize(finfo.size());
784         vigra::importImage(finfo, destImage(flatfield));
785     }
786     correctImage(srcImg, alpha, flatfield, src, vigra_ext::INTERP_SPLINE_16, vigra_ext::getMaxValForPixelType(info.getPixelType()),
787         output, outputAlpha, crop, progress);
788     vigra::ImageExportInfo outInfo(outfile);
789     outInfo.setICCProfile(info.getICCProfile());
790     outInfo.setPixelType(info.getPixelType());
791     if (!compression.empty())
792     {
793         outInfo.setCompression(compression.c_str());
794     }
795     const std::string filetype(vigra::getEncoder(outInfo.getFileName())->getFileType());
796     if (vigra::isBandNumberSupported(filetype, 4))
797     {
798         // image format supports alpha channel
799         std::cout << "Saving " << outInfo.getFileName() << std::endl;
800         vigra::exportImageAlpha(srcImageRange(output), srcImage(outputAlpha), outInfo);
801     }
802     else
803     {
804         // image format does not support an alpha channel, disregard alpha channel
805         std::cout << "Saving " << outInfo.getFileName() << " without alpha channel" << std::endl
806             << "because the fileformat " << filetype << " does not support" << std::endl
807             << "an alpha channel." << std::endl;
808         exportImage(srcImageRange(output), outInfo);
809     };
810 }
811