Hugin trunk 0.1
Loading...
Searching...
No Matches
StitchingExecutor.cpp
Go to the documentation of this file.
1
8/* This is free software; you can redistribute it and/or
9* modify it under the terms of the GNU General Public
10* License as published by the Free Software Foundation; either
11* version 2 of the License, or (at your option) any later version.
12*
13* This software is distributed in the hope that it will be useful,
14* but WITHOUT ANY WARRANTY; without even the implied warranty of
15* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16* Lesser General Public License for more details.
17*
18* You should have received a copy of the GNU General Public
19* License along with this software. If not, see
20* <http://www.gnu.org/licenses/>.
21*
22*/
23
24#include "base_wx/platform.h"
25#include "StitchingExecutor.h"
26#include <list>
27#include <wx/config.h>
28#include <wx/translation.h>
29#include <wx/arrstr.h>
30#include <wx/filefn.h>
31#include <wx/txtstrm.h>
32#include <wx/wfstream.h>
33#include "hugin_utils/utils.h"
37#include "base_wx/wxPlatform.h"
38#include "base_wx/LensTools.h"
40
41namespace HuginQueue
42{
43 namespace detail
44 {
45 // contains some helper functions
48 {
50 for (HuginBase::UIntSet::const_iterator it = img.begin(); it != img.end(); ++it)
51 {
52 filenames.Add(wxString::Format("%s%04u%s", prefix.c_str(), *it, postfix.c_str()));
53 };
54 return filenames;
55 };
56
59 {
60 for (size_t i = 0; i < input.size(); ++i)
61 {
62 output.Add(input[i]);
63 };
64 };
65
70 {
71 wxString argfileInput = config->Read("/output/FinalArgfile", wxEmptyString);
72 const bool generateGPanoTags = (config->Read("/output/writeGPano", HUGIN_EXIFTOOL_CREATE_GPANO) == 1l) && (exifToolVersion >= 9.09);
74 const HuginBase::PanoramaOptions &opts = pano.getOptions();
75 const bool readProjectionName = panoProjectionFeaturesQuery(opts.getProjection(), &proj) != 0;
76 // build placeholder map
77 struct Placeholder
78 {
80 wxString value;
82 {
84 value = newValue;
85 };
86 };
87 std::list<Placeholder> placeholders;
88#ifdef _WIN32
89 const wxString linebreak("&#xd;&#xa;");
90#else
91 const wxString linebreak("&#xa;");
92#endif
94 {
95 // %projectionNumber have to be processed before %projection, otherwise it does not work
96 placeholders.push_back(Placeholder("%projectionNumber", wxString::Format("%d", opts.getProjection())));
97 placeholders.push_back(Placeholder("%projection", wxString(proj.name, wxConvLocal)));
98 };
99 // fill in some placeholders
100 placeholders.push_back(Placeholder("%hfov", wxString::Format("%.0f", opts.getHFOV())));
101 placeholders.push_back(Placeholder("%vfov", wxString::Format("%.0f", opts.getVFOV())));
102 placeholders.push_back(Placeholder("%ev", wxString::Format("%.2f", opts.outputExposureValue)));
103 placeholders.push_back(Placeholder("%nrImages", wxString::Format("%lu", (unsigned long)images.size())));
104 placeholders.push_back(Placeholder("%nrAllImages", wxString::Format("%lu", (unsigned long)pano.getNrOfImages())));
105 placeholders.push_back(Placeholder("%fullwidth", wxString::Format("%u", opts.getWidth())));
106 placeholders.push_back(Placeholder("%fullheight", wxString::Format("%u", opts.getHeight())));
107 placeholders.push_back(Placeholder("%width", wxString::Format("%d", opts.getROI().width())));
108 placeholders.push_back(Placeholder("%height", wxString::Format("%d", opts.getROI().height())));
110 placeholders.push_back(Placeholder("%projectname", projectFilename.GetFullName()));
111 // now open the final argfile
112 wxFileName tempArgfileFinal(wxFileName::CreateTempFileName(GetConfigTempDir(config) + "he"));
115 // write argfile
116 outputFile << "-Software=Hugin " << wxString(hugin_utils::GetHuginVersion().c_str(), wxConvLocal) << endl;
117 outputFile << "-E" << endl;
118 outputFile << "-UserComment<${UserComment}" << linebreak;
120 {
121 outputFile << "Projection: " << wxString(proj.name, wxConvLocal) << " (" << opts.getProjection() << ")" << linebreak;
122 };
123 outputFile << "FOV: " << wxString::Format("%.0f", opts.getHFOV()) << " x " << wxString::Format("%.0f", opts.getVFOV()) << linebreak;
124 outputFile << "Ev: " << wxString::Format("%.2f", opts.outputExposureValue) << endl;
125 outputFile << "-f" << endl;
126 if (exifToolVersion >= 11.53)
127 {
128 // add composite image tags from EXIF 2.32
129 // these are supported by exiftool 11.53 and later
130 outputFile << "-CompositeImage=General Composite Image" << endl;
131 outputFile << wxString::Format("-CompositeImageCount=%lu %lu", (unsigned long)pano.getNrOfImages(), (unsigned long)pano.getActiveImages().size()) << endl;
132 };
134 {
135 //GPano tags are only indented for equirectangular images
137 {
139 const vigra::Rect2D roi = opts.getROI();
140 int left = roi.left();
141 int top = roi.top();
142 int width = roi.width();
143 int height = roi.height();
144
145 int fullWidth = opts.getWidth();
146 if (opts.getHFOV()<360)
147 {
148 fullWidth = static_cast<int>(opts.getWidth() * 360.0 / opts.getHFOV());
149 left += (fullWidth - opts.getWidth()) / 2;
150 };
151 int fullHeight = opts.getHeight();
152 if (!isCylindrical && opts.getVFOV()<180)
153 {
154 fullHeight = static_cast<int>(opts.getHeight() * 180.0 / opts.getVFOV());
155 top += (fullHeight - opts.getHeight()) / 2;
156 };
157 if (isCylindrical)
158 {
159 // different calculation of top parameter for cylindrical projection
160 top = height/2 - top;
161 };
162 outputFile << "-UsePanoramaViewer=True" << endl;
163 outputFile << "-StitchingSoftware=Hugin" << endl;
164 if (isCylindrical)
165 {
166 outputFile << "-ProjectionType=cylindrical" << endl;
167 }
168 else
169 {
170 outputFile << "-ProjectionType=equirectangular" << endl;
171 };
172 outputFile << "-CroppedAreaLeftPixels=" << left << endl;
173 outputFile << "-CroppedAreaTopPixels=" << top << endl;
174 outputFile << "-CroppedAreaImageWidthPixels=" << width << endl;
175 outputFile << "-CroppedAreaImageHeightPixels=" << height << endl;
176 outputFile << "-FullPanoWidthPixels=" << fullWidth << endl;
177 if (!isCylindrical)
178 {
179 // for cylindrical projection FullPanoHeightPixels is infinity
180 outputFile << "-FullPanoHeightPixels=" << fullHeight << endl;
181 };
182 outputFile << "-SourcePhotosCount=" << static_cast<wxUint32>(pano.getNrOfImages()) << endl;
183 };
184 };
185 // now open the input file and append it
186 if (!argfileInput.IsEmpty())
187 {
189 {
192 while (inputFileStream.IsOk() && !inputFileStream.Eof())
193 {
194 wxString line = input.ReadLine();
195 // replace all placeholders
196 for (auto variable : placeholders)
197 {
198 line.Replace(variable.placeholder, variable.value, true);
199 };
200 // now append to existing argfile
201 outputFile << line << endl;
202 };
203 };
204 };
205 return tempArgfileFinal.GetFullPath();
206 };
207
209 {
211 const wxString wxEndl("\n");
212 output
213 << "============================================" << wxEndl
214 << _("Stitching panorama...") << wxEndl
215 << "============================================" << wxEndl
216 << wxEndl
217 << _("Platform:") << " " << wxGetOsDescription() << wxEndl
218 << _("Version:") << " " << wxString(hugin_utils::GetHuginVersion().c_str(), wxConvLocal) << wxEndl
219 << _("Working directory:") << " " << wxFileName::GetCwd() << wxEndl
220 << _("Output prefix:") << " " << prefix << wxEndl
221 << wxEndl;
224 {
225 switch (opts.blendMode)
226 {
228 {
230 if (wxExecute(wxEscapeFilename(GetExternalProgram(config, bindir, "enblend")) + " --version", version, wxEXEC_SYNC) == 0l)
231 {
232 output << _("Blender:") << " " << version[0] << wxEndl;
233 }
234 else
235 {
236 output << _("Blender:") << " " << _("Unknown blender (enblend --version failed)") << wxEndl;
237 };
238 };
239 break;
241 default: // switch to internal blender for all other cases, not exposed in GUI
242 output << _("Blender:") << " " << _("internal") << wxEndl;
243 break;
244 };
245 };
247 {
249 if (wxExecute(wxEscapeFilename(GetExternalProgram(config, bindir, "enfuse")) + " --version", version, wxEXEC_SYNC) == 0l)
250 {
251 output << _("Exposure fusion:") << " " << version[0] << wxEndl;
252 }
253 else
254 {
255 output << _("Exposure fusion:") << " " << _("Unknown exposure fusion (enfuse --version failed)") << wxEndl;
256 };
257 };
258 if (config->Read("/output/useExiftool", HUGIN_USE_EXIFTOOL) == 1l)
259 {
261 if (wxExecute(wxEscapeFilename(GetExternalProgram(config, bindir, "exiftool")) + " -ver", version, wxEXEC_SYNC) == 0l)
262 {
263 output << _("ExifTool version:") << " " << version[0] << wxEndl;
264 version[0].ToCDouble(&exiftoolVersion);
265 }
266 else
267 {
268 output << _("ExifTool:") << " " << _("FAILED") << wxEndl;
269 exiftoolVersion = 1;
270 };
271 };
272 output
273 << wxEndl
274 << _("Number of active images:") << " " << allActiveImages.size() << wxEndl
275 << wxString::Format(_("Output exposure value: %.1f"), opts.outputExposureValue) << wxEndl
276 << wxString::Format(_("Canvas size: %dx%d"), opts.getSize().width(), opts.getSize().height()) << wxEndl
277 << wxString::Format(_("ROI: (%d, %d) - (%d, %d)"), opts.getROI().left(), opts.getROI().top(), opts.getROI().right(), opts.getROI().bottom()) << " " << wxEndl
278 << wxString::Format(_("FOV: %.0fx%.0f"), opts.getHFOV(), opts.getVFOV()) << wxEndl;
280 const bool readProjectionName = panoProjectionFeaturesQuery(opts.getProjection(), &proj) != 0;
282 {
283 output
284 << _("Projection:") << " " << wxGetTranslation(wxString(proj.name, wxConvLocal))
285 << "(" << opts.getProjection() << ")" << wxEndl;
286 }
287 else
288 {
289 output
290 << _("Projection:") << " " << opts.getProjection() << wxEndl;
291 };
292 output
293 << _("Using GPU for remapping:") << " " << (opts.remapUsingGPU ? _("true") : _("false")) << wxEndl
294 << wxEndl;
296 {
297 output << _("Panorama Outputs:") << wxEndl;
298 if (opts.outputLDRBlended)
299 {
300 output << "* " << _("Exposure corrected, low dynamic range") << wxEndl;
301 };
303 {
304 output << "* " << _("Exposure fused from stacks") << wxEndl;
305 };
307 {
308 output << "* " << _("Exposure fused from any arrangement") << wxEndl;
309 };
310 if (opts.outputHDRBlended)
311 {
312 output << "* " << _("High dynamic range") << wxEndl;
313 };
314 output << wxEndl;
315 };
317 {
318 output << _("Remapped Images:") << wxEndl;
319 if (opts.outputLDRBlended)
320 {
321 output << "* " << _("Exposure corrected, low dynamic range") << wxEndl;
322 };
324 {
325 output << "* " << _("No exposure correction, low dynamic range") << wxEndl;
326 };
327 if (opts.outputHDRLayers)
328 {
329 output << "* " << _("High dynamic range") << wxEndl;
330 };
331 output << wxEndl;
332 };
333 if (opts.outputLDRStacks || opts.outputHDRStacks)
334 {
335 output << _("Combined stacks:") << wxEndl;
336 if (opts.outputLDRStacks)
337 {
338 output << "* " << _("Exposure fused stacks") << wxEndl;
339 };
340 if (opts.outputHDRStacks)
341 {
342 output << "* " << _("High dynamic range") << wxEndl;
343 };
344 output << wxEndl;
345 };
347 {
348 output << _("Layers:") << wxEndl
349 << "* " << _("Blended layers of similar exposure, without exposure correction") << wxEndl
350 << wxEndl;
351 };
352 const HuginBase::SrcPanoImage img = pano.getImage(*allActiveImages.begin());
353 output << _("First input image") << wxEndl
354 << _("Number:") << " " << *allActiveImages.begin() << wxEndl
355 << _("Filename:") << " " << img.getFilename() << wxEndl
356 << wxString::Format(_("Size: %dx%d"), img.getWidth(), img.getHeight()) << wxEndl
357 << _("Projection:") << " " << getProjectionString(img) << wxEndl
358 << _("Response type:") << " " << getResponseString(img) << wxEndl
359 << wxString::Format(_("HFOV: %.0f"), img.getHFOV()) << wxEndl
360 << wxString::Format(_("Exposure value: %.1f"), img.getExposureValue()) << wxEndl
361 << wxEndl;
362 return output;
363 };
364
367 {
368 // if output is hard seam we keep the order
369 if (hardSeam)
370 {
371 return GetQuotedFilenamesString(files);
372 };
373 // user wants a blended seam, we need to figure out the correct order
374 int refImage = 0;
375 // first build a subpano which contains only one image per stack of the original pano
377 for (size_t i = 0; i < stacks.size(); ++i)
378 {
379 if (set_contains(stacks[i], referenceImage))
380 {
381 refImage = i;
382 };
383 stackImgs.insert(*(stacks[i].begin()));
384 };
385 // now create the subpano, don't forget to delete at end
388 fill_set(subpanoImgs, 0, stackImgs.size() - 1);
389 // find the blend order
391 delete subpano;
392 // now build the string in the correct order
393 wxString s;
394 for (size_t i = 0; i < blendOrder.size();++i)
395 {
396 s.Append(wxEscapeFilename(files[blendOrder[i]]) + " ");
397 };
398 return s;
399 };
400
402 void AddEdgeFillCommand(HuginQueue::CommandQueue* commands, const HuginBase::PanoramaOptions::EdgeFillMode& edgeFillMode, const wxString& ExePath, const wxString& inputFilename, const wxString& outputFilename, const wxString& compression)
403 {
405 {
406 commands->push_back(new NormalCommand(
407 GetInternalProgram(ExePath, "verdandi"),
408 "--output=" + wxEscapeFilename(outputFilename) + " " + compression + " --seam=blend " + wxEscapeFilename(inputFilename),
409 _("Filling edges...")));
410 }
411 }
412
413 } // namespace detail
414
416 {
417 CommandQueue* commands = new CommandQueue;
418 const HuginBase::UIntSet allActiveImages = getImagesinROI(pano, pano.getActiveImages());
419 if (allActiveImages.empty())
420 {
421 errStream << "ERROR: No active images in ROI. Nothing to do." << std::endl;
422 return commands;
423 }
424 std::vector<HuginBase::UIntSet> stacks;
425
426 // check options, not all are currently supported
428 wxConfigBase* config = wxConfigBase::Get();
429 opts.remapUsingGPU = config->Read("/Nona/UseGPU", HUGIN_NONA_USEGPU) == 1;
431 {
432 errStream << "ERROR: Only nona remappper is supported by hugin_executor." << std::endl;
433 return commands;
434 };
436 {
437 errStream << "ERROR: Only enblend and internal remappper are currently supported by hugin_executor." << std::endl;
438 return commands;
439 };
441 {
442 errStream << "ERROR: Only hdr merger HDRMERGE_AVERAGE is currently supported by hugin_executor." << std::endl;
443 return commands;
444 };
445 double exiftoolVersion;
447 // prepare some often needed variables
449 // prepare nona arguments
450 wxString nonaArgs("-v ");
452 if (!opts.outputLayersCompression.empty())
453 {
454 nonaArgs.Append("-z " + opts.outputLayersCompression + " ");
455 enLayersCompressionArgs.Append(" --compression=" + opts.outputLayersCompression + " ");
456 }
457 else
458 {
459 if (opts.outputImageType == "jpg")
460 {
461 nonaArgs.Append("-z LZW ");
462 }
463 };
464 if (opts.remapUsingGPU)
465 {
466 nonaArgs.Append("-g ");
467 };
468 // prepare enblend arguments
471 {
472 enblendArgs.Append(opts.enblendOptions);
473 if ((opts.getHFOV() == 360.0) && (opts.getWidth()==opts.getROI().width()))
474 {
475 enblendArgs.Append(" -w");
476 };
477 const vigra::Rect2D roi (opts.getROI());
478 if (roi.top() != 0 || roi.left() != 0)
479 {
480 enblendArgs << " -f" << roi.width() << "x" << roi.height() << "+" << roi.left() << "+" << roi.top();
481 }
482 else
483 {
484 enblendArgs << " -f" << roi.width() << "x" << roi.height();
485 };
486 enblendArgs.Append(" ");
487 };
488 // prepare internal blending arguments
491 {
492 verdandiArgs.Append(opts.verdandiOptions);
493 if ((opts.getHFOV() == 360.0) && (opts.getWidth() == opts.getROI().width()))
494 {
495 verdandiArgs.Append(" -w");
496 };
497 };
498 // prepare the compression switches
500 if (opts.outputImageType == "tif" && !opts.outputImageTypeCompression.empty())
501 {
502 finalCompressionArgs << " --compression=" << opts.outputImageTypeCompression;
503 }
504 else
505 {
506 if (opts.outputImageType == "jpg")
507 {
508 finalCompressionArgs << " --compression=" << opts.quality;
509 };
510 };
511 finalCompressionArgs.Append(" ");
512 // prepare enfuse arguments
514 if ((opts.getHFOV() == 360.0) && (opts.getWidth() == opts.getROI().width()))
515 {
516 enfuseArgs.Append(" -w");
517 };
518 const vigra::Rect2D roi (opts.getROI());
519 if (roi.top() != 0 || roi.left() != 0)
520 {
521 enfuseArgs << " -f" << roi.width() << "x" << roi.height() << "+" << roi.left() << "+" << roi.top();
522 }
523 else
524 {
525 enfuseArgs << " -f" << roi.width() << "x" << roi.height();
526 };
527 enfuseArgs.Append(" ");
528
529 // prepare exiftool args
530 const bool copyMetadata = config->Read("/output/useExiftool", HUGIN_USE_EXIFTOOL) == 1l;
533 if (copyMetadata)
534 {
535 exiftoolArgs = "-overwrite_original -TagsFromFile ";
536 exiftoolArgs.Append(wxEscapeFilename(wxString(pano.getImage(0).getFilename().c_str(), HUGIN_CONV_FILENAME)));
537 // required tags, can not be overwritten
538 exiftoolArgs.Append(" -WhitePoint -ColorSpace");
539 wxString exiftoolArgfile = config->Read("/output/CopyArgfile", wxEmptyString);
540 if (exiftoolArgfile.IsEmpty())
541 {
542 exiftoolArgfile = wxString(std::string(hugin_utils::GetDataDir() + "hugin_exiftool_copy.arg").c_str(), HUGIN_CONV_FILENAME);
543 };
546 exiftoolArgs.Append(" -@ " + wxEscapeFilename(argfile.GetFullPath()) + " ");
548 if (!finalArgfile.IsEmpty())
549 {
550 exiftoolArgsFinal.Append(" -@ " + wxEscapeFilename(finalArgfile) + " ");
552 };
553 };
556 // fill edge related options
558
559 // normal output
560 if (opts.outputLDRBlended || opts.outputLDRLayers)
561 {
563 const wxString finalFilename(prefix + "." + opts.outputImageType);
565 {
566 wxString finalNonaArgs("-v -r ldr ");
567 if (opts.remapUsingGPU)
568 {
569 finalNonaArgs.Append("-g ");
570 }
571 if (!opts.verdandiOptions.empty())
572 {
573 finalNonaArgs.Append(opts.verdandiOptions);
574 finalNonaArgs.Append(" ");
575 };
577 if (doEdgeFill)
578 {
579 finalNonaArgs.Append("-m TIFF -z " + opts.tiffCompression + " --final-suffix=_nofill ");
580 edgeInputFile = prefix + "_nofill.tif";
582 if (opts.keepEdgeFillInput)
583 {
584 if (copyMetadata)
585 {
587 };
588 }
589 else
590 {
592 };
593 }
594 else
595 {
596 if (opts.outputImageType == "tif")
597 {
598 finalNonaArgs.Append("-m TIFF ");
599 if (!opts.outputImageTypeCompression.empty())
600 {
601 finalNonaArgs.Append("-z " + opts.outputImageTypeCompression + " ");
602 };
603 }
604 else
605 {
606 if (opts.outputImageType == "jpg")
607 {
608 finalNonaArgs.Append("-m JPEG -z ");
609 finalNonaArgs << opts.quality << " ";
610 }
611 else
612 {
613 if (opts.outputImageType == "png")
614 {
615 finalNonaArgs.Append("-m PNG ");
616 }
617 else
618 {
619 errStream << "ERROR: Invalid output image type found." << std::endl;
620 return commands;
621 };
622 };
623 };
624 };
625 if (opts.outputLDRLayers)
626 {
627 finalNonaArgs.Append("--save-intermediate-images ");
629 }
630 finalNonaArgs.Append("-o " + wxEscapeFilename(prefix) + " " + quotedProject);
631 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, "nona"),
632 finalNonaArgs, _("Remapping and blending LDR images...")));
634 if (doEdgeFill)
635 {
637 };
638 if (copyMetadata)
639 {
641 };
642 }
643 else
644 {
645 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, "nona"),
646 nonaArgs + "-r ldr -m TIFF_m -o " + wxEscapeFilename(prefix) + " " + quotedProject,
647 _("Remapping LDR images...")));
649 if (opts.outputLDRBlended)
650 {
652 {
655 if (doEdgeFill)
656 {
657 edgeFillInput = prefix + "_nofill.tif";
658 finalEnblendArgs.Append("--compression=" + opts.tiffCompression + " -o " + wxEscapeFilename(edgeFillInput) + " -- ");
659 }
660 else
661 {
663 };
664 commands->push_back(new NormalCommand(
665 GetExternalProgram(config, ExePath, "enblend"),
667 _("Blending images..."))
668 );
669 if (doEdgeFill)
670 {
673 if (!opts.keepEdgeFillInput)
674 {
676 };
677 };
679 if (copyMetadata)
680 {
681 if (doEdgeFill && opts.keepEdgeFillInput)
682 {
684 };
686 };
687 };
688 if (!opts.outputLDRLayers)
689 {
691 };
692 };
693 };
694 };
695 // exposure fusion output
698 {
699 const wxArrayString remappedImages = detail::GetNumberedFilename(prefix + "_exposure_layers_", ".tif", allActiveImages);
700 std::vector<HuginBase::UIntSet> exposureLayers;
703 {
704 exposureLayers = getExposureLayers(pano, allActiveImages, opts);
705 };
708 {
709 // directly export exposure layers by nona
711 if (!opts.verdandiOptions.empty())
712 {
713 finalNonaArgs.Append(opts.verdandiOptions);
714 finalNonaArgs.Append(" ");
715 };
716 finalNonaArgs.append("-r ldr --create-exposure-layers --ignore-exposure -o " + wxEscapeFilename(prefix + "_exposure_"));
718 {
719 finalNonaArgs.append(" --save-intermediate-images --intermediate-suffix=layers_");
722 {
724 }
725 };
726 finalNonaArgs.append(" ");
728 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, "nona"),
729 finalNonaArgs, _("Remapping LDR images and blending exposure layers...")));
734 if (!opts.outputLDRExposureLayers)
735 {
737 };
739 {
741 };
742 }
743 else
744 {
745 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, "nona"),
746 nonaArgs + "-r ldr -m TIFF_m --ignore-exposure -o " + wxEscapeFilename(prefix + "_exposure_layers_") + " " + quotedProject,
747 _("Remapping LDR images without exposure correction...")));
750 {
752 };
754 {
755 // blending exposure layers, then fusing
756 // fuse all exposure layers
757 for (unsigned exposureLayer = 0; exposureLayer < exposureLayers.size(); ++exposureLayer)
758 {
759 const wxArrayString exposureLayersImgs = detail::GetNumberedFilename(prefix + "_exposure_layers_", ".tif", exposureLayers[exposureLayer]);
760 const wxString exposureLayerImgName = wxString::Format("%s_exposure_%04u%s", prefix.c_str(), exposureLayer, ".tif");
763 // variant with internal blender is handled before, so we need only enblend
764 commands->push_back(new NormalCommand(
765 GetExternalProgram(config, ExePath, "enblend"),
767 wxString::Format(_("Blending exposure layer %u..."), exposureLayer))
768 );
770 {
772 };
773 if (!opts.outputLDRExposureLayers)
774 {
776 };
777 };
778 };
779 };
781 {
782 const wxString fusedExposureLayersFilename(prefix + "_blended_fused." + opts.outputImageType);
785 if (doEdgeFill)
786 {
787 edgeFillInput = prefix + "_blended_fused_nofill.tif";
788 finalEnfuseArgs.Append("--compression=" + opts.tiffCompression + " -o " + wxEscapeFilename(edgeFillInput) + " -- ");
789 }
790 else
791 {
793 };
794 commands->push_back(new NormalCommand(
797 _("Fusing all exposure layers..."))
798 );
800 if (doEdgeFill)
801 {
804 if (!opts.keepEdgeFillInput)
805 {
807 };
808 };
809 if (copyMetadata)
810 {
811 if (doEdgeFill && opts.keepEdgeFillInput)
812 {
814 };
816 };
817 };
819 {
820 // fusing stacks, then blending
821 stacks = getHDRStacks(pano, allActiveImages, opts);
823 // fuse all stacks
824 for (unsigned stackNr = 0; stackNr < stacks.size(); ++stackNr)
825 {
826 const wxArrayString stackImgs = detail::GetNumberedFilename(prefix + "_exposure_layers_", ".tif", stacks[stackNr]);
827 const wxString stackImgName = wxString::Format("%s_stack_ldr_%04u%s", prefix.c_str(), stackNr, ".tif");
830 commands->push_back(new NormalCommand(
833 wxString::Format(_("Fusing stack number %u..."), stackNr))
834 );
835 if (copyMetadata && opts.outputLDRStacks)
836 {
838 };
839 if (!opts.outputLDRStacks)
840 {
842 };
843 };
845 {
846 const wxString fusedStacksFilename(prefix + "_fused." + opts.outputImageType);
848 if (doEdgeFill)
849 {
850 edgeFillInput = prefix + "_fused_nofill.tif";
851 };
852 switch (opts.blendMode)
853 {
855 {
857 if (doEdgeFill)
858 {
859 finalEnblendArgs.Append("--compression=" + opts.tiffCompression + " -o " + wxEscapeFilename(edgeFillInput) + "-- ");
860 }
861 else
862 {
864 };
865 commands->push_back(new NormalCommand(
866 GetExternalProgram(config, ExePath, "enblend"),
868 _("Blending all stacks..."))
869 );
870 };
871 break;
873 default: // switch to internal blender for all other cases, not exposed in GUI
874 {
876 if (doEdgeFill)
877 {
878 finalVerdandiArgs.Append("--compression=" + opts.tiffCompression + " -o" + wxEscapeFilename(edgeFillInput));
879 }
880 else
881 {
883 };
884 finalVerdandiArgs.Append(" -- " + detail::GetQuotedFilenamesStringForVerdandi(stackedImages, pano, stacks, opts.colorReferenceImage, opts.verdandiOptions.find("--seam=blend") == std::string::npos));
885 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, "verdandi"),
886 finalVerdandiArgs, _("Blending all stacks...")));
887 };
888 break;
889 };
891 if (doEdgeFill)
892 {
895 if (!opts.keepEdgeFillInput)
896 {
898 };
899 };
900 if (copyMetadata)
901 {
902 if (doEdgeFill && opts.keepEdgeFillInput)
903 {
905 };
907 };
908 };
909 };
910 };
911 // hdr output
912 if (opts.outputHDRLayers || opts.outputHDRStacks || opts.outputHDRBlended)
913 {
914 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, "nona"),
915 nonaArgs + "-r hdr -m EXR_m -o " + wxEscapeFilename(prefix + "_hdr_") + " " + quotedProject,
916 _("Remapping HDR images...")));
917 const wxArrayString remappedHDR = detail::GetNumberedFilename(prefix + "_hdr_", ".exr", allActiveImages);
918 const wxArrayString remappedHDRComp = detail::GetNumberedFilename(prefix + "_hdr_", "_gray.pgm", allActiveImages);
921 if (opts.outputHDRStacks || opts.outputHDRBlended)
922 {
923 // merging stacks, then blending
924 if (stacks.empty())
925 {
926 stacks = getHDRStacks(pano, allActiveImages, opts);
927 };
929 // merge all stacks
930 for (unsigned stackNr = 0; stackNr < stacks.size(); ++stackNr)
931 {
932 const wxArrayString stackImgs = detail::GetNumberedFilename(prefix + "_hdr_", ".exr", stacks[stackNr]);
933 const wxString stackImgName = wxString::Format("%s_stack_hdr_%04u%s", prefix.c_str(), stackNr, ".exr");
936 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, "hugin_hdrmerge"),
938 wxString::Format(_("Merging HDR stack number %u..."), stackNr)));
939 if (!opts.outputHDRStacks)
940 {
942 };
943 };
944 if (opts.outputHDRBlended)
945 {
946 const wxString mergedStacksFilename(prefix + "_hdr." + opts.outputImageTypeHDR);
949 if (doEdgeFill)
950 {
951 edgeFillInput = prefix + "_hdr_nofill.exr";
952 finalBlendArgs.Append(" -o " + wxEscapeFilename(edgeFillInput) + " -- ");
953 }
954 else
955 {
956 finalBlendArgs.Append(" -o " + wxEscapeFilename(mergedStacksFilename) + " -- ");
957 };
958 switch (opts.blendMode)
959 {
961 commands->push_back(new NormalCommand(
962 GetExternalProgram(config, ExePath, "enblend"),
964 _("Blending HDR stacks..."))
965 );
966 break;
968 default: // switch to internal blender for all other cases, not exposed in GUI
969 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, "verdandi"),
970 verdandiArgs + finalBlendArgs + detail::GetQuotedFilenamesStringForVerdandi(stackedImages, pano, stacks, opts.colorReferenceImage, opts.verdandiOptions.find("--seam=blend") == std::string::npos),
971 _("Blending HDR stacks...")));
972 break;
973 };
975 if (doEdgeFill)
976 {
979 if (!opts.keepEdgeFillInput)
980 {
982 };
983 };
984 if (copyMetadata)
985 {
986 if (doEdgeFill && opts.keepEdgeFillInput)
987 {
989 };
991 };
992 };
993 };
994 if (!opts.outputHDRLayers)
995 {
998 };
999 };
1000 // update metadata
1001 if (!filesForCopyTagsExiftool.IsEmpty())
1002 {
1003 commands->push_back(new OptionalCommand(GetExternalProgram(config, ExePath, "exiftool"),
1005 _("Updating metadata...")));
1006 };
1007 if (!filesForFullExiftool.IsEmpty())
1008 {
1009 commands->push_back(new OptionalCommand(GetExternalProgram(config, ExePath, "exiftool"),
1011 _("Updating metadata...")));
1012 };
1013 return commands;
1014 };
1015
1016 // now the user defined stitching engine
1017 // we read the settings from an ini file and construct our CommandQueue
1018 namespace detail
1019 {
1020 // add program with argment to queue
1021 // do some checks on the way
1022 // if an error occurs or the input is not valid, the queue is cleared and the function returns false
1023 bool AddBlenderCommand(CommandQueue* queue, const wxString& ExePath, const wxString& prog,
1024 const int& stepNr, const wxString& arguments, const wxString& description, std::ostream& errStream)
1025 {
1026 if (prog.IsEmpty())
1027 {
1028 errStream << "ERROR: Step " << stepNr << " has no program name specified." << std::endl;
1029 CleanQueue(queue);
1030 return false;
1031 }
1032 // check program name
1033 // get full path for some internal commands
1035 if (prog.CmpNoCase("verdandi") == 0)
1036 {
1037 program = GetInternalProgram(ExePath, "verdandi");
1038 }
1039 else
1040 {
1041 if (prog.CmpNoCase("hugin_hdrmerge") == 0)
1042 {
1043 program = GetInternalProgram(ExePath, "hugin_hdrmerge");
1044 }
1045 else
1046 {
1047 program = prog;
1048 }
1049 };
1050 // now add to queue
1051 queue->push_back(new NormalCommand(program, arguments, description));
1052 return true;
1053 };
1054
1055 // replace the %prefix% placeholder, with optional postfix
1056 // the string is modified in place
1057 // return true on success, false if there are errors
1059 {
1060 int prefixPos = args.Find("%prefix");
1061 while (prefixPos != wxNOT_FOUND)
1062 {
1063 const wxString nextChar = args.Mid(prefixPos + 7, 1);
1064 if (nextChar == "%")
1065 {
1066 args.Replace("%prefix%", wxEscapeFilename(prefix), true);
1067 }
1068 else
1069 {
1070 if (nextChar == ",")
1071 {
1072 const int closingPercent = args.Mid(prefixPos + 8).Find("%");
1073 if (closingPercent < 2)
1074 {
1075 return false;
1076 };
1077 wxString postfix = args.Mid(prefixPos + 8, closingPercent);
1078 args.Replace("%prefix," + postfix + "%", wxEscapeFilename(prefix + postfix), true);
1079 }
1080 else
1081 {
1082 return false;
1083 };
1084 };
1085 prefixPos = args.Find("%prefix");
1086 };
1087 return true;
1088 };
1089
1090 // replace the %with% or %height% placeholder
1091 bool ReplaceWidthHeightPlaceHolder(wxString& args, const wxString name, int value)
1092 {
1093 int pos = args.Find("%" + name);
1094 while (pos != wxNOT_FOUND)
1095 {
1096 const wxString nextChar = args.Mid(pos + 1 + name.Len(), 1);
1097 if (nextChar == "%")
1098 {
1099 args.Replace("%" + name + "%", wxString::Format("%d", value), true);
1100 }
1101 else
1102 {
1103 if (nextChar == "*")
1104 {
1105 const int closingPercent = args.Mid(pos + 2 + name.Len()).Find("%");
1106 if (closingPercent < 2)
1107 {
1108 return false;
1109 };
1110 wxString factorString = args.Mid(pos + 2 + name.Len(), closingPercent);
1111 double factor;
1112 if (!factorString.ToCDouble(&factor))
1113 {
1114 return false;
1115 };
1116 args.Replace("%" + name + "*" + factorString + "%", wxString::Format("%d", hugin_utils::roundi(factor*value)), true);
1117 }
1118 else
1119 {
1120 return false;
1121 };
1122 };
1123 pos = args.Find("%" + name);
1124 };
1125 return true;
1126 };
1127
1128 }
1129
1131 {
1132 CommandQueue* commands = new CommandQueue;
1133 const HuginBase::UIntSet allActiveImages = getImagesinROI(pano, pano.getActiveImages());
1134 if (allActiveImages.empty())
1135 {
1136 errStream << "ERROR: No active images in ROI. Nothing to do." << std::endl;
1137 return commands;
1138 }
1140 if (!input.IsOk())
1141 {
1142 errStream << "ERROR: Can not open file \"" << outputSettings.mb_str(wxConvLocal) << "\"." << std::endl;
1143 return commands;
1144 }
1145 wxFileConfig settings(input);
1146 long stepCount;
1147 settings.Read("/General/StepCount", &stepCount, 0);
1148 if (stepCount == 0)
1149 {
1150 errStream << "ERROR: User-setting does not define any output steps." << std::endl;
1151 return commands;
1152 }
1153 const wxString desc = GetSettingStringTranslated(&settings, "/General/Description", wxEmptyString);
1154 if (desc.IsEmpty())
1155 {
1156 statusText = wxString::Format(_("Stitching using \"%s\""), outputSettings.c_str());
1157 }
1158 else
1159 {
1160 statusText = wxString::Format(_("Stitching using \"%s\""), desc.c_str());
1161 };
1162 wxString intermediateImageType = GetSettingString(&settings, "/General/IntermediateImageType", ".tif");
1163 // add point if missing
1164 if (intermediateImageType.Left(1).Cmp(".")!=0)
1165 {
1166 intermediateImageType.Prepend(".");
1167 }
1168 // prepare some often needed variables/strings
1169 const HuginBase::PanoramaOptions opts = pano.getOptions();
1170 const bool needsWrapSwitch = (opts.getHFOV() == 360.0) && (opts.getWidth() == opts.getROI().width());
1171 const vigra::Rect2D roi(opts.getROI());
1173 if (roi.top() != 0 || roi.left() != 0)
1174 {
1175 sizeString << roi.width() << "x" << roi.height() << "+" << roi.left() << "+" << roi.top();
1176 }
1177 else
1178 {
1179 sizeString << roi.width() << "x" << roi.height();
1180 };
1183 for (auto& i : allActiveImages)
1184 {
1185 inputImages.Add(wxString(pano.getImage(i).getFilename().c_str(), HUGIN_CONV_FILENAME));
1186 };
1187
1188 std::vector<HuginBase::UIntSet> exposureLayers;
1190
1191 std::vector<HuginBase::UIntSet> stacks;
1193
1194 // now iterate all steps
1195 for (size_t i = 0; i < stepCount; ++i)
1196 {
1197 wxString stepString("/Step");
1198 stepString << i;
1199 if (!settings.HasGroup(stepString))
1200 {
1201 errStream << "ERROR: Output specifies " << stepCount << " steps, but step " << i << " is missing in configuration." << std::endl;
1202 CleanQueue(commands);
1203 return commands;
1204 }
1205 settings.SetPath(stepString);
1206 const wxString stepType=GetSettingString(&settings, "Type");
1207 if (stepType.IsEmpty())
1208 {
1209 errStream << "ERROR: \"" << stepString.mb_str(wxConvLocal) << "\" has no type defined." << std::endl;
1210 CleanQueue(commands);
1211 return commands;
1212 };
1213 wxString args = GetSettingString(&settings, "Arguments");
1214 if (args.IsEmpty())
1215 {
1216 errStream << "ERROR: Step " << i << " has no arguments given." << std::endl;
1217 CleanQueue(commands);
1218 return commands;
1219 }
1220 const wxString description = GetSettingStringTranslated(&settings, "Description");
1221 if (stepType.CmpNoCase("remap") == 0)
1222 {
1223 // build nona command
1224 const bool outputLayers = (settings.Read("OutputExposureLayers", 0l) == 1l);
1225 if (outputLayers)
1226 {
1227 args.Append(" --create-exposure-layers -o " + wxEscapeFilename(prefix + "_layer"));
1228 }
1229 else
1230 {
1231 args.Append(" -o " + wxEscapeFilename(prefix));
1232 };
1233 args.Append(" " + wxEscapeFilename(project));
1234 commands->push_back(new NormalCommand(GetInternalProgram(ExePath, "nona"),
1235 args, description));
1236 if (outputLayers)
1237 {
1238 if (exposureLayers.empty())
1239 {
1240 exposureLayers = getExposureLayers(pano, allActiveImages, opts);
1244 };
1246 if (settings.Read("Keep", 0l) == 0l)
1247 {
1249 };
1250 }
1251 else
1252 {
1254 const bool hdrOutput = args.MakeLower().Find("-r hdr") != wxNOT_FOUND;
1256 if (hdrOutput)
1257 {
1259 };
1260 if (settings.Read("Keep", 0l) == 0l)
1261 {
1263 if (hdrOutput)
1264 {
1266 };
1267 };
1268 };
1269 }
1270 else
1271 {
1272 if (stepType.CmpNoCase("merge") == 0)
1273 {
1274 // build a merge command
1275 wxString resultFile = GetSettingString(&settings, "Result");
1276 if (resultFile.IsEmpty())
1277 {
1278 errStream << "ERROR: Step " << i << " has no result file specified." << std::endl;
1279 CleanQueue(commands);
1280 return commands;
1281 };
1282 resultFile.Replace("%prefix%", prefix, true);
1283 if (args.Replace("%result%", wxEscapeFilename(resultFile), true) == 0)
1284 {
1285 errStream << "ERROR: Step " << i << " has missing %result% placeholder in arguments." << std::endl;
1286 CleanQueue(commands);
1287 return commands;
1288 };
1289 const wxString BlenderInput = GetSettingString(&settings, "Input", "all");
1290 // set the input images depending on the input
1291 if (BlenderInput.CmpNoCase("all") == 0)
1292 {
1293 if (args.Replace("%input%", GetQuotedFilenamesString(remappedImages), true) == 0)
1294 {
1295 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl;
1296 CleanQueue(commands);
1297 return commands;
1298 };
1299 }
1300 else
1301 {
1302 if (BlenderInput.CmpNoCase("stacks") == 0)
1303 {
1304 if (stacks.empty())
1305 {
1306 stacks= HuginBase::getHDRStacks(pano, allActiveImages, opts);
1308 fill_set(stackNumbers, 0, stacks.size() - 1);
1310 };
1311 if (args.Replace("%input%", GetQuotedFilenamesString(stacksFiles), true) == 0)
1312 {
1313 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl;
1314 CleanQueue(commands);
1315 return commands;
1316 };
1317 }
1318 else
1319 {
1320 if (BlenderInput.CmpNoCase("layers") == 0)
1321 {
1322 if (exposureLayers.empty())
1323 {
1324 exposureLayers = getExposureLayers(pano, allActiveImages, opts);
1328 };
1329 if (args.Replace("%input%", GetQuotedFilenamesString(exposureLayersFiles), true) == 0)
1330 {
1331 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl;
1332 CleanQueue(commands);
1333 return commands;
1334 };
1335 }
1336 else
1337 {
1338 errStream << "ERROR: Step " << i << " has invalid input type: \"" << BlenderInput.mb_str(wxConvLocal) << "\"." << std::endl;
1339 CleanQueue(commands);
1340 return commands;
1341 };
1342 };
1343 };
1344 args.Replace("%size%", sizeString, true);
1345 wxString wrapSwitch = GetSettingString(&settings, "WrapArgument");
1346 if (needsWrapSwitch && !wrapSwitch.IsEmpty())
1347 {
1348 args.Prepend(wrapSwitch + " ");
1349 }
1350 if (!detail::AddBlenderCommand(commands, ExePath, GetSettingString(&settings, "Program"), i,
1351 args, description, errStream))
1352 {
1353 return commands;
1354 };
1356 if (settings.Read("Keep", 1l) == 0l)
1357 {
1359 };
1360 }
1361 else
1362 {
1363 if (stepType.CmpNoCase("stack") == 0)
1364 {
1365 // build command for each stack
1366 if (stacks.empty())
1367 {
1368 stacks = HuginBase::getHDRStacks(pano, allActiveImages, opts);
1370 fill_set(stackNumbers, 0, stacks.size() - 1);
1372 };
1373 const bool clean = (settings.Read("Keep", 0l) == 0l);
1374 args.Replace("%size%", sizeString, true);
1375 // now iterate each stack
1376 for (size_t stackNr = 0; stackNr < stacks.size(); ++stackNr)
1377 {
1378 wxString finalArgs(args);
1380 if (finalArgs.Replace("%input%", GetQuotedFilenamesString(remappedStackImages), true) == 0)
1381 {
1382 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl;
1383 CleanQueue(commands);
1384 return commands;
1385 };
1386 if (finalArgs.Replace("%output%", wxEscapeFilename(stacksFiles[stackNr]), true) == 0)
1387 {
1388 errStream << "ERROR: Step " << i << " has missing %output% placeholder in arguments." << std::endl;
1389 CleanQueue(commands);
1390 return commands;
1391 };
1392 if (!detail::AddBlenderCommand(commands, ExePath, GetSettingString(&settings, "Program"), i,
1394 {
1395 return commands;
1396 };
1398 if (clean)
1399 {
1401 };
1402 }
1403 }
1404 else
1405 {
1406 if (stepType.CmpNoCase("layer") == 0)
1407 {
1408 // build command for each exposure layer
1409 if (exposureLayers.empty())
1410 {
1415 };
1416 const bool clean = (settings.Read("Keep", 0l) == 0l);
1417 args.Replace("%size%", sizeString, true);
1418 // iterate all exposure layers
1420 {
1421 wxString finalArgs(args);
1423 if (finalArgs.Replace("%input%", GetQuotedFilenamesString(remappedLayerImages), true) == 0)
1424 {
1425 errStream << "ERROR: Step " << i << " has missing %input% placeholder in arguments." << std::endl;
1426 CleanQueue(commands);
1427 return commands;
1428 };
1429 if (finalArgs.Replace("%output%", wxEscapeFilename(exposureLayersFiles[exposureLayerNr]), true) == 0)
1430 {
1431 errStream << "ERROR: Step " << i << " has missing %output% placeholder in arguments." << std::endl;
1432 CleanQueue(commands);
1433 return commands;
1434 };
1435 if (!detail::AddBlenderCommand(commands, ExePath, GetSettingString(&settings, "Program"), i,
1437 {
1438 return commands;
1439 };
1441 if (clean)
1442 {
1444 };
1445 }
1446 }
1447 else
1448 {
1449 if (stepType.CmpNoCase("modify") == 0)
1450 {
1451 // build a modify command
1452 wxString inputFiles = GetSettingString(&settings, "File");
1453 if (inputFiles.IsEmpty())
1454 {
1455 errStream << "ERROR: Step " << i << " has no input/output file specified." << std::endl;
1456 CleanQueue(commands);
1457 return commands;
1458 };
1459 if (args.Find("%file%") == wxNOT_FOUND)
1460 {
1461 errStream << "ERROR: Step " << i << " has missing %file% placeholder in arguments." << std::endl;
1462 CleanQueue(commands);
1463 return commands;
1464 };
1465 args.Replace("%project%", wxEscapeFilename(project), true);
1466 if (!detail::ReplacePrefixPlaceholder(args, prefix))
1467 {
1468 errStream << "ERROR: Step " << i << " has invalid %prefix% placeholder in arguments." << std::endl;
1469 CleanQueue(commands);
1470 return commands;
1471 };
1472 if (!detail::ReplaceWidthHeightPlaceHolder(args, "width", opts.getWidth()))
1473 {
1474 errStream << "ERROR: Step " << i << " has invalid %width% placeholder in arguments." << std::endl;
1475 CleanQueue(commands);
1476 return commands;
1477 }
1478 if (!detail::ReplaceWidthHeightPlaceHolder(args, "height", opts.getHeight()))
1479 {
1480 errStream << "ERROR: Step " << i << " has invalid %height% placeholder in arguments." << std::endl;
1481 CleanQueue(commands);
1482 return commands;
1483 }
1484 const wxString progName = GetSettingString(&settings, "Program");
1485 if (progName.IsEmpty())
1486 {
1487 errStream << "ERROR: Step " << i << " has no program name specified." << std::endl;
1488 CleanQueue(commands);
1489 return commands;
1490 };
1491#ifdef __WXMAC__
1492 // check if program can be found in bundle
1493 const wxString prog = GetExternalProgram(wxConfig::Get(), ExePath, progName);
1494#elif defined __WXMSW__
1495
1496 const wxString prog = MSWGetProgname(ExePath, progName);
1497#else
1498 const wxString prog = progName;
1499#endif
1500 if (inputFiles.CmpNoCase("all") == 0)
1501 {
1502 for (size_t imgNr = 0; imgNr < remappedImages.size(); ++imgNr)
1503 {
1504 wxString finalArgs(args);
1505 finalArgs.Replace("%file%", wxEscapeFilename(remappedImages[imgNr]), true);
1506 finalArgs.Replace("%sourceimage%", wxEscapeFilename(inputImages[imgNr]), true);
1507 commands->push_back(new NormalCommand(prog, finalArgs, description));
1508 };
1509 }
1510 else
1511 {
1512 if (inputFiles.CmpNoCase("stacks") == 0)
1513 {
1514 if (stacks.empty())
1515 {
1516 errStream << "ERROR: Step " << i << " requests to modify stacks, but no stack was created before." << std::endl;
1517 CleanQueue(commands);
1518 return commands;
1519 };
1520 for (size_t stackNr = 0; stackNr < stacksFiles.size(); ++stackNr)
1521 {
1522 wxString finalArgs(args);
1523 finalArgs.Replace("%file%", wxEscapeFilename(stacksFiles[stackNr]), true);
1524 commands->push_back(new NormalCommand(prog, finalArgs, description));
1525 };
1526 }
1527 else
1528 {
1529 if (inputFiles.CmpNoCase("layers") == 0)
1530 {
1531 if (exposureLayers.empty())
1532 {
1533 errStream << "ERROR: Step " << i << " requests to modify exposure layers, but no exposure layer was created before." << std::endl;
1534 CleanQueue(commands);
1535 return commands;
1536 };
1537 for (size_t layerNr = 0; layerNr < exposureLayersFiles.size(); ++layerNr)
1538 {
1539 wxString finalArgs(args);
1540 finalArgs.Replace("%file%", wxEscapeFilename(exposureLayersFiles[layerNr]), true);
1541 commands->push_back(new NormalCommand(prog, finalArgs, description));
1542 };
1543 }
1544 else
1545 {
1546 inputFiles.Replace("%prefix%", prefix, true);
1547 args.Replace("%file%", wxEscapeFilename(inputFiles), true);
1548 commands->push_back(new NormalCommand(prog , args, description));
1549 };
1550 };
1551 };
1552 }
1553 else
1554 {
1555 if (stepType.CmpNoCase("exiftool") == 0)
1556 {
1557 wxString resultFile = GetSettingString(&settings, "Result");
1558 if (resultFile.IsEmpty())
1559 {
1560 errStream << "ERROR: Step " << i << " has no result file specified." << std::endl;
1561 CleanQueue(commands);
1562 return commands;
1563 };
1564 resultFile.Replace("%prefix%", prefix, true);
1565 if (args.Replace("%result%", wxEscapeFilename(resultFile), true) == 0)
1566 {
1567 errStream << "ERROR: Step " << i << " has missing %result% placeholder in arguments." << std::endl;
1568 CleanQueue(commands);
1569 return commands;
1570 };
1571 args.Replace("%image0%", wxEscapeFilename(wxString(pano.getImage(0).getFilename().c_str(), HUGIN_CONV_FILENAME)), true);
1572 commands->push_back(new OptionalCommand(GetExternalProgram(wxConfigBase::Get(), ExePath, "exiftool"),
1573 args, description));
1574 }
1575 else
1576 {
1577 errStream << "ERROR: Step " << i << " has unknown Type \"" << stepType.mb_str(wxConvLocal) << "\"." << std::endl;
1578 CleanQueue(commands);
1579 return commands;
1580 };
1581 };
1582 };
1583 };
1584 };
1585 };
1586 };
1587 return commands;
1588 }
1589
1592 {
1593 wxString s;
1594 for (size_t i = 0; i < files.size(); ++i)
1595 {
1596 s.Append(wxEscapeFilename(files[i]) + " ");
1597 };
1598 return s;
1599 };
1600
1601}; // namespace
declaration of functions to handle stacks and layers
wxString getProjectionString(const HuginBase::SrcPanoImage &img)
Returns translated projection for given image.
wxString getResponseString(const HuginBase::SrcPanoImage &img)
Returns translated response type for given SrcPanoImage.
some helper classes for graphes
Utility calls into PanoTools using CPP interface.
interface of CommandQueue creating for stitching engine
Model for a panorama.
Panorama image options.
bool outputHDRLayers
save remapped layers (HDR)
bool outputLDRExposureLayers
save blended exposure layers, do not perform fusion (no exposure adjustment)
bool outputLDRExposureBlended
< save exposure fused stacks (no exposure adjustment)
bool outputLDRExposureRemapped
save remapped layers (no exposure adjustment)
const vigra::Rect2D & getROI() const
bool outputLDRBlended
save blended panorama (LDR)
bool outputLDRLayers
save remapped layers (LDR)
unsigned int getWidth() const
PanoramaOptions::ProjectionFormat getProjection() const
bool outputHDRStacks
save image stacks (HDR)
bool outputLDRExposureLayersFused
save blended exposure layers which are then fused (no exposure adjustment)
unsigned int getHeight() const
get panorama height
vigra::Size2D getSize() const
get size of output image
bool outputHDRBlended
save blended panorama (HDR)
Model for a panorama.
Definition Panorama.h:153
const SrcPanoImage & getImage(std::size_t nr) const
get a panorama image, counting starts with 0
Definition Panorama.h:211
const PanoramaOptions & getOptions() const
returns the options for this panorama
Definition Panorama.h:481
PanoramaData * getNewSubset(const UIntSet &imgs) const
Definition Panorama.h:183
UIntSet getActiveImages() const
get active images
std::size_t getNrOfImages() const
number of images.
Definition Panorama.h:205
All variables of a source image.
int getWidth() const
Get the width of the image in pixels.
int getHeight() const
Get the height of the image in pixels.
normal command for queue, processing is stopped if an error occurred in program
Definition Executor.h:38
optional command for queue, processing of queue is always continued, also if an error occurred
Definition Executor.h:54
#define HUGIN_USE_EXIFTOOL
#define HUGIN_EXIFTOOL_CREATE_GPANO
#define HUGIN_NONA_USEGPU
implementation of huginApp Class
#define HUGIN_CONV_FILENAME
Definition platform.h:40
std::vector< unsigned int > UIntVector
UIntVector getEstimatedBlendingOrder(const PanoramaData &pano, const UIntSet &images, const unsigned int referenceImage)
returns vector of image numbers for blending in approbiate order
std::vector< UIntSet > UIntSetVector
std::set< unsigned int > UIntSet
std::vector< UIntSet > getHDRStacks(const PanoramaData &pano, UIntSet allImgs, PanoramaOptions opts)
returns vector of set of output stacks
std::vector< UIntSet > getExposureLayers(const PanoramaData &pano, UIntSet allImgs, PanoramaOptions opts)
returns vector of set of output exposure layers
wxString PrintDetailInfo(const HuginBase::Panorama &pano, const HuginBase::PanoramaOptions &opts, const HuginBase::UIntSet &allActiveImages, const wxString &prefix, const wxString &bindir, wxConfigBase *config, double &exiftoolVersion)
bool ReplacePrefixPlaceholder(wxString &args, const wxString prefix)
wxString GenerateFinalArgfile(const HuginBase::Panorama &pano, const wxString &projectName, const wxConfigBase *config, const HuginBase::UIntSet &images, const double exifToolVersion)
generate the final argfile
void AddEdgeFillCommand(HuginQueue::CommandQueue *commands, const HuginBase::PanoramaOptions::EdgeFillMode &edgeFillMode, const wxString &ExePath, const wxString &inputFilename, const wxString &outputFilename, const wxString &compression)
adds the command for the edge fill option
wxArrayString GetNumberedFilename(const wxString &prefix, const wxString &postfix, const HuginBase::UIntSet &img)
returns an array of filenames with numbers
bool AddBlenderCommand(CommandQueue *queue, const wxString &ExePath, const wxString &prog, const int &stepNr, const wxString &arguments, const wxString &description, std::ostream &errStream)
void AddToArray(const wxArrayString &input, wxArrayString &output)
append all strings from input array to output array
wxString GetQuotedFilenamesStringForVerdandi(const wxArrayString &files, const HuginBase::Panorama &pano, const HuginBase::UIntSetVector &stacks, const int referenceImage, const bool hardSeam)
build quoted filename list for verdandi
bool ReplaceWidthHeightPlaceHolder(wxString &args, const wxString name, int value)
str wxEscapeFilename(const str &arg)
special escaping routine for CommandQueues
Definition Executor.h:79
wxString GetExternalProgram(wxConfigBase *config, const wxString &bindir, const wxString &name)
return path and name of external program, which can be overwritten by the user
Definition Executor.cpp:148
wxString GetQuotedFilenamesString(const wxArrayString &files)
return a wxString with all files in files quoted
void CleanQueue(CommandQueue *queue)
clean the queue, delete all entries, but not the queue itself
Definition Executor.cpp:118
CommandQueue * GetStitchingCommandQueueUserOutput(const HuginBase::Panorama &pano, const wxString &ExePath, const wxString &project, const wxString &prefix, const wxString &outputSettings, wxString &statusText, wxArrayString &outputFiles, wxArrayString &tempFilesDelete, std::ostream &errStream)
generates the command queue for stitching a pano, the commands are parsed from the given executor out...
std::vector< NormalCommand * > CommandQueue
Definition Executor.h:61
const wxString GetSettingStringTranslated(wxConfigBase *setting, const wxString &name, const wxString defaultValue)
read a translated string from settings and remove all whitespaces
Definition Executor.cpp:288
wxString GetInternalProgram(const wxString &bindir, const wxString &name)
return path and name of external program, which comes bundled with Hugin
Definition Executor.cpp:129
const wxString GetConfigTempDir(const wxConfigBase *config)
return the temp dir from the preferences, ensure that it ends with path separator
Definition Executor.cpp:302
CommandQueue * GetStitchingCommandQueue(const HuginBase::Panorama &pano, const wxString &ExePath, const wxString &project, const wxString &prefix, wxString &statusText, wxArrayString &outputFiles, wxArrayString &tempFilesDelete, std::ostream &errStream)
generates the command queue for stitching a pano it will also generate the necessary exiftool argfile...
const wxString GetSettingString(wxConfigBase *setting, const wxString &name, const wxString defaultValue)
read a string from setting and remove all whitespaces
Definition Executor.cpp:281
std::string GetDataDir()
returns the full path to the data directory
Definition utils.cpp:441
int roundi(T x)
Definition hugin_math.h:73
std::string GetHuginVersion()
return a string with version numbers
Definition utils.cpp:907
bool set_contains(const _Container &c, const typename _Container::key_type &key)
Definition stl_utils.h:74
void fill_set(_Container &c, typename _Container::key_type begin, typename _Container::key_type end)
Definition stl_utils.h:81
std::vector< deghosting::BImagePtr > threshold(const std::vector< deghosting::FImagePtr > &inputImages, const double threshold, const uint16_t flags)
Threshold function used for creating alpha masks for images.
Definition threshold.h:41
platform/compiler specific stuff.