Hugintrunk  0.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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"
39 #include "hugin/config_defaults.h"
40 
41 namespace HuginQueue
42 {
43  namespace detail
44  {
45  // contains some helper functions
47  wxArrayString GetNumberedFilename(const wxString& prefix, const wxString& postfix, const HuginBase::UIntSet& img)
48  {
49  wxArrayString filenames;
50  for (HuginBase::UIntSet::const_iterator it = img.begin(); it != img.end(); ++it)
51  {
52  filenames.Add(wxString::Format(wxT("%s%04u%s"), prefix.c_str(), *it, postfix.c_str()));
53  };
54  return filenames;
55  };
56 
58  void AddToArray(const wxArrayString& input, wxArrayString& output)
59  {
60  for (size_t i = 0; i < input.size(); ++i)
61  {
62  output.Add(input[i]);
63  };
64  };
65 
69  wxString GenerateFinalArgfile(const HuginBase::Panorama & pano, const wxString& projectName, const wxConfigBase* config, const HuginBase::UIntSet& images, const double exifToolVersion)
70  {
71  wxString argfileInput = config->Read(wxT("/output/FinalArgfile"), wxEmptyString);
72  const bool generateGPanoTags = (config->Read(wxT("/output/writeGPano"), HUGIN_EXIFTOOL_CREATE_GPANO) == 1l) && (exifToolVersion >= 9.09);
73  pano_projection_features proj;
74  const HuginBase::PanoramaOptions &opts = pano.getOptions();
75  const bool readProjectionName = panoProjectionFeaturesQuery(opts.getProjection(), &proj) != 0;
76  // build placeholder map
77  struct Placeholder
78  {
79  wxString placeholder;
80  wxString value;
81  Placeholder(const wxString& holder, const wxString& newValue)
82  {
83  placeholder = holder;
84  value = newValue;
85  };
86  };
87  std::list<Placeholder> placeholders;
88 #ifdef _WIN32
89  const wxString linebreak(wxT("&#xd;&#xa;"));
90 #else
91  const wxString linebreak(wxT("&#xa;"));
92 #endif
93  if (readProjectionName)
94  {
95  // %projectionNumber have to be processed before %projection, otherwise it does not work
96  placeholders.push_back(Placeholder(wxT("%projectionNumber"), wxString::Format(wxT("%d"), opts.getProjection())));
97  placeholders.push_back(Placeholder(wxT("%projection"), wxString(proj.name, wxConvLocal)));
98  };
99  // fill in some placeholders
100  placeholders.push_back(Placeholder(wxT("%hfov"), wxString::Format(wxT("%.0f"), opts.getHFOV())));
101  placeholders.push_back(Placeholder(wxT("%vfov"), wxString::Format(wxT("%.0f"), opts.getVFOV())));
102  placeholders.push_back(Placeholder(wxT("%ev"), wxString::Format(wxT("%.2f"), opts.outputExposureValue)));
103  placeholders.push_back(Placeholder(wxT("%nrImages"), wxString::Format(wxT("%lu"), (unsigned long)images.size())));
104  placeholders.push_back(Placeholder(wxT("%nrAllImages"), wxString::Format(wxT("%lu"), (unsigned long)pano.getNrOfImages())));
105  placeholders.push_back(Placeholder(wxT("%fullwidth"), wxString::Format(wxT("%u"), opts.getWidth())));
106  placeholders.push_back(Placeholder(wxT("%fullheight"), wxString::Format(wxT("%u"), opts.getHeight())));
107  placeholders.push_back(Placeholder(wxT("%width"), wxString::Format(wxT("%d"), opts.getROI().width())));
108  placeholders.push_back(Placeholder(wxT("%height"), wxString::Format(wxT("%d"), opts.getROI().height())));
109  wxFileName projectFilename(projectName);
110  placeholders.push_back(Placeholder(wxT("%projectname"), projectFilename.GetFullName()));
111  // now open the final argfile
112  wxFileName tempArgfileFinal(wxFileName::CreateTempFileName(GetConfigTempDir(config) + wxT("he")));
113  wxFFileOutputStream outputStream(tempArgfileFinal.GetFullPath());
114  wxTextOutputStream outputFile(outputStream);
115  // write argfile
116  outputFile << wxT("-Software=Hugin ") << wxString(hugin_utils::GetHuginVersion().c_str(), wxConvLocal) << endl;
117  outputFile << wxT("-E") << endl;
118  outputFile << wxT("-UserComment<${UserComment}") << linebreak;
119  if (readProjectionName)
120  {
121  outputFile << wxT("Projection: ") << wxString(proj.name, wxConvLocal) << wxT(" (") << opts.getProjection() << wxT(")") << linebreak;
122  };
123  outputFile << wxT("FOV: ") << wxString::Format(wxT("%.0f"), opts.getHFOV()) << wxT(" x ") << wxString::Format(wxT("%.0f"), opts.getVFOV()) << linebreak;
124  outputFile << wxT("Ev: ") << wxString::Format(wxT("%.2f"), opts.outputExposureValue) << endl;
125  outputFile << wxT("-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 << wxT("-CompositeImage=General Composite Image") << endl;
131  outputFile << wxString::Format(wxT("-CompositeImageCount=%lu %lu"), (unsigned long)pano.getNrOfImages(), (unsigned long)pano.getActiveImages().size()) << endl;
132  };
133  if (generateGPanoTags)
134  {
135  //GPano tags are only indented for equirectangular images
137  {
138  const bool isCylindrical = opts.getProjection() == HuginBase::PanoramaOptions::CYLINDRICAL;
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 << wxT("-UsePanoramaViewer=True") << endl;
163  outputFile << wxT("-StitchingSoftware=Hugin") << endl;
164  if (isCylindrical)
165  {
166  outputFile << wxT("-ProjectionType=cylindrical") << endl;
167  }
168  else
169  {
170  outputFile << wxT("-ProjectionType=equirectangular") << endl;
171  };
172  outputFile << wxT("-CroppedAreaLeftPixels=") << left << endl;
173  outputFile << wxT("-CroppedAreaTopPixels=") << top << endl;
174  outputFile << wxT("-CroppedAreaImageWidthPixels=") << width << endl;
175  outputFile << wxT("-CroppedAreaImageHeightPixels=") << height << endl;
176  outputFile << wxT("-FullPanoWidthPixels=") << fullWidth << endl;
177  if (!isCylindrical)
178  {
179  // for cylindrical projection FullPanoHeightPixels is infinity
180  outputFile << wxT("-FullPanoHeightPixels=") << fullHeight << endl;
181  };
182  outputFile << wxT("-SourcePhotosCount=") << static_cast<wxUint32>(pano.getNrOfImages()) << endl;
183  };
184  };
185  // now open the input file and append it
186  if (!argfileInput.IsEmpty())
187  {
188  if (wxFileExists(argfileInput))
189  {
190  wxFileInputStream inputFileStream(argfileInput);
191  wxTextInputStream input(inputFileStream);
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 
208  wxString PrintDetailInfo(const HuginBase::Panorama& pano, const HuginBase::PanoramaOptions& opts, const HuginBase::UIntSet& allActiveImages, const wxString& prefix, const wxString& bindir, wxConfigBase* config, double& exiftoolVersion)
209  {
210  wxString output;
211  const wxString wxEndl(wxT("\n"));
212  output
213  << wxT("============================================") << wxEndl
214  << _("Stitching panorama...") << wxEndl
215  << wxT("============================================") << wxEndl
216  << wxEndl
217  << _("Platform:") << wxT(" ") << wxGetOsDescription() << wxEndl
218  << _("Version:") << wxT(" ") << wxString(hugin_utils::GetHuginVersion().c_str(), wxConvLocal) << wxEndl
219  << _("Working directory:") << wxT(" ") << wxFileName::GetCwd() << wxEndl
220  << _("Output prefix:") << wxT(" ") << prefix << wxEndl
221  << wxEndl;
224  {
225  switch (opts.blendMode)
226  {
228  {
229  wxArrayString version;
230  if (wxExecute(wxEscapeFilename(GetExternalProgram(config, bindir, wxT("enblend"))) + wxT(" --version"), version, wxEXEC_SYNC) == 0l)
231  {
232  output << _("Blender:") << wxT(" ") << version[0] << wxEndl;
233  }
234  else
235  {
236  output << _("Blender:") << wxT(" ") << _("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:") << wxT(" ") << _("internal") << wxEndl;
243  break;
244  };
245  };
247  {
248  wxArrayString version;
249  if (wxExecute(wxEscapeFilename(GetExternalProgram(config, bindir, wxT("enfuse"))) + wxT(" --version"), version, wxEXEC_SYNC) == 0l)
250  {
251  output << _("Exposure fusion:") << wxT(" ") << version[0] << wxEndl;
252  }
253  else
254  {
255  output << _("Exposure fusion:") << wxT(" ") << _("Unknown exposure fusion (enfuse --version failed)") << wxEndl;
256  };
257  };
258  if (config->Read(wxT("/output/useExiftool"), HUGIN_USE_EXIFTOOL) == 1l)
259  {
260  wxArrayString version;
261  if (wxExecute(wxEscapeFilename(GetExternalProgram(config, bindir, wxT("exiftool"))) + wxT(" -ver"), version, wxEXEC_SYNC) == 0l)
262  {
263  output << _("ExifTool version:") << wxT(" ") << version[0] << wxEndl;
264  version[0].ToCDouble(&exiftoolVersion);
265  }
266  else
267  {
268  output << _("ExifTool:") << wxT(" ") << _("FAILED") << wxEndl;
269  exiftoolVersion = 1;
270  };
271  };
272  output
273  << wxEndl
274  << _("Number of active images:") << wxT(" ") << 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()) << wxT(" ") << wxEndl
278  << wxString::Format(_("FOV: %.0fx%.0f"), opts.getHFOV(), opts.getVFOV()) << wxEndl;
279  pano_projection_features proj;
280  const bool readProjectionName = panoProjectionFeaturesQuery(opts.getProjection(), &proj) != 0;
281  if (readProjectionName)
282  {
283  output
284  << _("Projection:") << wxT(" ") << wxGetTranslation(wxString(proj.name, wxConvLocal))
285  << wxT("(") << opts.getProjection() << wxT(")") << wxEndl;
286  }
287  else
288  {
289  output
290  << _("Projection:") << wxT(" ") << opts.getProjection() << wxEndl;
291  };
292  output
293  << _("Using GPU for remapping:") << wxT(" ") << (opts.remapUsingGPU ? _("true") : _("false")) << wxEndl
294  << wxEndl;
296  {
297  output << _("Panorama Outputs:") << wxEndl;
298  if (opts.outputLDRBlended)
299  {
300  output << wxT("* ") << _("Exposure corrected, low dynamic range") << wxEndl;
301  };
302  if (opts.outputLDRExposureBlended)
303  {
304  output << wxT("* ") << _("Exposure fused from stacks") << wxEndl;
305  };
307  {
308  output << wxT("* ") << _("Exposure fused from any arrangement") << wxEndl;
309  };
310  if (opts.outputHDRBlended)
311  {
312  output << wxT("* ") << _("High dynamic range") << wxEndl;
313  };
314  output << wxEndl;
315  };
317  {
318  output << _("Remapped Images:") << wxEndl;
319  if (opts.outputLDRBlended)
320  {
321  output << wxT("* ") << _("Exposure corrected, low dynamic range") << wxEndl;
322  };
323  if (opts.outputLDRExposureRemapped)
324  {
325  output << wxT("* ") << _("No exposure correction, low dynamic range") << wxEndl;
326  };
327  if (opts.outputHDRLayers)
328  {
329  output << wxT("* ") << _("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 << wxT("* ") << _("Exposure fused stacks") << wxEndl;
339  };
340  if (opts.outputHDRStacks)
341  {
342  output << wxT("* ") << _("High dynamic range") << wxEndl;
343  };
344  output << wxEndl;
345  };
346  if (opts.outputLDRExposureLayers)
347  {
348  output << _("Layers:") << wxEndl
349  << wxT("* ") << _("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:") << wxT(" ") << *allActiveImages.begin() << wxEndl
355  << _("Filename:") << wxT(" ") << img.getFilename() << wxEndl
356  << wxString::Format(_("Size: %dx%d"), img.getWidth(), img.getHeight()) << wxEndl
357  << _("Projection:") << wxT(" ") << getProjectionString(img) << wxEndl
358  << _("Response type:") << wxT(" ") << 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 
366  wxString GetQuotedFilenamesStringForVerdandi(const wxArrayString& files, const HuginBase::Panorama& pano, const HuginBase::UIntSetVector& stacks, const int referenceImage, const bool hardSeam)
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
376  HuginBase::UIntSet stackImgs;
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
386  HuginBase::PanoramaData* subpano = pano.getNewSubset(stackImgs);
387  HuginBase::UIntSet subpanoImgs;
388  fill_set(subpanoImgs, 0, stackImgs.size() - 1);
389  // find the blend order
390  HuginBase::UIntVector blendOrder = HuginBase::getEstimatedBlendingOrder(*subpano, subpanoImgs, refImage);
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]]) + wxT(" "));
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, wxT("verdandi")),
408  "--output=" + wxEscapeFilename(outputFilename) + " " + compression + " --seam=blend " + wxEscapeFilename(inputFilename),
409  _("Filling edges...")));
410  }
411  }
412 
413  } // namespace detail
414 
415  CommandQueue* GetStitchingCommandQueue(const HuginBase::Panorama & pano, const wxString& ExePath, const wxString& project, const wxString& prefix, wxString& statusText, wxArrayString& outputFiles, wxArrayString& tempFilesDelete, std::ostream& errStream)
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(wxT("/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;
446  statusText=detail::PrintDetailInfo(pano, opts, allActiveImages, prefix, ExePath, config, exiftoolVersion);
447  // prepare some often needed variables
448  const wxString quotedProject(wxEscapeFilename(project));
449  // prepare nona arguments
450  wxString nonaArgs(wxT("-v "));
451  wxString enLayersCompressionArgs;
452  if (!opts.outputLayersCompression.empty())
453  {
454  nonaArgs.Append(wxT("-z ") + opts.outputLayersCompression + wxT(" "));
455  enLayersCompressionArgs.Append(wxT(" --compression=") + opts.outputLayersCompression + wxT(" "));
456  }
457  else
458  {
459  if (opts.outputImageType == "jpg")
460  {
461  nonaArgs.Append(wxT("-z LZW "));
462  }
463  };
464  if (opts.remapUsingGPU)
465  {
466  nonaArgs.Append(wxT("-g "));
467  };
468  // prepare enblend arguments
469  wxString enblendArgs;
471  {
472  enblendArgs.Append(opts.enblendOptions);
473  if ((opts.getHFOV() == 360.0) && (opts.getWidth()==opts.getROI().width()))
474  {
475  enblendArgs.Append(wxT(" -w"));
476  };
477  const vigra::Rect2D roi (opts.getROI());
478  if (roi.top() != 0 || roi.left() != 0)
479  {
480  enblendArgs << wxT(" -f") << roi.width() << wxT("x") << roi.height() << wxT("+") << roi.left() << wxT("+") << roi.top();
481  }
482  else
483  {
484  enblendArgs << wxT(" -f") << roi.width() << wxT("x") << roi.height();
485  };
486  enblendArgs.Append(wxT(" "));
487  };
488  // prepare internal blending arguments
489  wxString verdandiArgs;
491  {
492  verdandiArgs.Append(opts.verdandiOptions);
493  if ((opts.getHFOV() == 360.0) && (opts.getWidth() == opts.getROI().width()))
494  {
495  verdandiArgs.Append(wxT(" -w"));
496  };
497  };
498  // prepare the compression switches
499  wxString finalCompressionArgs;
500  if (opts.outputImageType == "tif" && !opts.outputImageTypeCompression.empty())
501  {
502  finalCompressionArgs << wxT(" --compression=") << opts.outputImageTypeCompression;
503  }
504  else
505  {
506  if (opts.outputImageType == "jpg")
507  {
508  finalCompressionArgs << wxT(" --compression=") << opts.quality;
509  };
510  };
511  finalCompressionArgs.Append(wxT(" "));
512  // prepare enfuse arguments
513  wxString enfuseArgs(opts.enfuseOptions + wxT(" "));
514  if ((opts.getHFOV() == 360.0) && (opts.getWidth() == opts.getROI().width()))
515  {
516  enfuseArgs.Append(wxT(" -w"));
517  };
518  const vigra::Rect2D roi (opts.getROI());
519  if (roi.top() != 0 || roi.left() != 0)
520  {
521  enfuseArgs << wxT(" -f") << roi.width() << wxT("x") << roi.height() << wxT("+") << roi.left() << wxT("+") << roi.top();
522  }
523  else
524  {
525  enfuseArgs << wxT(" -f") << roi.width() << wxT("x") << roi.height();
526  };
527  enfuseArgs.Append(wxT(" "));
528 
529  // prepare exiftool args
530  const bool copyMetadata = config->Read(wxT("/output/useExiftool"), HUGIN_USE_EXIFTOOL) == 1l;
531  wxString exiftoolArgs;
532  wxString exiftoolArgsFinal;
533  if (copyMetadata)
534  {
535  exiftoolArgs = wxT("-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(wxT(" -WhitePoint -ColorSpace"));
539  wxString exiftoolArgfile = config->Read(wxT("/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  };
544  wxFileName argfile(exiftoolArgfile);
545  argfile.Normalize(wxPATH_NORM_ABSOLUTE | wxPATH_NORM_DOTS | wxPATH_NORM_TILDE | wxPATH_NORM_SHORTCUT);
546  exiftoolArgs.Append(wxT(" -@ ") + wxEscapeFilename(argfile.GetFullPath()) + wxT(" "));
547  wxString finalArgfile = detail::GenerateFinalArgfile(pano, project, config, allActiveImages, exiftoolVersion);
548  if (!finalArgfile.IsEmpty())
549  {
550  exiftoolArgsFinal.Append(wxT(" -@ ") + wxEscapeFilename(finalArgfile) + wxT(" "));
551  tempFilesDelete.Add(finalArgfile);
552  };
553  };
554  wxArrayString filesForFullExiftool;
555  wxArrayString filesForCopyTagsExiftool;
556  // fill edge related options
557  const bool doEdgeFill = (opts.edgeFillMode != HuginBase::PanoramaOptions::NO_EDGE_FILL);
558 
559  // normal output
560  if (opts.outputLDRBlended || opts.outputLDRLayers)
561  {
562  const wxArrayString remappedImages(detail::GetNumberedFilename(prefix, wxT(".tif"), allActiveImages));
563  const wxString finalFilename(prefix + wxT(".") + opts.outputImageType);
565  {
566  wxString finalNonaArgs(wxT("-v -r ldr "));
567  if (opts.remapUsingGPU)
568  {
569  finalNonaArgs.Append(wxT("-g "));
570  }
571  if (!opts.verdandiOptions.empty())
572  {
573  finalNonaArgs.Append(opts.verdandiOptions);
574  finalNonaArgs.Append(wxT(" "));
575  };
576  wxString edgeInputFile;
577  if (doEdgeFill)
578  {
579  finalNonaArgs.Append("-m TIFF -z " + opts.tiffCompression + " --final-suffix=_nofill ");
580  edgeInputFile = prefix + "_nofill.tif";
581  outputFiles.Add(edgeInputFile);
582  if (opts.keepEdgeFillInput)
583  {
584  if (copyMetadata)
585  {
586  filesForFullExiftool.Add(edgeInputFile);
587  };
588  }
589  else
590  {
591  tempFilesDelete.Add(edgeInputFile);
592  };
593  }
594  else
595  {
596  if (opts.outputImageType == "tif")
597  {
598  finalNonaArgs.Append(wxT("-m TIFF "));
599  if (!opts.outputImageTypeCompression.empty())
600  {
601  finalNonaArgs.Append(wxT("-z ") + opts.outputImageTypeCompression + wxT(" "));
602  };
603  }
604  else
605  {
606  if (opts.outputImageType == "jpg")
607  {
608  finalNonaArgs.Append(wxT("-m JPEG -z "));
609  finalNonaArgs << opts.quality << wxT(" ");
610  }
611  else
612  {
613  if (opts.outputImageType == "png")
614  {
615  finalNonaArgs.Append(wxT("-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(wxT("--save-intermediate-images "));
628  detail::AddToArray(remappedImages, outputFiles);
629  }
630  finalNonaArgs.Append(wxT("-o ") + wxEscapeFilename(prefix) + wxT(" ") + quotedProject);
631  commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")),
632  finalNonaArgs, _("Remapping and blending LDR images...")));
633  outputFiles.Add(finalFilename);
634  if (doEdgeFill)
635  {
636  detail::AddEdgeFillCommand(commands, opts.edgeFillMode, ExePath, edgeInputFile, finalFilename, finalCompressionArgs);
637  };
638  if (copyMetadata)
639  {
640  filesForFullExiftool.Add(finalFilename);
641  };
642  }
643  else
644  {
645  commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")),
646  nonaArgs + wxT("-r ldr -m TIFF_m -o ") + wxEscapeFilename(prefix) + wxT(" ") + quotedProject,
647  _("Remapping LDR images...")));
648  detail::AddToArray(remappedImages, outputFiles);
649  if (opts.outputLDRBlended)
650  {
652  {
653  wxString finalEnblendArgs(enblendArgs);
654  wxString edgeFillInput;
655  if (doEdgeFill)
656  {
657  edgeFillInput = prefix + "_nofill.tif";
658  finalEnblendArgs.Append("--compression=" + opts.tiffCompression + " -o " + wxEscapeFilename(edgeFillInput) + " -- ");
659  }
660  else
661  {
662  finalEnblendArgs.Append(finalCompressionArgs + wxT(" -o ") + wxEscapeFilename(finalFilename) + wxT(" -- "));
663  };
664  commands->push_back(new NormalCommand(
665  GetExternalProgram(config, ExePath, wxT("enblend")),
666  finalEnblendArgs + wxT(" ") + GetQuotedFilenamesString(remappedImages),
667  _("Blending images..."))
668  );
669  if (doEdgeFill)
670  {
671  detail::AddEdgeFillCommand(commands, opts.edgeFillMode, ExePath, edgeFillInput, finalFilename, finalCompressionArgs);
672  outputFiles.Add(edgeFillInput);
673  if (!opts.keepEdgeFillInput)
674  {
675  tempFilesDelete.Add(edgeFillInput);
676  };
677  };
678  outputFiles.Add(finalFilename);
679  if (copyMetadata)
680  {
681  if (doEdgeFill && opts.keepEdgeFillInput)
682  {
683  filesForFullExiftool.Add(edgeFillInput);
684  };
685  filesForFullExiftool.Add(finalFilename);
686  };
687  };
688  if (!opts.outputLDRLayers)
689  {
690  detail::AddToArray(remappedImages, tempFilesDelete);
691  };
692  };
693  };
694  };
695  // exposure fusion output
698  {
699  const wxArrayString remappedImages = detail::GetNumberedFilename(prefix + wxT("_exposure_layers_"), wxT(".tif"), allActiveImages);
700  std::vector<HuginBase::UIntSet> exposureLayers;
701  wxArrayString exposureLayersFiles;
703  {
704  exposureLayers = getExposureLayers(pano, allActiveImages, opts);
705  };
708  {
709  // directly export exposure layers by nona
710  wxString finalNonaArgs(nonaArgs);
711  if (!opts.verdandiOptions.empty())
712  {
713  finalNonaArgs.Append(opts.verdandiOptions);
714  finalNonaArgs.Append(wxT(" "));
715  };
716  finalNonaArgs.append(wxT("-r ldr --create-exposure-layers --ignore-exposure -o ") + wxEscapeFilename(prefix + wxT("_exposure_")));
718  {
719  finalNonaArgs.append(wxT(" --save-intermediate-images --intermediate-suffix=layers_"));
720  detail::AddToArray(remappedImages, outputFiles);
721  if (!opts.outputLDRExposureRemapped)
722  {
723  detail::AddToArray(remappedImages, tempFilesDelete);
724  }
725  };
726  finalNonaArgs.append(wxT(" "));
727  finalNonaArgs.append(quotedProject);
728  commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")),
729  finalNonaArgs, _("Remapping LDR images and blending exposure layers...")));
730  HuginBase::UIntSet exposureLayersNumber;
731  fill_set(exposureLayersNumber, 0, exposureLayers.size() - 1);
732  exposureLayersFiles = detail::GetNumberedFilename(prefix + wxT("_exposure_"), wxT(".tif"), exposureLayersNumber);
733  detail::AddToArray(exposureLayersFiles, outputFiles);
734  if (!opts.outputLDRExposureLayers)
735  {
736  detail::AddToArray(exposureLayersFiles, tempFilesDelete);
737  };
738  if (copyMetadata && opts.outputLDRExposureLayers)
739  {
740  detail::AddToArray(exposureLayersFiles, filesForCopyTagsExiftool);
741  };
742  }
743  else
744  {
745  commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")),
746  nonaArgs + wxT("-r ldr -m TIFF_m --ignore-exposure -o ") + wxEscapeFilename(prefix + wxT("_exposure_layers_")) + wxT(" ") + quotedProject,
747  _("Remapping LDR images without exposure correction...")));
748  detail::AddToArray(remappedImages, outputFiles);
749  if (!opts.outputLDRExposureRemapped)
750  {
751  detail::AddToArray(remappedImages, tempFilesDelete);
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 + wxT("_exposure_layers_"), wxT(".tif"), exposureLayers[exposureLayer]);
760  const wxString exposureLayerImgName = wxString::Format(wxT("%s_exposure_%04u%s"), prefix.c_str(), exposureLayer, wxT(".tif"));
761  exposureLayersFiles.Add(exposureLayerImgName);
762  outputFiles.Add(exposureLayerImgName);
763  // variant with internal blender is handled before, so we need only enblend
764  commands->push_back(new NormalCommand(
765  GetExternalProgram(config, ExePath, wxT("enblend")),
766  enblendArgs + enLayersCompressionArgs + wxT(" -o ") + wxEscapeFilename(exposureLayerImgName) + wxT(" -- ") + GetQuotedFilenamesString(exposureLayersImgs),
767  wxString::Format(_("Blending exposure layer %u..."), exposureLayer))
768  );
769  if (copyMetadata && opts.outputLDRExposureLayers)
770  {
771  filesForCopyTagsExiftool.Add(exposureLayerImgName);
772  };
773  if (!opts.outputLDRExposureLayers)
774  {
775  tempFilesDelete.Add(exposureLayerImgName);
776  };
777  };
778  };
779  };
781  {
782  const wxString fusedExposureLayersFilename(prefix + wxT("_blended_fused.") + opts.outputImageType);
783  wxString finalEnfuseArgs(enfuseArgs);
784  wxString edgeFillInput;
785  if (doEdgeFill)
786  {
787  edgeFillInput = prefix + "_blended_fused_nofill.tif";
788  finalEnfuseArgs.Append("--compression=" + opts.tiffCompression + " -o " + wxEscapeFilename(edgeFillInput) + " -- ");
789  }
790  else
791  {
792  finalEnfuseArgs.Append(finalCompressionArgs + wxT(" -o ") + wxEscapeFilename(fusedExposureLayersFilename) + wxT(" -- "));
793  };
794  commands->push_back(new NormalCommand(
795  GetExternalProgram(config, ExePath, wxT("enfuse")),
796  finalEnfuseArgs + wxT(" ")+GetQuotedFilenamesString(exposureLayersFiles),
797  _("Fusing all exposure layers..."))
798  );
799  outputFiles.Add(fusedExposureLayersFilename);
800  if (doEdgeFill)
801  {
802  detail::AddEdgeFillCommand(commands, opts.edgeFillMode, ExePath, edgeFillInput, fusedExposureLayersFilename, finalCompressionArgs);
803  outputFiles.Add(edgeFillInput);
804  if (!opts.keepEdgeFillInput)
805  {
806  tempFilesDelete.Add(edgeFillInput);
807  };
808  };
809  if (copyMetadata)
810  {
811  if (doEdgeFill && opts.keepEdgeFillInput)
812  {
813  filesForFullExiftool.Add(edgeFillInput);
814  };
815  filesForFullExiftool.Add(fusedExposureLayersFilename);
816  };
817  };
818  if (opts.outputLDRStacks || opts.outputLDRExposureBlended)
819  {
820  // fusing stacks, then blending
821  stacks = getHDRStacks(pano, allActiveImages, opts);
822  wxArrayString stackedImages;
823  // fuse all stacks
824  for (unsigned stackNr = 0; stackNr < stacks.size(); ++stackNr)
825  {
826  const wxArrayString stackImgs = detail::GetNumberedFilename(prefix + wxT("_exposure_layers_"), wxT(".tif"), stacks[stackNr]);
827  const wxString stackImgName = wxString::Format(wxT("%s_stack_ldr_%04u%s"), prefix.c_str(), stackNr, wxT(".tif"));
828  outputFiles.Add(stackImgName);
829  stackedImages.Add(stackImgName);
830  commands->push_back(new NormalCommand(
831  GetExternalProgram(config, ExePath, wxT("enfuse")),
832  enfuseArgs + enLayersCompressionArgs + wxT(" -o ") + wxEscapeFilename(stackImgName) + wxT(" -- ") + GetQuotedFilenamesString(stackImgs),
833  wxString::Format(_("Fusing stack number %u..."), stackNr))
834  );
835  if (copyMetadata && opts.outputLDRStacks)
836  {
837  filesForCopyTagsExiftool.Add(stackImgName);
838  };
839  if (!opts.outputLDRStacks)
840  {
841  tempFilesDelete.Add(stackImgName);
842  };
843  };
844  if (opts.outputLDRExposureBlended)
845  {
846  const wxString fusedStacksFilename(prefix + wxT("_fused.") + opts.outputImageType);
847  wxString edgeFillInput;
848  if (doEdgeFill)
849  {
850  edgeFillInput = prefix + "_fused_nofill.tif";
851  };
852  switch (opts.blendMode)
853  {
855  {
856  wxString finalEnblendArgs(enblendArgs);
857  if (doEdgeFill)
858  {
859  finalEnblendArgs.Append("--compression=" + opts.tiffCompression + " -o " + wxEscapeFilename(edgeFillInput) + "-- ");
860  }
861  else
862  {
863  finalEnblendArgs.Append(finalCompressionArgs + wxT(" -o ") + wxEscapeFilename(fusedStacksFilename) + wxT(" -- "));
864  };
865  commands->push_back(new NormalCommand(
866  GetExternalProgram(config, ExePath, wxT("enblend")),
867  finalEnblendArgs+wxT(" ")+GetQuotedFilenamesString(stackedImages),
868  _("Blending all stacks..."))
869  );
870  };
871  break;
873  default: // switch to internal blender for all other cases, not exposed in GUI
874  {
875  wxString finalVerdandiArgs(verdandiArgs);
876  if (doEdgeFill)
877  {
878  finalVerdandiArgs.Append("--compression=" + opts.tiffCompression + " -o" + wxEscapeFilename(edgeFillInput));
879  }
880  else
881  {
882  finalVerdandiArgs.Append(finalCompressionArgs + wxT(" -o ") + wxEscapeFilename(fusedStacksFilename));
883  };
884  finalVerdandiArgs.Append(wxT(" -- ") + detail::GetQuotedFilenamesStringForVerdandi(stackedImages, pano, stacks, opts.colorReferenceImage, opts.verdandiOptions.find("--seam=blend") == std::string::npos));
885  commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("verdandi")),
886  finalVerdandiArgs, _("Blending all stacks...")));
887  };
888  break;
889  };
890  outputFiles.Add(fusedStacksFilename);
891  if (doEdgeFill)
892  {
893  detail::AddEdgeFillCommand(commands, opts.edgeFillMode, ExePath, edgeFillInput, fusedStacksFilename, finalCompressionArgs);
894  outputFiles.Add(edgeFillInput);
895  if (!opts.keepEdgeFillInput)
896  {
897  tempFilesDelete.Add(edgeFillInput);
898  };
899  };
900  if (copyMetadata)
901  {
902  if (doEdgeFill && opts.keepEdgeFillInput)
903  {
904  filesForFullExiftool.Add(edgeFillInput);
905  };
906  filesForFullExiftool.Add(fusedStacksFilename);
907  };
908  };
909  };
910  };
911  // hdr output
912  if (opts.outputHDRLayers || opts.outputHDRStacks || opts.outputHDRBlended)
913  {
914  commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")),
915  nonaArgs + wxT("-r hdr -m EXR_m -o ") + wxEscapeFilename(prefix + wxT("_hdr_")) + wxT(" ") + quotedProject,
916  _("Remapping HDR images...")));
917  const wxArrayString remappedHDR = detail::GetNumberedFilename(prefix + wxT("_hdr_"), wxT(".exr"), allActiveImages);
918  const wxArrayString remappedHDRComp = detail::GetNumberedFilename(prefix + wxT("_hdr_"), wxT("_gray.pgm"), allActiveImages);
919  detail::AddToArray(remappedHDR, outputFiles);
920  detail::AddToArray(remappedHDRComp, outputFiles);
921  if (opts.outputHDRStacks || opts.outputHDRBlended)
922  {
923  // merging stacks, then blending
924  if (stacks.empty())
925  {
926  stacks = getHDRStacks(pano, allActiveImages, opts);
927  };
928  wxArrayString stackedImages;
929  // merge all stacks
930  for (unsigned stackNr = 0; stackNr < stacks.size(); ++stackNr)
931  {
932  const wxArrayString stackImgs = detail::GetNumberedFilename(prefix + wxT("_hdr_"), wxT(".exr"), stacks[stackNr]);
933  const wxString stackImgName = wxString::Format(wxT("%s_stack_hdr_%04u%s"), prefix.c_str(), stackNr, wxT(".exr"));
934  stackedImages.Add(stackImgName);
935  outputFiles.Add(stackImgName);
936  commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("hugin_hdrmerge")),
937  opts.hdrmergeOptions + wxT(" -o ") + wxEscapeFilename(stackImgName) + wxT(" -- ") + GetQuotedFilenamesString(stackImgs),
938  wxString::Format(_("Merging HDR stack number %u..."), stackNr)));
939  if (!opts.outputHDRStacks)
940  {
941  tempFilesDelete.Add(stackImgName);
942  };
943  };
944  if (opts.outputHDRBlended)
945  {
946  const wxString mergedStacksFilename(prefix + wxT("_hdr.") + opts.outputImageTypeHDR);
947  wxString finalBlendArgs;
948  wxString edgeFillInput;
949  if (doEdgeFill)
950  {
951  edgeFillInput = prefix + "_hdr_nofill.exr";
952  finalBlendArgs.Append(wxT(" -o ") + wxEscapeFilename(edgeFillInput) + wxT(" -- "));
953  }
954  else
955  {
956  finalBlendArgs.Append(wxT(" -o ") + wxEscapeFilename(mergedStacksFilename) + wxT(" -- "));
957  };
958  switch (opts.blendMode)
959  {
961  commands->push_back(new NormalCommand(
962  GetExternalProgram(config, ExePath, wxT("enblend")),
963  enblendArgs + finalBlendArgs + wxT(" ") + GetQuotedFilenamesString(stackedImages),
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, wxT("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  };
974  outputFiles.Add(mergedStacksFilename);
975  if (doEdgeFill)
976  {
977  detail::AddEdgeFillCommand(commands, opts.edgeFillMode, ExePath, edgeFillInput, mergedStacksFilename, finalCompressionArgs);
978  outputFiles.Add(edgeFillInput);
979  if (!opts.keepEdgeFillInput)
980  {
981  tempFilesDelete.Add(edgeFillInput);
982  };
983  };
984  if (copyMetadata)
985  {
986  if (doEdgeFill && opts.keepEdgeFillInput)
987  {
988  filesForFullExiftool.Add(edgeFillInput);
989  };
990  filesForFullExiftool.Add(mergedStacksFilename);
991  };
992  };
993  };
994  if (!opts.outputHDRLayers)
995  {
996  detail::AddToArray(remappedHDR, tempFilesDelete);
997  detail::AddToArray(remappedHDRComp, tempFilesDelete);
998  };
999  };
1000  // update metadata
1001  if (!filesForCopyTagsExiftool.IsEmpty())
1002  {
1003  commands->push_back(new OptionalCommand(GetExternalProgram(config, ExePath, wxT("exiftool")),
1004  exiftoolArgs + GetQuotedFilenamesString(filesForCopyTagsExiftool),
1005  _("Updating metadata...")));
1006  };
1007  if (!filesForFullExiftool.IsEmpty())
1008  {
1009  commands->push_back(new OptionalCommand(GetExternalProgram(config, ExePath, wxT("exiftool")),
1010  exiftoolArgs + exiftoolArgsFinal + GetQuotedFilenamesString(filesForFullExiftool),
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
1034  wxString program;
1035  if (prog.CmpNoCase(wxT("verdandi")) == 0)
1036  {
1037  program = GetInternalProgram(ExePath, wxT("verdandi"));
1038  }
1039  else
1040  {
1041  if (prog.CmpNoCase(wxT("hugin_hdrmerge")) == 0)
1042  {
1043  program = GetInternalProgram(ExePath, wxT("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
1058  bool ReplacePrefixPlaceholder(wxString& args, const wxString prefix)
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 
1130  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)
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  }
1139  wxFileInputStream input(outputSettings);
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(wxT("/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, wxT("/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, wxT("/General/IntermediateImageType"), wxT(".tif"));
1163  // add point if missing
1164  if (intermediateImageType.Left(1).Cmp(wxT("."))!=0)
1165  {
1166  intermediateImageType.Prepend(wxT("."));
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());
1172  wxString sizeString;
1173  if (roi.top() != 0 || roi.left() != 0)
1174  {
1175  sizeString << roi.width() << wxT("x") << roi.height() << wxT("+") << roi.left() << wxT("+") << roi.top();
1176  }
1177  else
1178  {
1179  sizeString << roi.width() << wxT("x") << roi.height();
1180  };
1181  const wxArrayString remappedImages(detail::GetNumberedFilename(prefix, intermediateImageType, allActiveImages));
1182  wxArrayString inputImages;
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;
1189  wxArrayString exposureLayersFiles;
1190 
1191  std::vector<HuginBase::UIntSet> stacks;
1192  wxArrayString stacksFiles;
1193 
1194  // now iterate all steps
1195  for (size_t i = 0; i < stepCount; ++i)
1196  {
1197  wxString stepString(wxT("/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, wxT("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, wxT("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, wxT("Description"));
1221  if (stepType.CmpNoCase(wxT("remap")) == 0)
1222  {
1223  // build nona command
1224  const bool outputLayers = (settings.Read(wxT("OutputExposureLayers"), 0l) == 1l);
1225  if (outputLayers)
1226  {
1227  args.Append(wxT(" --create-exposure-layers -o ") + wxEscapeFilename(prefix + wxT("_layer")));
1228  }
1229  else
1230  {
1231  args.Append(wxT(" -o ") + wxEscapeFilename(prefix));
1232  };
1233  args.Append(wxT(" ") + wxEscapeFilename(project));
1234  commands->push_back(new NormalCommand(GetInternalProgram(ExePath, wxT("nona")),
1235  args, description));
1236  if (outputLayers)
1237  {
1238  if (exposureLayers.empty())
1239  {
1240  exposureLayers = getExposureLayers(pano, allActiveImages, opts);
1241  HuginBase::UIntSet exposureLayersNumber;
1242  fill_set(exposureLayersNumber, 0, exposureLayers.size() - 1);
1243  exposureLayersFiles = detail::GetNumberedFilename(prefix + wxT("_layer"), intermediateImageType, exposureLayersNumber);
1244  };
1245  detail::AddToArray(exposureLayersFiles, outputFiles);
1246  if (settings.Read(wxT("Keep"), 0l) == 0l)
1247  {
1248  detail::AddToArray(exposureLayersFiles, tempFilesDelete);
1249  };
1250  }
1251  else
1252  {
1253  const wxArrayString remappedHDRComp = detail::GetNumberedFilename(prefix, wxT("_gray.pgm"), allActiveImages);
1254  const bool hdrOutput = args.MakeLower().Find(wxT("-r hdr")) != wxNOT_FOUND;
1255  detail::AddToArray(remappedImages, outputFiles);
1256  if (hdrOutput)
1257  {
1258  detail::AddToArray(remappedHDRComp, outputFiles);
1259  };
1260  if (settings.Read(wxT("Keep"), 0l) == 0l)
1261  {
1262  detail::AddToArray(remappedImages, tempFilesDelete);
1263  if (hdrOutput)
1264  {
1265  detail::AddToArray(remappedHDRComp, tempFilesDelete);
1266  };
1267  };
1268  };
1269  }
1270  else
1271  {
1272  if (stepType.CmpNoCase(wxT("merge")) == 0)
1273  {
1274  // build a merge command
1275  wxString resultFile = GetSettingString(&settings, wxT("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(wxT("%prefix%"), prefix, true);
1283  if (args.Replace(wxT("%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, wxT("Input"), wxT("all"));
1290  // set the input images depending on the input
1291  if (BlenderInput.CmpNoCase(wxT("all")) == 0)
1292  {
1293  if (args.Replace(wxT("%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(wxT("stacks")) == 0)
1303  {
1304  if (stacks.empty())
1305  {
1306  stacks= HuginBase::getHDRStacks(pano, allActiveImages, opts);
1307  HuginBase::UIntSet stackNumbers;
1308  fill_set(stackNumbers, 0, stacks.size() - 1);
1309  stacksFiles = detail::GetNumberedFilename(prefix + wxT("_stack"), intermediateImageType, stackNumbers);
1310  };
1311  if (args.Replace(wxT("%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(wxT("layers")) == 0)
1321  {
1322  if (exposureLayers.empty())
1323  {
1324  exposureLayers = getExposureLayers(pano, allActiveImages, opts);
1325  HuginBase::UIntSet exposureLayersNumber;
1326  fill_set(exposureLayersNumber, 0, exposureLayers.size() - 1);
1327  exposureLayersFiles = detail::GetNumberedFilename(prefix + wxT("_layer"), intermediateImageType, exposureLayersNumber);
1328  };
1329  if (args.Replace(wxT("%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(wxT("%size%"), sizeString, true);
1345  wxString wrapSwitch = GetSettingString(&settings, wxT("WrapArgument"));
1346  if (needsWrapSwitch && !wrapSwitch.IsEmpty())
1347  {
1348  args.Prepend(wrapSwitch + wxT(" "));
1349  }
1350  if (!detail::AddBlenderCommand(commands, ExePath, GetSettingString(&settings, wxT("Program")), i,
1351  args, description, errStream))
1352  {
1353  return commands;
1354  };
1355  outputFiles.Add(resultFile);
1356  if (settings.Read(wxT("Keep"), 1l) == 0l)
1357  {
1358  tempFilesDelete.Add(resultFile);
1359  };
1360  }
1361  else
1362  {
1363  if (stepType.CmpNoCase(wxT("stack")) == 0)
1364  {
1365  // build command for each stack
1366  if (stacks.empty())
1367  {
1368  stacks = HuginBase::getHDRStacks(pano, allActiveImages, opts);
1369  HuginBase::UIntSet stackNumbers;
1370  fill_set(stackNumbers, 0, stacks.size() - 1);
1371  stacksFiles = detail::GetNumberedFilename(prefix + wxT("_stack"), intermediateImageType, stackNumbers);
1372  };
1373  const bool clean = (settings.Read(wxT("Keep"), 0l) == 0l);
1374  args.Replace(wxT("%size%"), sizeString, true);
1375  // now iterate each stack
1376  for (size_t stackNr = 0; stackNr < stacks.size(); ++stackNr)
1377  {
1378  wxString finalArgs(args);
1379  wxArrayString remappedStackImages = detail::GetNumberedFilename(prefix, intermediateImageType, stacks[stackNr]);
1380  if (finalArgs.Replace(wxT("%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(wxT("%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, wxT("Program")), i,
1393  finalArgs, description, errStream))
1394  {
1395  return commands;
1396  };
1397  outputFiles.Add(stacksFiles[stackNr]);
1398  if (clean)
1399  {
1400  tempFilesDelete.Add(stacksFiles[stackNr]);
1401  };
1402  }
1403  }
1404  else
1405  {
1406  if (stepType.CmpNoCase(wxT("layer")) == 0)
1407  {
1408  // build command for each exposure layer
1409  if (exposureLayers.empty())
1410  {
1411  exposureLayers = HuginBase::getExposureLayers(pano, allActiveImages, opts);
1412  HuginBase::UIntSet exposureLayersNumber;
1413  fill_set(exposureLayersNumber, 0, exposureLayers.size() - 1);
1414  exposureLayersFiles = detail::GetNumberedFilename(prefix + wxT("_layer"), intermediateImageType, exposureLayersNumber);
1415  };
1416  const bool clean = (settings.Read(wxT("Keep"), 0l) == 0l);
1417  args.Replace(wxT("%size%"), sizeString, true);
1418  // iterate all exposure layers
1419  for (size_t exposureLayerNr = 0; exposureLayerNr < exposureLayers.size(); ++exposureLayerNr)
1420  {
1421  wxString finalArgs(args);
1422  wxArrayString remappedLayerImages = detail::GetNumberedFilename(prefix, intermediateImageType, exposureLayers[exposureLayerNr]);
1423  if (finalArgs.Replace(wxT("%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(wxT("%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, wxT("Program")), i,
1436  finalArgs, description, errStream))
1437  {
1438  return commands;
1439  };
1440  outputFiles.Add(exposureLayersFiles[exposureLayerNr]);
1441  if (clean)
1442  {
1443  tempFilesDelete.Add(exposureLayersFiles[exposureLayerNr]);
1444  };
1445  }
1446  }
1447  else
1448  {
1449  if (stepType.CmpNoCase(wxT("modify")) == 0)
1450  {
1451  // build a modify command
1452  wxString inputFiles = GetSettingString(&settings, wxT("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(wxT("%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(wxT("%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, wxT("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(wxT("all")) == 0)
1501  {
1502  for (size_t imgNr = 0; imgNr < remappedImages.size(); ++imgNr)
1503  {
1504  wxString finalArgs(args);
1505  finalArgs.Replace(wxT("%file%"), wxEscapeFilename(remappedImages[imgNr]), true);
1506  finalArgs.Replace(wxT("%sourceimage%"), wxEscapeFilename(inputImages[imgNr]), true);
1507  commands->push_back(new NormalCommand(prog, finalArgs, description));
1508  };
1509  }
1510  else
1511  {
1512  if (inputFiles.CmpNoCase(wxT("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(wxT("%file%"), wxEscapeFilename(stacksFiles[stackNr]), true);
1524  commands->push_back(new NormalCommand(prog, finalArgs, description));
1525  };
1526  }
1527  else
1528  {
1529  if (inputFiles.CmpNoCase(wxT("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(wxT("%file%"), wxEscapeFilename(exposureLayersFiles[layerNr]), true);
1541  commands->push_back(new NormalCommand(prog, finalArgs, description));
1542  };
1543  }
1544  else
1545  {
1546  inputFiles.Replace(wxT("%prefix%"), prefix, true);
1547  args.Replace(wxT("%file%"), wxEscapeFilename(inputFiles), true);
1548  commands->push_back(new NormalCommand(prog , args, description));
1549  };
1550  };
1551  };
1552  }
1553  else
1554  {
1555  if (stepType.CmpNoCase(wxT("exiftool")) == 0)
1556  {
1557  wxString resultFile = GetSettingString(&settings, wxT("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(wxT("%prefix%"), prefix, true);
1565  if (args.Replace(wxT("%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(wxT("%image0%"), wxEscapeFilename(wxString(pano.getImage(0).getFilename().c_str(), HUGIN_CONV_FILENAME)), true);
1572  commands->push_back(new OptionalCommand(GetExternalProgram(wxConfigBase::Get(), ExePath, wxT("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 
1591  wxString GetQuotedFilenamesString(const wxArrayString& files)
1592  {
1593  wxString s;
1594  for (size_t i = 0; i < files.size(); ++i)
1595  {
1596  s.Append(wxEscapeFilename(files[i]) + wxT(" "));
1597  };
1598  return s;
1599  };
1600 
1601 }; // namespace
const wxString GetSettingString(wxConfigBase *setting, const wxString &name, const wxString defaultValue)
read a string from setting and remove all whitespaces
Definition: Executor.cpp:281
normal command for queue, processing is stopped if an error occurred in program
Definition: Executor.h:37
PanoramaOptions::ProjectionFormat getProjection() const
std::vector< UIntSet > getHDRStacks(const PanoramaData &pano, UIntSet allImgs, PanoramaOptions opts)
returns vector of set of output stacks
Definition: LayerStacks.cpp:35
implementation of huginApp Class
declaration of functions to handle stacks and layers
std::vector< UIntSet > UIntSetVector
Definition: PanoramaData.h:56
std::vector< UIntSet > getExposureLayers(const PanoramaData &pano, UIntSet allImgs, PanoramaOptions opts)
returns vector of set of output exposure layers
Definition: LayerStacks.cpp:96
const wxString GetConfigTempDir(const wxConfigBase *config)
return the temp dir from the preferences, ensure that it ends with path separator ...
Definition: Executor.cpp:302
int roundi(T x)
Definition: hugin_math.h:73
PanoramaData * getNewSubset(const UIntSet &imgs) const
Definition: Panorama.h:183
bool outputLDRLayers
save remapped layers (LDR)
optional command for queue, processing of queue is always continued, also if an error occurred ...
Definition: Executor.h:53
interface of CommandQueue creating for stitching engine
#define HUGIN_CONV_FILENAME
Definition: platform.h:40
bool outputHDRLayers
save remapped layers (HDR)
unsigned int getHeight() const
get panorama height
#define HUGIN_EXIFTOOL_CREATE_GPANO
UIntSet getImagesinROI(const PanoramaData &pano, const UIntSet activeImages)
returns set of images which are visible in output ROI
some helper classes for graphes
void AddToArray(const wxArrayString &input, wxArrayString &output)
append all strings from input array to output array
#define HUGIN_USE_EXIFTOOL
wxString GetInternalProgram(const wxString &bindir, const wxString &name)
return path and name of external program, which comes bundled with Hugin
Definition: Executor.cpp:129
bool set_contains(const _Container &c, const typename _Container::key_type &key)
Definition: stl_utils.h:74
static char * line
Definition: svm.cpp:2784
int getHeight() const
Get the height of the image in pixels.
Definition: SrcPanoImage.h:276
std::string outputImageTypeCompression
bool outputLDRExposureBlended
&lt; save exposure fused stacks (no exposure adjustment)
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
UIntVector getEstimatedBlendingOrder(const PanoramaData &pano, const UIntSet &images, const unsigned int referenceImage)
returns vector of image numbers for blending in approbiate order
std::set< unsigned int > UIntSet
Definition: PanoramaData.h:51
wxString GenerateFinalArgfile(const HuginBase::Panorama &pano, const wxString &projectName, const wxConfigBase *config, const HuginBase::UIntSet &images, const double exifToolVersion)
generate the final argfile
wxString getResponseString(const HuginBase::SrcPanoImage &img)
Returns translated response type for given SrcPanoImage.
Definition: LensTools.cpp:125
const vigra::Rect2D & getROI() const
Model for a panorama.
Definition: Panorama.h:152
std::string GetDataDir()
returns the full path to the data directory
Definition: utils.cpp:441
bool outputLDRBlended
save blended panorama (LDR)
std::size_t getNrOfImages() const
number of images.
Definition: Panorama.h:205
bool ReplaceWidthHeightPlaceHolder(wxString &args, const wxString name, int value)
std::vector< unsigned int > UIntVector
Definition: PanoramaData.h:54
int getWidth() const
Get the width of the image in pixels.
Definition: SrcPanoImage.h:266
wxString GetQuotedFilenamesString(const wxArrayString &files)
return a wxString with all files in files quoted
bool outputHDRBlended
save blended panorama (HDR)
Model for a panorama.
Definition: PanoramaData.h:81
#define HUGIN_NONA_USEGPU
bool ReplacePrefixPlaceholder(wxString &args, const wxString prefix)
UIntSet getActiveImages() const
get active images
Definition: Panorama.cpp:1585
Utility calls into PanoTools using CPP interface.
bool outputLDRExposureLayers
save blended exposure layers, do not perform fusion (no exposure adjustment)
void CleanQueue(CommandQueue *queue)
clean the queue, delete all entries, but not the queue itself
Definition: Executor.cpp:118
unsigned int getWidth() const
bool AddBlenderCommand(CommandQueue *queue, const wxString &ExePath, const wxString &prog, const int &stepNr, const wxString &arguments, const wxString &description, std::ostream &errStream)
const PanoramaOptions & getOptions() const
returns the options for this panorama
Definition: Panorama.h:481
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
str wxEscapeFilename(const str &arg)
special escaping routine for CommandQueues
Definition: Executor.h:79
bool outputLDRExposureRemapped
save remapped layers (no exposure adjustment)
bool outputLDRExposureLayersFused
save blended exposure layers which are then fused (no exposure adjustment)
std::string GetHuginVersion()
return a string with version numbers
Definition: utils.cpp:920
platform/compiler specific stuff.
const SrcPanoImage & getImage(std::size_t nr) const
get a panorama image, counting starts with 0
Definition: Panorama.h:211
void fill_set(_Container &c, typename _Container::key_type begin, typename _Container::key_type end)
Definition: stl_utils.h:81
All variables of a source image.
Definition: SrcPanoImage.h:194
Panorama image options.
bool outputHDRStacks
save image stacks (HDR)
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...
vigra::Size2D getSize() const
get size of output image
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...
wxString getProjectionString(const HuginBase::SrcPanoImage &img)
Returns translated projection for given image.
Definition: LensTools.cpp:107
BlendingMechanism blendMode
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
std::vector< NormalCommand * > CommandQueue
Definition: Executor.h:61
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
wxString PrintDetailInfo(const HuginBase::Panorama &pano, const HuginBase::PanoramaOptions &opts, const HuginBase::UIntSet &allActiveImages, const wxString &prefix, const wxString &bindir, wxConfigBase *config, double &exiftoolVersion)
wxArrayString GetNumberedFilename(const wxString &prefix, const wxString &postfix, const HuginBase::UIntSet &img)
returns an array of filenames with numbers