1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2012-02-04
7  * Description : a tool to create panorama by fusion of several images.
8  *               This type is based on pto file format described here:
9  *               hugin.sourceforge.net/docs/nona/nona.txt, and
10  *               on pto files produced by Hugin's tools.
11  *
12  * Copyright (C) 2012-2015 by Benjamin Girault <benjamin dot girault at gmail dot com>
13  *
14  * This program is free software; you can redistribute it
15  * and/or modify it under the terms of the GNU General
16  * Public License as published by the Free Software Foundation;
17  * either version 2, or (at your option) any later version.
18  *
19  * This program 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
22  * GNU General Public License for more details.
23  *
24  * ============================================================ */
25 
26 #include "ptotype.h"
27 
28 // C includes
29 
30 #include <math.h>
31 
32 // Qt includes
33 
34 #include <QFile>
35 
36 // Local includes
37 
38 #include "digikam_debug.h"
39 
40 namespace Digikam
41 {
42 
createFile(const QString & filepath)43 bool PTOType::createFile(const QString& filepath)
44 {
45     QFile file(filepath);
46 
47     if (!file.open(QFile::WriteOnly | QFile::Truncate))
48     {
49         qCDebug(DIGIKAM_GENERAL_LOG) << "Cannot open " << filepath << " to write the pto file";
50         return false;
51     }
52 
53     QTextStream out(&file);
54     out.setRealNumberPrecision(15);
55 
56     // First, the pano line
57 
58     if (project.previousComments.size() > 0)
59     {
60         out << project.previousComments.join(QLatin1Char('\n')) << endl;
61     }
62 
63     out << "p";
64     out << " f" << project.projection;
65 
66     if (project.size.width() > 0)
67     {
68         out << " w" << project.size.width();
69     }
70 
71     if (project.size.height() > 0)
72     {
73         out << " h" << project.size.height();
74     }
75 
76     if (project.fieldOfView > 0)
77     {
78         out << " v" << project.fieldOfView;
79     }
80 
81     out << " k" << project.photometricReferenceId;
82     out << " E" << project.exposure;
83     out << " R" << project.hdr;
84 
85     switch (project.bitDepth)
86     {
87         case Project::UINT16:
88             out << " T\"UINT16\"";
89             break;
90 
91         case Project::FLOAT:
92             out << " T\"FLOAT\"";
93             break;
94 
95         default:
96             break;
97     }
98 
99     if (project.crop.height() > 1 && project.crop.width() > 1)
100     {
101         out << " S";
102         out << project.crop.left();
103         out <<  "," << project.crop.right();
104         out <<  "," << project.crop.top();
105         out <<  "," << project.crop.bottom();
106     }
107 
108     out << " n\"";
109 
110     switch (project.fileFormat.fileType)
111     {
112         case Project::FileFormat::PNG:
113         {
114             out << "PNG";
115             break;
116         }
117 
118         case Project::FileFormat::JPEG:
119         {
120             out <<  "JPEG";
121             out << " q" << project.fileFormat.quality;
122             break;
123         }
124 
125         case Project::FileFormat::TIFF:
126         {
127             out << "TIFF c:" << project.fileFormat.compressionMethod;
128             break;
129         }
130 
131         case Project::FileFormat::TIFF_m:
132         case Project::FileFormat::TIFF_multilayer:
133         {
134             if (project.fileFormat.fileType == Project::FileFormat::TIFF_multilayer)
135             {
136                 out << "TIFF_multilayer";
137             }
138             else
139             {
140                 out << "TIFF_m";
141             }
142 
143             out << " c:";
144 
145             switch (project.fileFormat.compressionMethod)
146             {
147                 case Project::FileFormat::PANO_NONE:
148                     out << "PANO_NONE";
149                     break;
150 
151                 case Project::FileFormat::LZW:
152                     out << "LZW";
153                     break;
154 
155                 case Project::FileFormat::DEFLATE:
156                     out << "DEFLATE";
157                     break;
158             }
159 
160             if (project.fileFormat.savePositions)
161             {
162                 out << " p1";
163             }
164 
165             if (project.fileFormat.cropped)
166             {
167                 out << " r:CROP";
168             }
169 
170             break;
171         }
172 
173         default:
174         {
175             qCCritical(DIGIKAM_GENERAL_LOG) << "Unknown file format for pto file generation!";
176             file.close();
177 
178             return false;
179         }
180     }
181 
182     out << "\"";
183     out << project.unmatchedParameters.join(QLatin1Char(' ')) << endl;
184 
185     // Second, the stitcher line
186 
187     if (stitcher.previousComments.size() > 0)
188     {
189         out << stitcher.previousComments.join(QLatin1Char('\n')) << endl;
190     }
191 
192     out << "m";
193     out << " g" << stitcher.gamma;
194     out << " i" << (int) stitcher.interpolator;
195 
196     if (stitcher.speedUp != Stitcher::SLOW)
197     {
198         out << " f" << 2 - ((int) stitcher.speedUp);
199     }
200 
201     out << " m" << stitcher.huberSigma;
202     out << " p" << stitcher.photometricHuberSigma;
203     out << stitcher.unmatchedParameters.join(QLatin1Char(' ')) << endl;
204 
205     // Third, the images
206     // Note: the order is very important here
207 
208     for (int id = 0 ; id < images.size() ; ++id)
209     {
210         const Image &image = images[id];
211 
212         if (image.previousComments.size() > 0)
213         {
214             out << image.previousComments.join(QLatin1Char('\n')) << endl;
215         }
216 
217         out << "i";
218         out << " w" << image.size.width();
219         out << " h" << image.size.height();
220         out << " f" << (int) image.lensProjection;
221 
222         if (image.fieldOfView.referenceId >= 0 || image.fieldOfView.value > 0)
223         {
224             out << " v" << image.fieldOfView;
225         }
226 
227         out << " Ra" << image.photometricEMoRA;
228         out << " Rb" << image.photometricEMoRB;
229         out << " Rc" << image.photometricEMoRC;
230         out << " Rd" << image.photometricEMoRD;
231         out << " Re" << image.photometricEMoRE;
232         out << " Eev" << image.exposure;
233         out << " Er" << image.whiteBalanceRed;
234         out << " Eb" << image.whiteBalanceBlue;
235         out << " r" << image.roll;
236         out << " p" << image.pitch;
237         out << " y" << image.yaw;
238         out << " TrX" << image.mosaicCameraPositionX;
239         out << " TrY" << image.mosaicCameraPositionY;
240         out << " TrZ" << image.mosaicCameraPositionZ;
241 
242         if (version == V2014)
243         {
244             out << " Tpy" << image.mosaicProjectionPlaneYaw;
245             out << " Tpp" << image.mosaicProjectionPlanePitch;
246         }
247 
248         out << " j" << image.stackNumber;
249         out << " a" << image.lensBarrelCoefficientA;
250         out << " b" << image.lensBarrelCoefficientB;
251         out << " c" << image.lensBarrelCoefficientC;
252         out << " d" << image.lensCenterOffsetX;
253         out << " e" << image.lensCenterOffsetY;
254         out << " g" << image.lensShearX;
255         out << " t" << image.lensShearY;
256 
257         const Image* imageVM = &image;
258 
259         if (image.vignettingMode.referenceId >= 0)
260         {
261             imageVM = &images[image.vignettingMode.referenceId];
262         }
263 
264         if (((int)imageVM->vignettingMode.value) & ((int)Image::RADIAL))
265         {
266             out << " Va" << image.vignettingCorrectionI;
267             out << " Vb" << image.vignettingCorrectionJ;
268             out << " Vc" << image.vignettingCorrectionK;
269             out << " Vd" << image.vignettingCorrectionL;
270         }
271         else
272         {
273             out << " Vf" << image.vignettingFlatfieldImageName;
274         }
275 
276         out << " Vx" << image.vignettingOffsetX;
277         out << " Vy" << image.vignettingOffsetY;
278         out << " Vm" << image.vignettingMode;
279         out << image.unmatchedParameters.join(QLatin1Char(' '));
280         out << " n\"" << image.fileName << "\"";
281         out << endl;
282     }
283 
284     // Fourth, the variable to optimize
285 
286     for (int id = 0 ; id < images.size() ; ++id)
287     {
288         const Image& image = images[id];
289 
290         foreach (Optimization optim, image.optimizationParameters)
291         {
292             if (optim.previousComments.size() > 0)
293             {
294                 out << optim.previousComments.join(QLatin1Char('\n')) << endl;
295             }
296 
297             out << "v ";
298 
299             switch (optim.parameter)
300             {
301                 case Optimization::LENSA:
302                     out << 'a';
303                     break;
304 
305                 case Optimization::LENSB:
306                     out << 'b';
307                     break;
308 
309                 case Optimization::LENSC:
310                     out << 'c';
311                     break;
312 
313                 case Optimization::LENSD:
314                     out << 'd';
315                     break;
316 
317                 case Optimization::LENSE:
318                     out << 'e';
319                     break;
320 
321                 case Optimization::LENSHFOV:
322                     out << 'v';
323                     break;
324 
325                 case Optimization::LENSYAW:
326                     out << 'y';
327                     break;
328 
329                 case Optimization::LENSPITCH:
330                     out << 'p';
331                     break;
332 
333                 case Optimization::LENSROLL:
334                     out << 'r';
335                     break;
336 
337                 case Optimization::EXPOSURE:
338                     out << "Eev";
339                     break;
340 
341                 case Optimization::WBR:
342                     out << "Er";
343                     break;
344 
345                 case Optimization::WBB:
346                     out << "Eb";
347                     break;
348 
349                 case Optimization::VA:
350                     out << "Va";
351                     break;
352 
353                 case Optimization::VB:
354                     out << "Vb";
355                     break;
356 
357                 case Optimization::VC:
358                     out << "Vc";
359                     break;
360 
361                 case Optimization::VD:
362                     out << "Vd";
363                     break;
364 
365                 case Optimization::VX:
366                     out << "Vx";
367                     break;
368 
369                 case Optimization::VY:
370                     out << "Vy";
371                     break;
372 
373                 case Optimization::RA:
374                     out << "Ra";
375                     break;
376 
377                 case Optimization::RB:
378                     out << "Rb";
379                     break;
380 
381                 case Optimization::RC:
382                     out << "Rc";
383                     break;
384 
385                 case Optimization::RD:
386                     out << "Rd";
387                     break;
388 
389                 case Optimization::RE:
390                     out << "Re";
391                     break;
392 
393                 case Optimization::UNKNOWN:
394                     qCCritical(DIGIKAM_GENERAL_LOG) << "Unknown optimization parameter!";
395                     file.close();
396                     return false;
397             }
398 
399             out << id << endl;
400         }
401     }
402 
403     out << "v" << endl;
404 
405     // Fifth, the masks
406 
407     for (int id = 0 ; id < images.size() ; ++id)
408     {
409         const Image& image = images[id];
410 
411         foreach (Mask mask, image.masks)
412         {
413             if (mask.previousComments.size() > 0)
414             {
415                 out << mask.previousComments.join(QLatin1Char('\n')) << endl;
416             }
417 
418             out << "k i" << id;
419             out << " t" << (int) mask.type;
420             out << " p\"";
421 
422             for (int pid = 0 ; pid < mask.hull.size() ; ++pid)
423             {
424                 out << (pid == 0 ? "" : " ");
425                 out << mask.hull[pid].x() << ' ' << mask.hull[pid].y();
426             }
427 
428             out << "\"" << endl;
429         }
430     }
431 
432     // Sixth, the control points
433 
434     foreach (ControlPoint cp, controlPoints)
435     {
436         if (cp.previousComments.size() > 0)
437         {
438             out << cp.previousComments.join(QLatin1Char('\n')) << endl;
439         }
440 
441         out << "c n" << cp.image1Id;
442         out << " N" << cp.image2Id;
443         out << " x" << cp.p1_x;
444         out << " y" << cp.p1_y;
445         out << " X" << cp.p2_x;
446         out << " Y" << cp.p2_y;
447         out << " t" << cp.type;
448         out << endl;
449     }
450 
451     // Finally the ending comments
452 
453     out << lastComments.join(QLatin1Char('\n')) << endl;
454 
455     file.close();
456 
457     return true;
458 }
459 
460 /*
461 QPair<double, int> PTOType::standardDeviation(int image1Id, int image2Id)
462 {
463     double mean_x = 0, mean_y = 0;
464     double n = 0;
465 
466     foreach (ControlPoint cp, controlPoints)
467     {
468         if ((cp.image1Id == image1Id && cp.image2Id == image2Id) || (cp.image1Id == image2Id && cp.image2Id == image1Id))
469         {
470             mean_x += cp.p2_x - cp.p1_x;
471             mean_y += cp.p2_y - cp.p1_y;
472             n++;
473         }
474     }
475 
476     if (n == 0)
477     {
478         return QPair<double, int>(0, 0);
479     }
480 
481     mean_x /= n;
482     mean_y /= n;
483     double result = 0;
484 
485     foreach (PTOType::ControlPoint cp, controlPoints)
486     {
487         if ((cp.image1Id == image1Id && cp.image2Id == image2Id) || (cp.image1Id == image2Id && cp.image2Id == image1Id))
488         {
489             double epsilon_x = (cp.p2_x - cp.p1_x) - mean_x;
490             double epsilon_y = (cp.p2_y - cp.p1_y) - mean_y;
491             result += epsilon_x * epsilon_x + epsilon_y * epsilon_y;
492         }
493     }
494 
495     return QPair<double, int>(result, n);
496 }
497 
498 QPair<double, int> PTOType::standardDeviation(int imageId)
499 {
500     int n = 0;
501     double result = 0;
502 
503     for (int i = 0; i < images.size(); ++i)
504     {
505         QPair<double, int> tmp = standardDeviation(imageId, i);
506         result += tmp.first;
507         n += tmp.second;
508     }
509 
510     return QPair<double, int>(result, n);
511 }
512 
513 QPair<double, int> PTOType::standardDeviation()
514 {
515     int n = 0;
516     double result = 0;
517 
518     for (int i = 0; i < images.size(); ++i)
519     {
520         QPair<double, int> tmp = standardDeviation(i);
521         result += tmp.first;
522         n += tmp.second;
523     }
524 
525     return QPair<double, int>(result, n);
526 }
527 */
528 
529 } // namespace Digikam
530