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 = ℑ
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