1 // -*- c-basic-offset: 4 -*-
2 
3 /** @file SrcPanoImage.h
4  *
5  *  @brief
6  *
7  *  @author Pablo d'Angelo <pablo.dangelo@web.de>
8  *          James Legg
9  *
10  * !! from PanoImage.h 1970
11  *
12  */
13 /*
14  *  This program is free software; you can redistribute it and/or
15  *  modify it under the terms of the GNU General Public
16  *  License as published by the Free Software Foundation; either
17  *  version 2 of the License, or (at your option) any later version.
18  *
19  *  This software is distributed in the hope that it will be useful,
20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22  *  General Public License for more details.
23  *
24  *  You should have received a copy of the GNU General Public
25  *  License along with this software. If not, see
26  *  <http://www.gnu.org/licenses/>.
27  *
28  */
29 
30 // for debugging
31 #include <iostream>
32 #include <stdio.h>
33 #include <stdexcept>
34 //#include <wx/wxprec.h>
35 
36 #include "SrcPanoImage.h"
37 
38 #include <iostream>
39 #include <vector>
40 #include <vigra/diff2d.hxx>
41 #include <vigra/imageinfo.hxx>
42 #include <hugin_utils/utils.h>
43 #include <exiv2/exiv2.hpp>
44 #include <lensdb/LensDB.h>
45 #include "Exiv2Helper.h"
46 
47 #ifdef __FreeBSD__
48 #define log2(x)        (log(x) / M_LN2)
49 #endif /* __FreeBSD__ */
50 
51 #include "ImageVariableTranslate.h"
52 
53 namespace HuginBase {
54 
resize(const vigra::Size2D & sz)55 void SrcPanoImage::resize(const vigra::Size2D & sz)
56 {
57         // TODO: check if images have the same orientation.
58         // calculate scaling ratio
59         const double scale = (double) sz.x / m_Size.getData().x;
60 
61         // center shift
62         m_RadialDistortionCenterShift.setData(m_RadialDistortionCenterShift.getData() * scale);
63         m_Shear.setData(m_Shear.getData() * scale);
64 
65         // crop
66         // ensure the scaled rectangle is inside the new image size
67         switch (m_CropMode.getData())
68         {
69             case NO_CROP:
70                 m_CropRect.setData(vigra::Rect2D(sz));
71                 break;
72             case CROP_RECTANGLE:
73                 {
74                     vigra::Rect2D rect(m_CropRect.getData());
75                     rect *= scale;
76                     rect &= vigra::Rect2D(sz);
77                     m_CropRect.setData(rect);
78                 }
79                 break;
80             case CROP_CIRCLE:
81                 {
82                     vigra::Rect2D rect(m_CropRect.getData());
83                     rect *= scale;
84                     m_CropRect.setData(rect);
85                 }
86                 break;
87         }
88 
89         m_Size.setData(sz);
90         // vignetting correction
91         m_RadialVigCorrCenterShift.setData(m_RadialVigCorrCenterShift.getData() *scale);
92         // resize masks
93         MaskPolygonVector scaledMasks=m_Masks.getData();
94         for(unsigned int i=0;i<scaledMasks.size();i++)
95             scaledMasks[i].scale(scale);
96         m_Masks.setData(scaledMasks);
97         scaledMasks.clear();
98         scaledMasks=m_ActiveMasks.getData();
99         for(unsigned int i=0;i<scaledMasks.size();i++)
100             scaledMasks[i].scale(scale);
101         m_ActiveMasks.setData(scaledMasks);
102 }
103 
horizontalWarpNeeded()104 bool SrcPanoImage::horizontalWarpNeeded()
105 {
106     switch (m_Projection.getData())
107     {
108         case PANORAMIC:
109         case EQUIRECTANGULAR:
110             if (m_HFOV.getData() == 360) return true;
111         case FULL_FRAME_FISHEYE:
112         case CIRCULAR_FISHEYE:
113         case RECTILINEAR:
114         case FISHEYE_ORTHOGRAPHIC:
115         case FISHEYE_STEREOGRAPHIC:
116         case FISHEYE_EQUISOLID:
117         case FISHEYE_THOBY:
118         default:
119             break;
120     }
121     return false;
122 }
123 
isInside(vigra::Point2D p,bool ignoreMasks) const124 bool SrcPanoImage::isInside(vigra::Point2D p, bool ignoreMasks) const
125 {
126     bool insideCrop=false;
127     switch(m_CropMode.getData()) {
128         case NO_CROP:
129         case CROP_RECTANGLE:
130             insideCrop = m_CropRect.getData().contains(p);
131             break;
132         case CROP_CIRCLE:
133         {
134             if (0 > p.x || 0 > p.y || p.x >= m_Size.getData().x || p.y >= m_Size.getData().y) {
135                 // outside image
136                 return false;
137             }
138             hugin_utils::FDiff2D cropCenter;
139             cropCenter.x = m_CropRect.getData().left() + m_CropRect.getData().width()/2.0;
140             cropCenter.y = m_CropRect.getData().top() + m_CropRect.getData().height()/2.0;
141             double radius2 = std::min(m_CropRect.getData().width()/2.0, m_CropRect.getData().height()/2.0);
142             radius2 = radius2 * radius2;
143             hugin_utils::FDiff2D pf = hugin_utils::FDiff2D(p) - cropCenter;
144             insideCrop = (radius2 > pf.x*pf.x+pf.y*pf.y );
145         }
146     }
147     if(insideCrop && !ignoreMasks)
148         return !(isInsideMasks(p));
149     else
150         return insideCrop;
151 }
152 
isCircularCrop() const153 bool SrcPanoImage::isCircularCrop() const
154 {
155     HuginBase::BaseSrcPanoImage::Projection projection=m_Projection.getData();
156     return (projection==CIRCULAR_FISHEYE || projection==FISHEYE_THOBY || projection==FISHEYE_ORTHOGRAPHIC);
157 };
158 
getCorrectTCA() const159 bool SrcPanoImage::getCorrectTCA() const
160 {
161     bool nr = (m_RadialDistortionRed.getData()[0] == 0.0 && m_RadialDistortionRed.getData()[1] == 0.0 &&
162                m_RadialDistortionRed.getData()[2] == 0.0 && m_RadialDistortionRed.getData()[3] == 1);
163     bool nb = (m_RadialDistortionBlue.getData()[0] == 0.0 && m_RadialDistortionBlue.getData()[1] == 0.0 &&
164                m_RadialDistortionBlue.getData()[2] == 0.0 && m_RadialDistortionBlue.getData()[3] == 1);
165     return !(nr && nb);
166 }
167 
168 
getRadialDistortionCenter() const169 hugin_utils::FDiff2D SrcPanoImage::getRadialDistortionCenter() const
170 {
171     return hugin_utils::FDiff2D(m_Size.getData()) / 2.0 + m_RadialDistortionCenterShift.getData();
172 }
173 
174 
getRadialVigCorrCenter() const175 hugin_utils::FDiff2D SrcPanoImage::getRadialVigCorrCenter() const
176 {
177     return (hugin_utils::FDiff2D(m_Size.getData()) - hugin_utils::FDiff2D(1, 1)) / 2.0 + m_RadialVigCorrCenterShift.getData();
178 }
179 
setCropMode(CropMode val)180 void SrcPanoImage::setCropMode(CropMode val)
181 {
182     m_CropMode.setData(val);
183     if (val == NO_CROP) {
184         m_CropRect.setData(vigra::Rect2D(m_Size.getData()));
185     }
186 }
187 
setSize(vigra::Size2D val)188 void SrcPanoImage::setSize(vigra::Size2D val)
189 {
190     m_Size.setData(val);
191     if (m_CropMode.getData() == NO_CROP) {
192         m_CropRect.setData(vigra::Rect2D(val));
193     }
194 }
195 
getExposure() const196 double SrcPanoImage::getExposure() const
197 { return 1.0/pow(2.0, m_ExposureValue.getData()); }
198 
setExposure(const double & val)199 void SrcPanoImage::setExposure(const double & val)
200 { m_ExposureValue.setData(log2(1/val)); }
201 
202 
operator ==(const BaseSrcPanoImage & other) const203 bool BaseSrcPanoImage::operator==(const BaseSrcPanoImage & other) const
204 {
205     DEBUG_TRACE("");
206     return (
207 #define image_variable( name, type, default_value ) \
208     m_##name.getData() == other.m_##name.getData() &&
209 #include "image_variables.h"
210 #undef image_variable
211     true // All the variable checks above end with && so we need this.
212     );
213 }
214 
215 // convinience functions to extract a set of variables
getVar(const std::string & code) const216 double SrcPanoImage::getVar(const std::string & code) const
217 {
218     DEBUG_TRACE("");
219     assert(!code.empty());
220 #define image_variable( name, type, default_value ) \
221     if (PTOVariableConverterFor##name::checkApplicability(code)) \
222         return PTOVariableConverterFor##name::getValueFromVariable(code, m_##name );\
223     else
224 #include "image_variables.h"
225 #undef image_variable
226     {// this is for the final else.
227         DEBUG_ERROR("Unknown variable " << code);
228     }
229     return 0;
230 }
231 
setVar(const std::string & code,double val)232 void SrcPanoImage::setVar(const std::string & code, double val)
233 {
234     DEBUG_TRACE("Var:" << code << " value: " << val);
235     assert(!code.empty());
236 #define image_variable( name, type, default_value ) \
237     if (PTOVariableConverterFor##name::checkApplicability(code)) \
238         {PTOVariableConverterFor##name::setValueFromVariable(code, m_##name, val);}\
239     else
240 #include "image_variables.h"
241 #undef image_variable
242     {// this is for the final else.
243         DEBUG_ERROR("Unknown variable " << code);
244     }
245 }
246 
getVariableMap() const247 VariableMap SrcPanoImage::getVariableMap() const
248 {
249     // make a variable map vector
250 
251     // fill variable map with details about this image.
252     // position
253     DEBUG_TRACE("");
254 
255     VariableMap vars;
256 #define image_variable( name, type, default_value ) \
257     PTOVariableConverterFor##name::addToVariableMap(m_##name, vars);
258 #include "image_variables.h"
259 #undef image_variable
260 
261     return vars;
262 }
263 
checkImageSizeKnown()264 bool SrcPanoImage::checkImageSizeKnown()
265 {
266     if(getWidth()<=0 || getHeight()<=0)
267     {
268         try
269         {
270             vigra::ImageImportInfo info(getFilename().c_str());
271             setSize(info.size());
272             // save pixeltype for later, so we don't need to parse the file again
273             const std::string pixeltype(info.getPixelType());
274             FileMetaData metaData = getFileMetadata();
275             metaData["pixeltype"] = pixeltype;
276             setFileMetadata(metaData);
277         }
278         catch(std::exception & )
279         {
280             return false;
281         }
282     };
283     return true;
284 
285 };
286 
readEXIF()287 bool SrcPanoImage::readEXIF()
288 {
289     std::string filename = getFilename();
290     double roll = 0;
291     // clear all old values
292     setFileMetadata(FileMetaData());
293     setExifExposureTime(0);
294     setExifAperture(0);
295     setExifExposureMode(0);
296     setExifISO(0);
297     setExifMake(std::string(""));
298     setExifModel(std::string(""));
299     setExifLens(std::string(""));
300     setExifOrientation(0);
301     setExifFocalLength(0);
302     setExifFocalLength35(0);
303     setExifCropFactor(0);
304     setExifDistance(0);
305     setExifDate(std::string(""));
306     setExifRedBalance(1);
307     setExifBlueBalance(1);
308 
309     if(!checkImageSizeKnown())
310     {
311         return false;
312     };
313 
314     // if width==2*height assume equirectangular image
315     if (getWidth() == 2 * getHeight())
316     {
317         FileMetaData metaData = getFileMetadata();
318         metaData["projection"] = "equirectangular";
319         metaData["HFOV"] = "360";
320         setFileMetadata(metaData);
321     };
322 
323 #if defined EXIV2_VERSION && EXIV2_TEST_VERSION(0,27,99)
324     Exiv2::Image::UniquePtr image;
325 #else
326     Exiv2::Image::AutoPtr image;
327 #endif
328     try {
329         image = Exiv2::ImageFactory::open(filename.c_str());
330     }
331     catch (const Exiv2::Error& e)
332     {
333         std::cerr << "Exiv2: Error reading metadata (" << e.what() << ")" << std::endl;
334         return false;
335     }
336 
337     try
338     {
339         image->readMetadata();
340     }
341     catch (const Exiv2::Error& e)
342     {
343         std::cerr << "Caught Exiv2 exception '" << e.what() << "' for file " << filename << std::endl;
344         return false;
345     }
346 
347     // look into XMP metadata
348     Exiv2::XmpData& xmpData = image->xmpData();
349     if (!xmpData.empty())
350     {
351         // we need to catch exceptions in case file does not contain any GPano tags
352         try
353         {
354             Exiv2::XmpData::iterator pos = xmpData.findKey(Exiv2::XmpKey("Xmp.GPano.ProjectionType"));
355             FileMetaData metaData = getFileMetadata();
356             if (pos != xmpData.end())
357             {
358                 if (hugin_utils::tolower(pos->toString()) == "equirectangular")
359                 {
360                     long croppedWidth = 0;
361                     long croppedHeight = 0;
362                     pos = xmpData.findKey(Exiv2::XmpKey("Xmp.GPano.CroppedAreaImageWidthPixels"));
363                     if (pos != xmpData.end())
364                     {
365                         croppedWidth = pos->toLong();
366                     }
367                     else
368                     {
369                         // tag is required
370                         throw std::logic_error("Required tag CroppedAreaImageWidthPixels missing");
371                     };
372                     pos = xmpData.findKey(Exiv2::XmpKey("Xmp.GPano.CroppedAreaImageHeightPixels"));
373                     if (pos != xmpData.end())
374                     {
375                         croppedHeight = pos->toLong();
376                     }
377                     else
378                     {
379                         // tag is required
380                         throw std::logic_error("Required tag CroppedAreaImageHeightPixels missing");
381                     };
382                     // check if sizes matches, if not ignore all tags
383                     if (getWidth() == croppedWidth && getHeight() == croppedHeight)
384                     {
385                         pos = xmpData.findKey(Exiv2::XmpKey("Xmp.GPano.FullPanoWidthPixels"));
386                         double hfov = 0;
387                         if (pos != xmpData.end())
388                         {
389                             hfov = 360 * croppedWidth / (double)pos->toLong();
390                         }
391                         else
392                         {
393                             // tag is required
394                             throw std::logic_error("Required tag FullPanoWidthPixels missing");
395                         };
396                         long fullHeight = 0;
397                         pos = xmpData.findKey(Exiv2::XmpKey("Xmp.GPano.FullPanoHeightPixels"));
398                         if (pos != xmpData.end())
399                         {
400                             fullHeight = pos->toLong();
401                         }
402                         else
403                         {
404                             // tag is required
405                             throw std::logic_error("Required tag FullPanoHeightPixels missing");
406                         };
407                         long cropTop = 0;
408                         pos = xmpData.findKey(Exiv2::XmpKey("Xmp.GPano.CroppedAreaTopPixels"));
409                         if (pos != xmpData.end())
410                         {
411                             cropTop = pos->toLong();
412                         }
413                         else
414                         {
415                             // tag is required
416                             throw std::logic_error("Required tag CroppedAreaTopPixels missing");
417                         };
418 
419                         // all found, remember for later
420                         metaData["projection"] = "equirectangular";
421                         metaData["HFOV"] = hugin_utils::doubleToString(hfov, 3);
422                         metaData["e"] = hugin_utils::doubleToString(-cropTop - ((getHeight() - fullHeight) / 2.0), 4);
423                         setFileMetadata(metaData);
424                     };
425                 };
426             };
427         }
428         catch (std::exception& e)
429         {
430             // just to catch error when image contains no GPano tags
431             std::cerr << "Error reading GPano tags from " << filename << "(" << e.what() << ")" << std::endl;
432         };
433     };
434 
435     Exiv2::ExifData &exifData = image->exifData();
436     if (exifData.empty()) {
437         std::cerr << "Unable to read EXIF data from opened file:" << filename << std::endl;
438         return !getFileMetadata().empty();
439     }
440 
441     setExifExposureTime(Exiv2Helper::getExiv2ValueDouble(exifData, Exiv2::exposureTime(exifData)));
442     setExifAperture(Exiv2Helper::getExiv2ValueDouble(exifData, Exiv2::fNumber(exifData)));
443 
444     //read exposure mode
445     setExifExposureMode(Exiv2Helper::getExiv2ValueLong(exifData, "Exif.Photo.ExposureMode"));
446 
447     // read ISO from EXIF or makernotes
448     setExifISO(Exiv2Helper::getExiv2ValueDouble(exifData, Exiv2::isoSpeed(exifData)));
449 
450     setExifMake(Exiv2Helper::getExiv2ValueString(exifData, Exiv2::make(exifData)));
451     setExifModel(Exiv2Helper::getExiv2ValueString(exifData, Exiv2::model(exifData)));
452 
453     //reading lens
454     setExifLens(Exiv2Helper::getLensName(exifData));
455 
456     long orientation = Exiv2Helper::getExiv2ValueLong(exifData, "Exif.Image.Orientation");
457     if (orientation>0 && trustExivOrientation())
458     {
459         switch (orientation) {
460             case 3:  // rotate 180
461                 roll = 180;
462                 break;
463             case 6: // rotate 90
464                 roll = 90;
465                 break;
466             case 8: // rotate 270
467                 roll = 270;
468                 break;
469             default:
470                 break;
471         }
472     }
473 
474     long pixXdim = Exiv2Helper::getExiv2ValueLong(exifData,"Exif.Photo.PixelXDimension");
475     long pixYdim = Exiv2Helper::getExiv2ValueLong(exifData,"Exif.Photo.PixelYDimension");
476 
477     if (pixXdim !=0 && pixYdim !=0 )
478     {
479         double ratioExif = pixXdim/(double)pixYdim;
480         double ratioImage = getWidth()/(double)getHeight();
481         if (fabs( ratioExif - ratioImage) > 0.1)
482         {
483             // Image has been modified without adjusting exif tags.
484             // Assume user has rotated to upright pose
485             roll = 0;
486         }
487     }
488     // save for later
489     setExifOrientation(roll);
490 
491     double cropFactor = 0;
492     DEBUG_DEBUG("cropFactor: " << cropFactor);
493 
494     float eFocalLength = Exiv2Helper::getExiv2ValueDouble(exifData, Exiv2::focalLength(exifData));
495     float eFocalLength35 = Exiv2Helper::getExiv2ValueLong(exifData,"Exif.Photo.FocalLengthIn35mmFilm");
496     float focalLength=0;
497     //The various methods to detmine crop factor
498     if (eFocalLength35 > 0 && eFocalLength > 0)
499     {
500         cropFactor = eFocalLength35 / eFocalLength;
501         focalLength = eFocalLength;
502     }
503     else
504     {
505         if (eFocalLength35 > 0)
506         {
507             // 35 mm equiv focal length available, crop factor unknown.
508             // do not ask for crop factor, assume 1.  Probably a full frame sensor
509             cropFactor = 1;
510             focalLength = eFocalLength35;
511         }
512         else
513         {
514             focalLength = (eFocalLength > 0) ? eFocalLength : 0;
515             // alternative way to calculate crop factor
516             cropFactor = Exiv2Helper::getCropFactor(exifData, getWidth(), getHeight());
517             // check result
518             if (cropFactor < 0.1)
519             {
520                 cropFactor = 0;
521             };
522         };
523     };
524     // check results, if 35 mm focal length is too small reset crop factor to 0
525     if (focalLength > 0 && cropFactor > 0 && focalLength*cropFactor < 6)
526     {
527         cropFactor = 0;
528         // check alternative way to calculate crop factor, e.g. when focal length and focal length in 35 mm are given
529         // and are the same, but not a full frame camera
530         const double newCropFactor = Exiv2Helper::getCropFactor(exifData, getWidth(), getHeight());
531         if (newCropFactor > 0)
532         {
533             if (focalLength*newCropFactor >= 6)
534             {
535                 cropFactor = newCropFactor;
536             }
537         };
538     };
539 
540     setExifFocalLength(focalLength);
541     setExifFocalLength35(eFocalLength35);
542     setExifCropFactor(cropFactor);
543 
544     setExifDistance(Exiv2Helper::getExiv2ValueDouble(exifData, Exiv2::subjectDistance(exifData)));
545     setExifDate(Exiv2Helper::getExiv2ValueString(exifData, "Exif.Photo.DateTimeOriginal"));
546 
547     double redBalance, blueBalance;
548     Exiv2Helper::readRedBlueBalance(exifData, redBalance, blueBalance);
549     setExifRedBalance(redBalance);
550     setExifBlueBalance(blueBalance);
551 
552     DEBUG_DEBUG("Results for:" << filename);
553     DEBUG_DEBUG("Focal Length: " << getExifFocalLength());
554     DEBUG_DEBUG("Crop Factor:  " << getCropFactor());
555     DEBUG_DEBUG("Roll:         " << getExifOrientation());
556 
557     return true;
558 }
559 
applyEXIFValues(bool applyEVValue)560 bool SrcPanoImage::applyEXIFValues(bool applyEVValue)
561 {
562     setRoll(getExifOrientation());
563     if(applyEVValue)
564     {
565         setExposureValue(calcExifExposureValue());
566     };
567     // special handling for GPano tags
568     FileMetaData metaData = getFileMetadata();
569     if (!metaData.empty())
570     {
571         FileMetaData::const_iterator pos = metaData.find("projection");
572         if (pos != metaData.end())
573         {
574             if (pos->second == "equirectangular")
575             {
576                 pos = metaData.find("HFOV");
577                 if (pos != metaData.end())
578                 {
579                     double hfov = 0;
580                     hugin_utils::stringToDouble(pos->second, hfov);
581                     double e = 0;
582                     pos = metaData.find("e");
583                     if (pos != metaData.end())
584                     {
585                         hugin_utils::stringToDouble(pos->second, e);
586                     };
587                     if (hfov != 0)
588                     {
589                         setProjection(EQUIRECTANGULAR);
590                         setHFOV(hfov);
591                         setCropFactor(1.0);
592                         hugin_utils::FDiff2D p = getRadialDistortionCenterShift();
593                         p.y = e;
594                         setRadialDistortionCenterShift(p);
595                         return true;
596                     };
597                 };
598             };
599         };
600     };
601     double cropFactor=getExifCropFactor();
602     double focalLength=getExifFocalLength();
603     if(cropFactor>0.1)
604     {
605         setCropFactor(cropFactor);
606     };
607     if (focalLength > 0 && cropFactor > 0.1)
608     {
609         setHFOV(calcHFOV(getProjection(), focalLength, cropFactor, getSize()));
610         DEBUG_DEBUG("HFOV:         " << getHFOV());
611         return true;
612     }
613     else
614     {
615         return false;
616     }
617 }
618 
readCropfactorFromDB()619 bool SrcPanoImage::readCropfactorFromDB()
620 {
621     // finally search in lens database
622     if(getCropFactor()<0.1 && !getExifMake().empty() && !getExifModel().empty())
623     {
624         double dbCrop=0;
625         if(LensDB::LensDB::GetSingleton().GetCropFactor(getExifMake(),getExifModel(),dbCrop))
626         {
627             if(dbCrop>0.1)
628             {
629                 setCropFactor(dbCrop);
630                 setExifCropFactor(dbCrop);
631                 if (getExifFocalLength() > 0)
632                 {
633                     setHFOV(calcHFOV(getProjection(), getExifFocalLength(), dbCrop, getSize()));
634                 };
635                 return true;
636             };
637         };
638     };
639     return false;
640 };
641 
getDBLensName() const642 std::string SrcPanoImage::getDBLensName() const
643 {
644     std::string lens(getExifLens());
645     if (!lens.empty())
646     {
647         return lens;
648     }
649     lens = getExifMake();
650     if (!lens.empty())
651     {
652         if (!getExifModel().empty())
653         {
654             lens.append("|");
655             lens.append(getExifModel());
656             return lens;
657         };
658     };
659     return std::string();
660 };
661 
isFisheye(const BaseSrcPanoImage::Projection & proj)662 bool isFisheye(const BaseSrcPanoImage::Projection& proj)
663 {
664     switch (proj)
665     {
666         case BaseSrcPanoImage::CIRCULAR_FISHEYE:
667         case BaseSrcPanoImage::FULL_FRAME_FISHEYE:
668         case BaseSrcPanoImage::FISHEYE_ORTHOGRAPHIC:
669         case BaseSrcPanoImage::FISHEYE_STEREOGRAPHIC:
670         case BaseSrcPanoImage::FISHEYE_EQUISOLID:
671         case BaseSrcPanoImage::FISHEYE_THOBY:
672             return true;
673         default:
674             return false;
675     };
676     return false;
677 };
678 
readProjectionFromDB(const bool ignoreFovRectilinear)679 bool SrcPanoImage::readProjectionFromDB(const bool ignoreFovRectilinear)
680 {
681     bool success=false;
682     double oldFocal = 0;
683     const std::string lensname = getDBLensName();
684     const double focal = getExifFocalLength();
685     if (!lensname.empty())
686     {
687         const LensDB::LensDB& lensDB=LensDB::LensDB::GetSingleton();
688         Projection dbProjection;
689         if(lensDB.GetProjection(lensname, dbProjection))
690         {
691             oldFocal = calcFocalLength(getProjection(), getHFOV(), getCropFactor(), getSize());
692             setProjection(dbProjection);
693             success = true;
694         };
695         if (focal>0)
696         {
697             double fov;
698             // read fov only for non rectilinear images
699             // for these relay on the EXIF data, because often user manage to store
700             // wrong values in the database, so ignore them for rectilinear images
701             if ((getProjection() != RECTILINEAR || !ignoreFovRectilinear) && lensDB.GetFov(lensname, focal, fov))
702             {
703                 // calculate FOV for given image, take different aspect ratios into account
704                 const double newFocal = calcFocalLength(getProjection(), fov, getCropFactor(), vigra::Size2D(3000, 2000));
705                 const double newFov = calcHFOV(getProjection(), newFocal, getCropFactor(), getSize());
706                 setHFOV(newFov);
707                 oldFocal = 0;
708                 // for fisheye lenses read also automatically the distortions parameters from lens db
709                 // because fisheye often don't follow exactly one of the projection models and need
710                 // the distortion parameters to model the real projection of the used fisheye lens
711                 if(isFisheye(getProjection()))
712                 {
713                     std::vector<double> dist;
714                     if (lensDB.GetDistortion(lensname, focal, dist))
715                     {
716                         if (dist.size() == 3)
717                         {
718                             dist.push_back(1.0 - dist[0] - dist[1] - dist[2]);
719                             setRadialDistortion(dist);
720                         };
721                     };
722                 };
723             };
724             vigra::Rect2D dbCropRect;
725             if (lensDB.GetCrop(lensname, focal, getSize(), dbCropRect))
726             {
727                 setCropMode(isCircularCrop() ? CROP_CIRCLE : CROP_RECTANGLE);
728                 setCropRect(dbCropRect);
729             };
730         };
731         // updated fov after changing projection, if not already done with value from database
732         if (success && oldFocal > 0)
733         {
734             const double newFov = calcHFOV(getProjection(), oldFocal, getCropFactor(), getSize());
735             setHFOV(newFov);
736         };
737     };
738     // store information about reading from database in FileMetadata map
739     if (success)
740     {
741         FileMetaData metaData = getFileMetadata();
742         metaData["readProjectionFromDB"] = "true";
743         setFileMetadata(metaData);
744     };
745 
746     return success;
747 };
748 
readDistortionFromDB()749 bool SrcPanoImage::readDistortionFromDB()
750 {
751     const std::string lensname = getDBLensName();
752     const double focal = getExifFocalLength();
753     if (!lensname.empty() && focal > 0)
754     {
755         const LensDB::LensDB& lensDB=LensDB::LensDB::GetSingleton();
756         std::vector<double> dist;
757         if(lensDB.GetDistortion(lensname, focal, dist))
758         {
759             if(dist.size()==3)
760             {
761                 dist.push_back(1.0-dist[0]-dist[1]-dist[2]);
762                 setRadialDistortion(dist);
763                 return true;
764             };
765         };
766     };
767     return false;
768 };
769 
readVignettingFromDB()770 bool SrcPanoImage::readVignettingFromDB()
771 {
772     const std::string lensname = getDBLensName();
773     const double focal = getExifFocalLength();
774     if (!lensname.empty() && focal > 0)
775     {
776         const LensDB::LensDB& lensDB=LensDB::LensDB::GetSingleton();
777         std::vector<double> vig;
778         if(lensDB.GetVignetting(lensname, focal, getExifAperture(), getExifDistance(), vig))
779         {
780             if (vig.size() == 4)
781             {
782                 setRadialVigCorrCoeff(vig);
783                 return true;
784             };
785         };
786     };
787     return false;
788 };
789 
calcHFOV(SrcPanoImage::Projection proj,double fl,double crop,vigra::Size2D imageSize)790 double SrcPanoImage::calcHFOV(SrcPanoImage::Projection proj, double fl, double crop, vigra::Size2D imageSize)
791 {
792     // calculate diagonal of film
793     double d = sqrt(36.0*36.0 + 24.0*24.0) / crop;
794     double r = (double)imageSize.x / imageSize.y;
795 
796     // calculate the sensor width and height that fit the ratio
797     // the ratio is determined by the size of our image.
798     hugin_utils::FDiff2D sensorSize;
799     sensorSize.x = d / sqrt(1 + 1/(r*r));
800     sensorSize.y = sensorSize.x / r;
801 
802     double hfov = 360;
803 
804     switch (proj) {
805         case SrcPanoImage::RECTILINEAR:
806             hfov = 2*atan((sensorSize.x/2.0)/fl)  * 180.0/M_PI;
807             break;
808         case SrcPanoImage::CIRCULAR_FISHEYE:
809         case SrcPanoImage::FULL_FRAME_FISHEYE:
810             hfov = sensorSize.x / fl * 180/M_PI;
811             break;
812         case SrcPanoImage::EQUIRECTANGULAR:
813         case SrcPanoImage::PANORAMIC:
814             hfov = (sensorSize.x / fl) / M_PI * 180;
815             break;
816         case SrcPanoImage::FISHEYE_ORTHOGRAPHIC:
817             {
818                 double val=(sensorSize.x/2.0)/fl;
819                 double n;
820                 double frac=modf(val, &n);
821                 hfov = 2 * asin(frac) * 180.0/M_PI + n * 180.0;
822             }
823             break;
824         case SrcPanoImage::FISHEYE_EQUISOLID:
825             hfov = 4 * asin(std::min<double>(1.0, (sensorSize.x/4.0)/fl)) * 180.0/M_PI;
826             break;
827         case SrcPanoImage::FISHEYE_STEREOGRAPHIC:
828             hfov = 4 * atan((sensorSize.x/4.0)/fl) * 180.0/M_PI;
829             break;
830         case SrcPanoImage::FISHEYE_THOBY:
831             hfov = 2 * asin(std::min<double>(1.0, sensorSize.x/(2.0*fl*1.47))) * 180.0/M_PI/0.713;
832             break;
833         default:
834             hfov = 360;
835             // TODO: add formulas for other projections
836             DEBUG_WARN("Focal length calculations only supported with rectilinear and fisheye images");
837     }
838     return hfov;
839 }
840 
calcFocalLength(SrcPanoImage::Projection proj,double hfov,double crop,vigra::Size2D imageSize)841 double SrcPanoImage::calcFocalLength(SrcPanoImage::Projection proj, double hfov, double crop, vigra::Size2D imageSize)
842 {
843     // calculate diagonal of film
844     double d = sqrt(36.0*36.0 + 24.0*24.0) / crop;
845     double r = (double)imageSize.x / imageSize.y;
846 
847     // calculate the sensor width and height that fit the ratio
848     // the ratio is determined by the size of our image.
849     hugin_utils::FDiff2D sensorSize;
850     sensorSize.x = d / sqrt(1 + 1/(r*r));
851     sensorSize.y = sensorSize.x / r;
852 
853     switch (proj)
854     {
855         case SrcPanoImage::RECTILINEAR:
856             return (sensorSize.x/2.0) / tan(hfov/180.0*M_PI/2);
857             break;
858         case SrcPanoImage::CIRCULAR_FISHEYE:
859         case SrcPanoImage::FULL_FRAME_FISHEYE:
860             // same projection equation for both fisheye types,
861             // assume equal area projection.
862             return sensorSize.x / (hfov/180*M_PI);
863             break;
864         case SrcPanoImage::EQUIRECTANGULAR:
865         case SrcPanoImage::PANORAMIC:
866             return  (sensorSize.x / (hfov/180*M_PI));
867             break;
868         case SrcPanoImage::FISHEYE_ORTHOGRAPHIC:
869             {
870                 int t=(int)ceil((hfov-180)/360);
871                 return (sensorSize.x /2.0) / (2 * t + pow ( -1.0, t) * sin(hfov/180.0*M_PI/2.0));
872             };
873         case SrcPanoImage::FISHEYE_STEREOGRAPHIC:
874             return (sensorSize.x/4.0) / tan(hfov/180.0*M_PI/4.0);
875         case SrcPanoImage::FISHEYE_EQUISOLID:
876             return (sensorSize.x/4.0) / sin(hfov/180.0*M_PI/4.0);
877         case SrcPanoImage::FISHEYE_THOBY:
878             return (sensorSize.x/2.0) / (1.47 * sin(hfov/180.0*M_PI * 0.713 / 2.0));
879         default:
880             // TODO: add formulas for other projections
881             DEBUG_WARN("Focal length calculations only supported with rectilinear and fisheye images");
882             return 0;
883     }
884 }
885 
calcCropFactor(SrcPanoImage::Projection proj,double hfov,double focalLength,vigra::Size2D imageSize)886 double SrcPanoImage::calcCropFactor(SrcPanoImage::Projection proj, double hfov, double focalLength, vigra::Size2D imageSize)
887 {
888     // calculate diagonal of film
889     double r = (double)imageSize.x / imageSize.y;
890 
891     double x = 36;
892     switch (proj)
893     {
894         case SrcPanoImage::RECTILINEAR:
895             x = focalLength * tan(hfov/180.0*M_PI/2);
896             break;
897         case SrcPanoImage::CIRCULAR_FISHEYE:
898         case SrcPanoImage::FULL_FRAME_FISHEYE:
899         case SrcPanoImage::EQUIRECTANGULAR:
900         case SrcPanoImage::FISHEYE_ORTHOGRAPHIC:
901         case SrcPanoImage::FISHEYE_STEREOGRAPHIC:
902         case SrcPanoImage::FISHEYE_EQUISOLID:
903         case SrcPanoImage::FISHEYE_THOBY:
904         case SrcPanoImage::PANORAMIC:
905             // same projection equation for both fisheye types,
906             // assume equal area projection.
907             x = focalLength * (hfov/180*M_PI);
908             break;
909         default:
910             // TODO: add formulas for other projections
911             DEBUG_WARN("Focal length calculations only supported with rectilinear and fisheye images");
912             return 0;
913     }
914     // diagonal of sensor
915     double diag = x * sqrt(1+ 1/(r*r));
916     return sqrt(36.0*36.0 + 24.0*24.0) / diag;
917 }
918 
calcExifExposureValue()919 double SrcPanoImage::calcExifExposureValue()
920 {
921     double ev=0;
922     double photoFNumber=getExifAperture();
923     if(photoFNumber==0)
924     {
925         //if no F-number was found in EXIF data assume a f stop of 3.5 to get
926         //a reasonable ev value if shutter time, e. g. for manual lenses is found
927         photoFNumber=3.5;
928     };
929     if (getExifExposureTime() > 0)
930     {
931         double gain = 1;
932         if (getExifISO()> 0)
933         {
934             gain = getExifISO() / 100.0;
935         }
936         ev = log2(photoFNumber * photoFNumber / (gain * getExifExposureTime()));
937     };
938     return ev;
939 };
940 
updateFocalLength(double newFocalLength)941 void SrcPanoImage::updateFocalLength(double newFocalLength)
942 {
943     double newHFOV=calcHFOV(getProjection(),newFocalLength,getCropFactor(),getSize());
944     if(newHFOV!=0)
945     {
946         setHFOV(newHFOV);
947     };
948 };
949 
updateCropFactor(double focalLength,double newCropFactor)950 void SrcPanoImage::updateCropFactor(double focalLength, double newCropFactor)
951 {
952     double newHFOV=calcHFOV(getProjection(),focalLength,newCropFactor,getSize());
953     if(newHFOV!=0)
954     {
955         setHFOV(newHFOV);
956     };
957     setCropFactor(newCropFactor);
958 };
959 
960 // mask handling stuff
addMask(MaskPolygon newMask)961 void SrcPanoImage::addMask(MaskPolygon newMask)
962 {
963     MaskPolygonVector newMasks=m_Masks.getData();
964     newMasks.push_back(newMask);
965     setMasks(newMasks);
966 };
967 
addActiveMask(MaskPolygon newMask)968 void SrcPanoImage::addActiveMask(MaskPolygon newMask)
969 {
970     MaskPolygonVector newMasks=m_ActiveMasks.getData();
971     newMasks.push_back(newMask);
972     setActiveMasks(newMasks);
973 };
974 
clearActiveMasks()975 void SrcPanoImage::clearActiveMasks()
976 {
977     MaskPolygonVector emptyMaskVector;
978     m_ActiveMasks.setData(emptyMaskVector);
979 };
980 
hasMasks() const981 bool SrcPanoImage::hasMasks() const
982 {
983     return !m_Masks.getData().empty();
984 };
985 
hasPositiveMasks() const986 bool SrcPanoImage::hasPositiveMasks() const
987 {
988     MaskPolygonVector masks=m_Masks.getData();
989     if(!masks.empty())
990     {
991         for(unsigned int i=0;i<masks.size();i++)
992         {
993             if(masks[i].isPositive())
994             {
995                 return true;
996             };
997         };
998     };
999     return false;
1000 };
1001 
hasActiveMasks() const1002 bool SrcPanoImage::hasActiveMasks() const
1003 {
1004     return !m_ActiveMasks.getData().empty();
1005 };
1006 
printMaskLines(std::ostream & o,unsigned int newImgNr) const1007 void SrcPanoImage::printMaskLines(std::ostream &o, unsigned int newImgNr) const
1008 {
1009     if(!m_Masks.getData().empty())
1010         for(unsigned int i=0;i<m_Masks.getData().size();i++)
1011             m_Masks.getData()[i].printPolygonLine(o, newImgNr);
1012 };
1013 
changeMaskType(unsigned int index,HuginBase::MaskPolygon::MaskType newType)1014 void SrcPanoImage::changeMaskType(unsigned int index, HuginBase::MaskPolygon::MaskType newType)
1015 {
1016     if(index<m_Masks.getData().size())
1017     {
1018         MaskPolygonVector editedMasks=m_Masks.getData();
1019         editedMasks[index].setMaskType(newType);
1020         m_Masks.setData(editedMasks);
1021     };
1022 };
1023 
deleteMask(unsigned int index)1024 void SrcPanoImage::deleteMask(unsigned int index)
1025 {
1026     if(index<m_Masks.getData().size())
1027     {
1028         MaskPolygonVector oldMasks=m_Masks.getData();
1029         oldMasks.erase(oldMasks.begin()+index);
1030         m_Masks.setData(oldMasks);
1031     };
1032 };
1033 
deleteAllMasks()1034 void SrcPanoImage::deleteAllMasks()
1035 {
1036     MaskPolygonVector emptyMaskVector;
1037     m_Masks.setData(emptyMaskVector);
1038 };
1039 
isInsideMasks(vigra::Point2D p) const1040 bool SrcPanoImage::isInsideMasks(vigra::Point2D p) const
1041 {
1042     if(!hasActiveMasks())
1043         return false;
1044     bool insideMask=false;
1045     unsigned int i=0;
1046     while(!insideMask && i<m_ActiveMasks.getData().size())
1047     {
1048         insideMask=m_ActiveMasks.getData()[i].isInside(p);
1049         i++;
1050     };
1051     return insideMask;
1052 };
1053 
1054 /**
1055  * Decides if the Exiv Orientation Tag of an images is plausible.
1056  * Current checks:
1057  * - If width is smaller than height, image is probably already rotated, tag may be wrong.
1058  * @return true if plausible.
1059  */
trustExivOrientation()1060 bool SrcPanoImage::trustExivOrientation()
1061 {
1062     if(getSize().width() < getSize().height())
1063         return false;
1064 
1065     return true;
1066 }
1067 
getExifDateTime(struct tm * datetime) const1068 const int SrcPanoImage::getExifDateTime(struct tm* datetime) const
1069 {
1070     //initialize struct
1071     std::memset(datetime, 0x0, sizeof(*datetime));
1072     //ignore daylight saving flag because it is not saved in EXIF date time format
1073     datetime->tm_isdst=-1;
1074     return Exiv2::exifTime(m_ExifDate.getData().c_str(),datetime);
1075 };
1076 
1077 } // namespace
1078