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