Hugintrunk  0.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
nona.cpp
Go to the documentation of this file.
1 // -*- c-basic-offset: 4 -*-
26 #include <hugin_config.h>
27 #include <fstream>
28 #include <sstream>
29 
30 #include <algorithm>
31 #include <cctype>
32 #include <string>
33 
34 #include <vigra/error.hxx>
35 
36 #include <getopt.h>
37 
38 #include <hugin_basic.h>
40 #include <hugin_utils/platform.h>
43 #include "hugin_utils/stl_utils.h"
44 #include "nona/StitcherOptions.h"
45 
46 #include <tiffio.h>
47 
48 static void usage(const char* name)
49 {
50  std::cout << name << ": stitch a panorama image" << std::endl
51  << std::endl
52  << "nona version " << hugin_utils::GetHuginVersion() << std::endl
53  << std::endl
54  << " It uses the transform function from PanoTools, the stitching itself" << std::endl
55  << " is quite simple, no seam feathering is done." << std::endl
56  << " only the non-antialiasing interpolators of panotools are supported" << std::endl
57  << std::endl
58  << " The following output formats (n option of panotools p script line)" << std::endl
59  << " are supported:"<< std::endl
60  << std::endl
61  << " JPEG, TIFF, PNG : Single image formats with internal blender"<< std::endl
62  << " JPEG_m, TIFF_m, PNG_m : multiple image files"<< std::endl
63  << " TIFF_multilayer : Multilayer tiff files, readable by The Gimp 2.0" << std::endl
64  << std::endl
65  << "Usage: " << name << " [options] -o output project_file (image files)" << std::endl
66  << " Options: " << std::endl
67  << " -c create coordinate images (only TIFF_m output)" << std::endl
68  << " -v|--verbose verbose output" << std::endl
69  << " -d|--debug print detailed output for gpu processing" << std::endl
70  << " -g|--gpu perform image remapping on the GPU" << std::endl
71  << std::endl
72  << " The following options can be used to override settings in the project file:" << std::endl
73  << " -i num remap only image with number num" << std::endl
74  << " (can be specified multiple times)" << std::endl
75  << " -m str set output file format (TIFF, TIFF_m, TIFF_multilayer," << std::endl
76  << " EXR, EXR_m, JPEG, JPEG_m, PNG, PNG_m)" << std::endl
77  << " -r ldr/hdr set output mode." << std::endl
78  << " ldr keep original bit depth and response" << std::endl
79  << " hdr merge to hdr" << std::endl
80  << " -e exposure set exposure for ldr mode" << std::endl
81  << " -p TYPE pixel type of the output. Can be one of:" << std::endl
82  << " UINT8 8 bit unsigned integer" << std::endl
83  << " UINT16 16 bit unsigned integer" << std::endl
84  << " INT16 16 bit signed integer" << std::endl
85  << " UINT32 32 bit unsigned integer" << std::endl
86  << " INT32 32 bit signed integer" << std::endl
87  << " FLOAT 32 bit floating point" << std::endl
88  << " -z|--compression set compression type." << std::endl
89  << " Possible options for tiff output:" << std::endl
90  << " NONE no compression" << std::endl
91  << " PACKBITS packbits compression" << std::endl
92  << " LZW lzw compression" << std::endl
93  << " DEFLATE deflate compression" << std::endl
94  << " For jpeg output set quality number" << std::endl
95  << " --bigtiff Use BigTIFF format for TIFF images" << std::endl
96  << " --ignore-exposure don't correct exposure" << std::endl
97  << " (this does not work with -e switch together)" << std::endl
98  << " --output-range-compression=value set range compression" << std::endl
99  << " value should be a real in range 0..20" << std::endl
100  << " --save-intermediate-images saves also the intermediate" << std::endl
101  << " images (only when output is TIFF, PNG or JPEG)" << std::endl
102  << " --intermediate-suffix=SUFFIX suffix for intermediate images" << std::endl
103  << " --final-suffix=SUFFIX suffix for the final image" << std::endl
104  << " --create-exposure-layers create all exposure layers" << std::endl
105  << " (this will always use TIFF)" << std::endl
106  << " --clip-exposure[=lower cutoff:upper cutoff]" << std::endl
107  << " mask automatically all dark and bright pixels" << std::endl
108  << " optionally you can specify the limits for the" << std::endl
109  << " lower and upper cutoff (specify in range 0...1," << std::endl
110  << " relative the full range)" << std::endl
111  << " --seam=hard|blend select the blend mode for the seam" << std::endl
112  << std::endl;
113 }
114 
115 int main(int argc, char* argv[])
116 {
117 
118  // parse arguments
119  const char* optstring = "z:cho:i:t:m:p:r:e:vgd";
120  int c;
121 
122  bool doCoord = false;
123  HuginBase::UIntSet outputImages;
124  std::string basename;
125  std::string outputFormat;
126  bool overrideOutputMode = false;
127  std::string compression;
129  bool overrideExposure = false;
130  double exposure=0;
131  double rangeCompression = -1;
133  int verbose = 0;
134  bool useGPU = false;
135  std::string outputPixelType;
136  bool createExposureLayers = false;
137 
138  enum
139  {
140  IGNOREEXPOSURE=1000,
141  SAVEINTERMEDIATEIMAGES,
142  INTERMEDIATESUFFIX,
143  FINALSUFFIX,
144  EXPOSURELAYERS,
145  MASKCLIPEXPOSURE,
146  SEAMMODE,
147  USE_BIGTIFF,
148  RANGECOMPRESSION
149  };
150  static struct option longOptions[] =
151  {
152  { "ignore-exposure", no_argument, NULL, IGNOREEXPOSURE },
153  { "save-intermediate-images", no_argument, NULL, SAVEINTERMEDIATEIMAGES },
154  { "intermediate-suffix", required_argument, NULL, INTERMEDIATESUFFIX },
155  { "final-suffix", required_argument, NULL, FINALSUFFIX },
156  { "compression", required_argument, NULL, 'z' },
157  { "create-exposure-layers", no_argument, NULL, EXPOSURELAYERS },
158  { "clip-exposure", optional_argument, NULL, MASKCLIPEXPOSURE },
159  { "seam", required_argument, NULL, SEAMMODE},
160  { "gpu", no_argument, NULL, 'g'},
161  { "bigtiff", no_argument, NULL, USE_BIGTIFF },
162  { "output-range-compression", required_argument, NULL, RANGECOMPRESSION },
163  { "help", no_argument, NULL, 'h'},
164  { "debug", no_argument, NULL, 'd'},
165  { "verbose", no_argument, NULL, 'v'},
166  { "output", required_argument, NULL, 'o'},
167  0
168  };
169 
170  while ((c = getopt_long(argc, argv, optstring, longOptions, nullptr)) != -1)
171  {
172  switch (c)
173  {
174  case 'o':
175  basename = optarg;
176  break;
177  case 'c':
178  doCoord = true;
179  break;
180  case 'i':
181  outputImages.insert(atoi(optarg));
182  break;
183  case 'm':
184  outputFormat = optarg;
185  break;
186  case 'p':
187  outputPixelType = optarg;
188  break;
189  case 'r':
190  if (std::string(optarg) == "ldr")
191  {
192  overrideOutputMode = true;
194  }
195  else if (std::string(optarg) == "hdr")
196  {
197  overrideOutputMode = true;
199  }
200  else
201  {
202  std::cerr << hugin_utils::stripPath(argv[0]) << ": \"" << optarg << "\" is not a valid parameter for output mode (-r)." << std::endl;
203  return 1;
204  }
205  break;
206  case 'e':
207  overrideExposure = true;
208  exposure = atof(optarg);
209  break;
210  case IGNOREEXPOSURE:
211  HuginBase::Nona::SetAdvancedOption(advOptions, "ignoreExposure", true);
212  break;
213  case SAVEINTERMEDIATEIMAGES:
214  HuginBase::Nona::SetAdvancedOption(advOptions, "saveIntermediateImages", true);
215  break;
216  case INTERMEDIATESUFFIX:
217  HuginBase::Nona::SetAdvancedOption(advOptions, "saveIntermediateImagesSuffix", std::string(optarg));
218  break;
219  case FINALSUFFIX:
220  HuginBase::Nona::SetAdvancedOption(advOptions, "saveFinalImageSuffix", std::string(optarg));
221  break;
222  case EXPOSURELAYERS:
223  createExposureLayers = true;
224  break;
225  case MASKCLIPEXPOSURE:
226  HuginBase::Nona::SetAdvancedOption(advOptions, "maskClipExposure", true);
227  if (optarg != NULL && *optarg != 0)
228  {
229  // optional argument given, check if valid
230  std::vector<std::string> tokens = hugin_utils::SplitString(std::string(optarg), ":");
231  if (tokens.size() == 2)
232  {
233  double lowerCutoff;
234  double upperCutoff;
235  if (hugin_utils::stringToDouble(tokens[0], lowerCutoff) && hugin_utils::stringToDouble(tokens[1], upperCutoff))
236  {
237  if (lowerCutoff < 0 || lowerCutoff>1)
238  {
239  std::cerr << hugin_utils::stripPath(argv[0]) << ": Argument \"" << tokens[0] << "\" is not a valid number for lower cutoff." << std::endl;
240  return 1;
241  };
242  if (upperCutoff < 0 || upperCutoff>1)
243  {
244  std::cerr << hugin_utils::stripPath(argv[0]) << ": Argument \"" << tokens[1] << "\" is not a valid number for upper cutoff." << std::endl;
245  return 1;
246  };
247  if (lowerCutoff >= upperCutoff)
248  {
249  std::cerr << hugin_utils::stripPath(argv[0]) << ": Lower cutoff \"" << tokens[0] << "\" is higher than upper cutoff" << std::endl
250  << " \"" << tokens[1] << "\". This is no valid input." << std::endl;
251  return 1;
252  };
253  HuginBase::Nona::SetAdvancedOption(advOptions, "maskClipExposureLowerCutoff", static_cast<float>(lowerCutoff));
254  HuginBase::Nona::SetAdvancedOption(advOptions, "maskClipExposureUpperCutoff", static_cast<float>(upperCutoff));
255  }
256  else
257  {
258  std::cerr << hugin_utils::stripPath(argv[0]) << ": Argument \"" << optarg << "\" is not valid number for --clip-exposure" << std::endl
259  << " Expected --clip-exposure=lower cutoff:upper cutoff" << std::endl
260  << " Both should be numbers between 0 and 1." << std::endl;
261  return 1;
262  }
263  }
264  else
265  {
266  std::cerr << hugin_utils::stripPath(argv[0]) << ": Argument \"" << optarg << "\" is not valid for --clip-exposure" << std::endl
267  << " Expected --clip-exposure=lower cutoff:upper cutoff" << std::endl;
268  return 1;
269  };
270  }
271  break;
272  case SEAMMODE:
273  {
274  std::string text(optarg);
275  text = hugin_utils::tolower(text);
276  if (text == "hard")
277  {
278  HuginBase::Nona::SetAdvancedOption(advOptions, "hardSeam", true);
279  }
280  else
281  {
282  if (text == "blend")
283  {
284  HuginBase::Nona::SetAdvancedOption(advOptions, "hardSeam", false);
285  }
286  else
287  {
288  std::cerr << hugin_utils::stripPath(argv[0]) << ": String \"" << text << "\" is not a recognized seam blend mode." << std::endl;
289  return 1;
290  };
291  };
292  };
293  break;
294  case 'h':
295  usage(hugin_utils::stripPath(argv[0]).c_str());
296  return 0;
297  case 't':
298  std::cout << "WARNING: Switch -t is deprecated. Set environment variable OMP_NUM_THREADS instead" << std::endl;
299  break;
300  case 'v':
301  ++verbose;
302  break;
303  case 'z':
304  compression = optarg;
305  compression=hugin_utils::toupper(compression);
306  break;
307  case 'g':
308  useGPU = true;
309  break;
310  case 'd':
312  break;
313  case USE_BIGTIFF:
314  HuginBase::Nona::SetAdvancedOption(advOptions, "useBigTIFF", true);
315  break;
316  case RANGECOMPRESSION:
317  if (!hugin_utils::stringToDouble(std::string(optarg), rangeCompression))
318  {
319  std::cerr << hugin_utils::stripPath(argv[0]) << ": Could not parse output range compression (" << optarg << ")." << std::endl;
320  return 1;
321  };
322  if (rangeCompression < 0.0 || rangeCompression > 20.0)
323  {
324  std::cerr << hugin_utils::stripPath(argv[0]) << ": range compression must be a real between 0 and 20." << std::endl;
325  return 1;
326  };
327  break;
328  case ':':
329  case '?':
330  // missing argument or invalid switch
331  return 1;
332  break;
333  default:
334  // this should not happen
335  abort();
336  }
337  }
338 
339  if (basename.empty())
340  {
341  std::cerr << hugin_utils::stripPath(argv[0]) << ": No output prefix given." << std::endl;
342  return 1;
343  };
344  if(argc - optind < 1)
345  {
346  std::cerr << hugin_utils::stripPath(argv[0]) << ": No project file given." << std::endl;
347  return 1;
348  }
349  unsigned nCmdLineImgs = argc -optind -1;
350 
351  const char* scriptFile = argv[optind];
352 
353  // suppress tiff warnings
354  TIFFSetWarningHandler(0);
355 
356  HuginBase::Panorama pano;
357  if (!pano.ReadPTOFile(scriptFile, hugin_utils::getPathPrefix(scriptFile)))
358  {
359  exit(1);
360  };
361 
362  if ( nCmdLineImgs > 0)
363  {
364  if (nCmdLineImgs != pano.getNrOfImages())
365  {
366  std::cerr << "Incorrect number of images specified on command line\nProject required " << pano.getNrOfImages() << " but " << nCmdLineImgs << " where given" << std::endl;
367  exit(1);
368  }
369  for (unsigned i=0; i < pano.getNrOfImages(); i++)
370  {
371  pano.setImageFilename(i, argv[optind+i+1]);
372  }
373 
374  }
376 
377  // save coordinate images, if requested
378  opts.saveCoordImgs = doCoord;
379  if (createExposureLayers)
380  {
381  if (!outputFormat.empty())
382  {
383  std::cout << "Warning: Ignoring output format " << outputFormat << std::endl
384  << " Switch --create-exposure-layers will enforce TIFF_m output." << std::endl;
385  };
386  outputFormat = "TIFF";
387  if (!outputImages.empty())
388  {
389  std::cout << "Warning: Ignoring specified output images." << std::endl
390  << " Switch --create-exposure-layers will always work on all active images." << std::endl;
391  outputImages.clear();
392  };
393  };
394  if (outputFormat == "TIFF_m")
395  {
397  opts.outputImageType = "tif";
398  }
399  else if (outputFormat == "JPEG_m")
400  {
402  opts.tiff_saveROI = false;
403  opts.outputImageType = "jpg";
404  }
405  else if (outputFormat == "JPEG")
406  {
408  opts.tiff_saveROI = false;
409  opts.outputImageType = "jpg";
410  }
411  else if (outputFormat == "PNG_m")
412  {
414  opts.tiff_saveROI = false;
415  opts.outputImageType = "png";
416  }
417  else if (outputFormat == "PNG")
418  {
420  opts.tiff_saveROI = false;
421  opts.outputImageType = "png";
422  }
423  else if (outputFormat == "TIFF")
424  {
426  opts.outputImageType = "tif";
427  }
428  else if (outputFormat == "TIFF_multilayer")
429  {
431  opts.outputImageType = "tif";
432  }
433  else if (outputFormat == "EXR_m")
434  {
436  opts.outputImageType = "exr";
437  }
438  else if (outputFormat == "EXR")
439  {
441  opts.outputImageType = "exr";
442  }
443  else if (outputFormat != "")
444  {
445  std::cerr << "Error: unknown output format: " << outputFormat << endl;
446  return 1;
447  }
448 
449  if (!compression.empty())
450  {
451  if (opts.outputImageType == "tif")
452  {
453  opts.tiffCompression = compression;
454  }
455  else
456  {
457  if (opts.outputImageType == "jpg")
458  {
459  int q = atoi(compression.c_str());
460  if (q > 0 && q <= 100)
461  {
462  opts.quality = q;
463  }
464  else
465  {
466  std::cerr << "WARNING: \"" << compression << "\" is not valid compression value for jpeg images." << std::endl
467  << " Using value " << opts.quality << " found in pto file." << std::endl;
468  };
469  };
470  };
471  };
472 
473  if (!outputPixelType.empty())
474  {
475  opts.outputPixelType = outputPixelType;
476  }
477 
478  if (overrideOutputMode)
479  {
480  opts.outputMode = outputMode;
481  }
482 
483  if (overrideExposure)
484  {
485  opts.outputExposureValue = exposure;
486  if (HuginBase::Nona::GetAdvancedOption(advOptions, "ignoreExposure", false))
487  {
488  HuginBase::Nona::SetAdvancedOption(advOptions, "ignoreExposure", false);
489  std::cout << "WARNING: Switches --ignore-exposure and -e can't to used together." << std::endl
490  << " Ignore switch --ignore-exposure." << std::endl;
491  }
492  }
493  if (rangeCompression >= 0.0)
494  {
495  if (HuginBase::Nona::GetAdvancedOption(advOptions, "ignoreExposure", false))
496  {
497  std::cout << "WARNING: Switch --ignore-exposure disables range compression." << std::endl
498  << " --output-range-compression is therefore ignored." << std::endl;
499  }
500  else
501  {
502  opts.outputRangeCompression = rangeCompression;
503  };
504  };
505 
506  if (outputImages.empty())
507  {
508  outputImages = HuginBase::getImagesinROI(pano, pano.getActiveImages());
509  }
510  else
511  {
512  HuginBase::UIntSet activeImages = HuginBase::getImagesinROI(pano, pano.getActiveImages());
513  for (HuginBase::UIntSet::const_iterator it = outputImages.begin(); it != outputImages.end(); ++it)
514  {
515  if(!set_contains(activeImages,*it))
516  {
517  std::cerr << "The project file does not contains an image with number " << *it << std::endl;
518  return 1;
519  };
520  };
521  };
522  if(outputImages.empty())
523  {
524  std::cout << "Project does not contain active images." << std::endl
525  << "Nothing to do for nona." << std::endl;
526  return 0;
527  };
528  if(useGPU)
529  {
530  switch(opts.getProjection())
531  {
532  // the following projections are not supported by nona-gpu
538  useGPU=false;
539  std::cout << "Nona-GPU does not support this projection. Switch to CPU calculation."<<std::endl;
540  break;
541  default:
542  //fallthrough, to silence compiler warning
543  break;
544  };
545  };
546 
547  DEBUG_DEBUG("output basename: " << basename);
548 
549  try
550  {
551  AppBase::ProgressDisplay* pdisp = NULL;
552  if(verbose > 0)
553  {
554  pdisp = new AppBase::StreamProgressDisplay(std::cout);
555  }
556  else
557  {
558  pdisp = new AppBase::DummyProgressDisplay;
559  }
560 
561  if (useGPU)
562  {
563  useGPU = hugin_utils::initGPU(&argc, argv);
564  if (!useGPU)
565  {
566  std::cout << "Could not initialize GPU. Switching back to CPU calculation." << std::endl;
567  };
568  }
569  opts.remapUsingGPU = useGPU;
570  pano.setOptions(opts);
571 
572  if (createExposureLayers)
573  {
574  HuginBase::UIntSetVector exposureLayers = getExposureLayers(pano, outputImages, opts);
575  if (exposureLayers.empty())
576  {
577  std::cerr << "ERROR: Could not determine exposure layers. Cancel execution." << std::endl;
578  }
579  else
580  {
581  // we need to pass the basename to the stitcher
582  // because NonaFileOutputStitcher get already filename with numbers added
583  HuginBase::Nona::SetAdvancedOption(advOptions, "basename", basename);
584  for (size_t i = 0; i < exposureLayers.size(); ++i)
585  {
586  HuginBase::PanoramaOptions modOptions(opts);
587  // set output exposure to exposure value of first image of layers
588  // normally this this invoked with --ignore-exposure, so this has no effect
589  modOptions.outputExposureValue = pano.getImage(*(exposureLayers[i].begin())).getExposureValue();
590  // build filename
591  std::ostringstream filename;
592  filename << basename << std::setfill('0') << std::setw(4) << i;
593  HuginBase::NonaFileOutputStitcher(pano, pdisp, modOptions, exposureLayers[i], filename.str(), advOptions).run();
594  }
595  }
596  }
597  else
598  {
599  // stitch panorama
600  HuginBase::NonaFileOutputStitcher(pano, pdisp, opts, outputImages, basename, advOptions).run();
601  };
602  // add a final newline, after the last progress message
603  if (verbose > 0)
604  {
605  std::cout << std::endl;
606  }
607 
608  if (useGPU)
609  {
611  }
612 
613  if(pdisp != NULL)
614  {
615  delete pdisp;
616  pdisp=NULL;
617  }
618  }
619  catch (std::exception& e)
620  {
621  std::cerr << "caught exception: " << e.what() << std::endl;
622  return 1;
623  }
624 
625  return 0;
626 }
bool wrapupGPU()
cleanup GPU settings
Definition: utils.cpp:914
PanoramaOptions::ProjectionFormat getProjection() const
Dummy progress display, without output.
declaration of functions to handle stacks and layers
bool GetAdvancedOption(const AdvancedOptions &opts, const std::string &name, const bool defaultValue)
check if given option is saved and return its boolean value, otherwise return defaultValue ...
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
UIntSet getImagesinROI(const PanoramaData &pano, const UIntSet activeImages)
returns set of images which are visible in output ROI
bool set_contains(const _Container &c, const typename _Container::key_type &key)
Definition: stl_utils.h:74
This class will use the stitchPanorama function of nona.
virtual void run()
runs the algorithm.
std::string toupper(const std::string &s)
Definition: stl_utils.h:59
std::set< unsigned int > UIntSet
Definition: PanoramaData.h:51
Model for a panorama.
Definition: Panorama.h:152
std::string getPathPrefix(const std::string &filename)
Get the path to a filename.
Definition: utils.cpp:184
void SetGPUDebugMessages(const bool doPrint)
std::size_t getNrOfImages() const
number of images.
Definition: Panorama.h:205
platform/compiler specific stuff.
bool ReadPTOFile(const std::string &filename, const std::string &prefix="")
read pto file from the given filename into Panorama object it does some checks on the file and issues...
Definition: Panorama.cpp:2023
Helper class for storing different options.
void setImageFilename(unsigned int img, const std::string &fname)
set a new image filename
Definition: Panorama.cpp:373
bool stringToDouble(const STR &str_, double &dest)
convert a string to a double, ignore localisation.
Definition: utils.h:114
bool initGPU(int *argcp, char **argv)
Try to initalise GLUT and GLEW, and create an OpenGL context for GPU stitching.
Definition: utils.cpp:858
UIntSet getActiveImages() const
get active images
Definition: Panorama.cpp:1585
const PanoramaOptions & getOptions() const
returns the options for this panorama
Definition: Panorama.h:481
static void usage()
Definition: Main.cpp:32
#define DEBUG_DEBUG(msg)
Definition: utils.h:68
std::string GetHuginVersion()
return a string with version numbers
Definition: utils.cpp:920
std::map< std::string, std::string > AdvancedOptions
const SrcPanoImage & getImage(std::size_t nr) const
get a panorama image, counting starts with 0
Definition: Panorama.h:211
void setOptions(const PanoramaOptions &opt)
set new output settings This is not used directly for optimizing/stiching, but it can be feed into ru...
Definition: Panorama.cpp:1531
std::vector< std::string > SplitString(const std::string &s, const std::string &sep)
split string s at given sep, returns vector of strings
Definition: utils.cpp:294
Panorama image options.
a progress display to print progress reports to a stream
std::string tolower(const std::string &s)
convert a string to lowercase
Definition: stl_utils.h:49
std::string stripPath(const std::string &filename)
remove the path of a filename (mainly useful for gui display of filenames)
Definition: utils.cpp:160
void SetAdvancedOption(AdvancedOptions &opts, const std::string &name, const bool value)
store the option with name in AdvancedOptions
Contains functions to transform whole images.
int main(int argc, char *argv[])
Definition: Main.cpp:167