Hugintrunk  0.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
pto_mask.cpp
Go to the documentation of this file.
1 // -*- c-basic-offset: 4 -*-
2 
11 /* This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This software is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public
22  * License along with this software. If not, see
23  * <http://www.gnu.org/licenses/>.
24  *
25  */
26 
27 #include <fstream>
28 #include <sstream>
29 #include <getopt.h>
30 #include <panodata/Panorama.h>
31 
32 struct MaskFiles
33 {
34  size_t imageNr;
35  std::string maskFile;
36 };
37 
38 static void usage(const char* name)
39 {
40  std::cout << name << ": add mask to pto project" << std::endl
41  << name << " version " << hugin_utils::GetHuginVersion() << std::endl
42  << std::endl
43  << "Usage: " << name << " [options] input.pto" << std::endl
44  << std::endl
45  << " Options:" << std::endl
46  << " -o, --output=file.pto Output Hugin PTO file. Default: <filename>_mask.pto" << std::endl
47  << " --mask=filename@imgNr Read the mask from the file and" << std::endl
48  << " assign the mask to given image" << std::endl
49  << " --rotate=CLOCKWISE|90|COUNTERCLOCKWISE|-90" << std::endl
50  << " Rotates the mask clock- or counterclockwise" << std::endl
51  << " --process==CLIP|SCALE|PROP_SCALE Specify how the mask should be modified" << std::endl
52  << " if the mask is create for an image with" << std::endl
53  << " different size." << std::endl
54  << " * CLIP: clipping (Default)" << std::endl
55  << " * SCALE: Scaling width and height individually" << std::endl
56  << " * PROP_SCALE: Proportional scale" << std::endl
57  << " --delete-mask=maskNr@imgNr|ALL@imgNr|ALL" << std::endl
58  << " Removes the specified mask(s)" << std::endl
59  << " -h, --help Shows this help" << std::endl
60  << std::endl;
61 }
62 
63 int main(int argc, char* argv[])
64 {
65  // parse arguments
66  const char* optstring = "o:h";
67  enum
68  {
69  MASK_SWITCH=1000,
70  ROTATE_SWITCH,
71  PROC_SWITCH,
72  MASK_DELETE_SWITCH
73  };
74  static struct option longOptions[] =
75  {
76  {"output", required_argument, NULL, 'o' },
77  {"mask", required_argument, NULL, MASK_SWITCH },
78  {"rotate", required_argument, NULL, ROTATE_SWITCH},
79  {"process", required_argument, NULL, PROC_SWITCH},
80  {"delete-mask", required_argument, NULL, MASK_DELETE_SWITCH},
81  {"help", no_argument, NULL, 'h' },
82  0
83  };
84 
85  int c;
86  std::vector<MaskFiles> maskFiles;
87  size_t rotate=0;
88  size_t process=0;
89  std::string output;
90  // boolean if all masks should be delete
91  bool deleteAllMasks = false;
92  // map, which contains all masks which should be deleted
93  // key is the image number, value is a set with the mask numbers
94  // UINT_MAX indicates all masks for given image
95  std::map<unsigned int, HuginBase::UIntSet> masksToDelete;
96  while ((c = getopt_long (argc, argv, optstring, longOptions,nullptr)) != -1)
97  {
98  switch (c)
99  {
100  case 'o':
101  output = optarg;
102  break;
103  case MASK_SWITCH:
104  {
105  std::string s=optarg;
106  size_t found=s.rfind('@');
107  MaskFiles mf;
108  if(found!=std::string::npos)
109  {
110  std::string s2=s.substr(found+1, std::string::npos);
111  mf.imageNr=atoi(s2.c_str());
112  if(mf.imageNr==0 && s2!="0")
113  {
114  std::cerr << hugin_utils::stripPath(argv[0]) << ": Could not parse image number: \"" << s2 << "\"." << std::endl;
115  return 1;
116  };
117  }
118  else
119  {
120  std::cerr << hugin_utils::stripPath(argv[0]) << ": No image number found in \"" << s << "\"." << std::endl;
121  return 1;
122  };
123  mf.maskFile=s.substr(0, found);
125  {
126  std::cerr << hugin_utils::stripPath(argv[0]) << ": File \"" << mf.maskFile << "\" does not exists." << std::endl;
127  return 1;
128  };
129  maskFiles.push_back(mf);
130  };
131  break;
132  case ROTATE_SWITCH:
133  {
134  std::string s=optarg;
136  if(s=="CLOCKWISE" || s=="90")
137  {
138  rotate=1;
139  }
140  else
141  {
142  if(s=="COUNTERCLOCKWISE" || s=="-90")
143  {
144  rotate=2;
145  }
146  else
147  {
148  std::cerr << hugin_utils::stripPath(argv[0]) << ": Unknown rotate command (" << optarg << ") found." << std::endl;
149  return 1;
150  };
151  };
152  };
153  break;
154  case PROC_SWITCH:
155  {
156  std::string s=optarg;
158  if(s=="CLIP")
159  {
160  process=0;
161  }
162  else
163  {
164  if(s=="SCALE")
165  {
166  process=1;
167  }
168  else
169  {
170  if(s=="PROP_SCALE")
171  {
172  process=2;
173  }
174  else
175  {
176  std::cerr << hugin_utils::stripPath(argv[0]) << ": Unknown process command (" << optarg << ") found." << std::endl;
177  return 1;
178  };
179  };
180  };
181  }
182  break;
183  case MASK_DELETE_SWITCH:
184  {
185  std::string s = hugin_utils::tolower(optarg);
186  if (s == "all")
187  {
188  deleteAllMasks = true;
189  }
190  else
191  {
192  size_t found = s.rfind('@');
193  if (found != std::string::npos)
194  {
195  // parse mask number with special case for all
196  std::string s2 = s.substr(0, found);
197  unsigned int maskNr;
198  if (s2 == "all")
199  {
200  maskNr = UINT_MAX;
201  }
202  else
203  {
204  if (!hugin_utils::stringToUInt(s2, maskNr))
205  {
206  std::cerr << hugin_utils::stripPath(argv[0]) << ": Could not parse mask number: \"" << s2 << "\"." << std::endl;
207  return 1;
208  };
209  };
210  // now parse imgNr
211  s2 = s.substr(found + 1, std::string::npos);
212  unsigned int imgNr;
213  if (!hugin_utils::stringToUInt(s2, imgNr))
214  {
215  std::cerr << hugin_utils::stripPath(argv[0]) << ": Could not parse image number: \"" << s2 << "\"." << std::endl;
216  return 1;
217  };
218  // now remember parsed imgNr and maskNr
219  masksToDelete[imgNr].insert(maskNr);
220  }
221  else
222  {
223  std::cerr << hugin_utils::stripPath(argv[0]) << ": No image number found in \"" << s << "\"." << std::endl;
224  return 1;
225  };
226  };
227  };
228  break;
229  case 'h':
230  usage(hugin_utils::stripPath(argv[0]).c_str());
231  return 0;
232  case ':':
233  case '?':
234  // missing argument or invalid switch
235  return 1;
236  break;
237  default:
238  // this should not happen
239  abort();
240  }
241  }
242 
243  if (argc - optind != 1)
244  {
245  if (argc - optind < 1)
246  {
247  std::cerr << hugin_utils::stripPath(argv[0]) << ": No project file given." << std::endl;
248  }
249  else
250  {
251  std::cerr << hugin_utils::stripPath(argv[0]) << ": Only one project file expected." << std::endl;
252  };
253  return 1;
254  };
255 
256  if (maskFiles.empty() && !deleteAllMasks && masksToDelete.empty())
257  {
258  std::cerr << hugin_utils::stripPath(argv[0]) << ": No changes to mask given." << std::endl << std::endl;
259  return 1;
260  };
261 
262  std::string input=argv[optind];
263  // read panorama
264  HuginBase::Panorama pano;
265  if (!pano.ReadPTOFile(input, hugin_utils::getPathPrefix(input)))
266  {
267  return 1;
268  };
269 
270  //delete masks
271  if (deleteAllMasks)
272  {
273  // delete all masks
274  std::cout << "Deleting all masks in project file" << std::endl;
275  for (size_t i = 0; i < pano.getNrOfImages(); ++i)
276  {
278  };
279  }
280  else
281  {
282  if (!masksToDelete.empty())
283  {
284  for (auto it = masksToDelete.begin(); it != masksToDelete.end(); ++it)
285  {
286  // get image number and check that it is valid
287  const unsigned int imgNr = it->first;
288  if (imgNr < pano.getNrOfImages())
289  {
290  HuginBase::MaskPolygonVector masks = pano.getImage(imgNr).getMasks();
291  const HuginBase::UIntSet& masksSet = it->second;
292  if (*masksSet.rbegin() == UINT_MAX)
293  {
294  // delete all masks for given image
296  std::cout << "Deleting all masks for image " << imgNr << std::endl;
297  }
298  else
299  {
300  // iterate all masks
301  for (auto maskNr = masksSet.rbegin(); maskNr != masksSet.rend(); ++maskNr)
302  {
303  // range check
304  if (*maskNr < masks.size())
305  {
306  masks.erase(masks.begin() + (*maskNr));
307  std::cout << "Deleting mask " << *maskNr << " for image " << imgNr << std::endl;
308  }
309  else
310  {
311  std::cout << "Ignoring deleting non-existing mask number " << *maskNr << " for image " << imgNr << std::endl;
312  };
313  };
314  };
315  // finally update mask vector im panorama
316  pano.updateMasksForImage(imgNr, masks);
317  }
318  else
319  {
320  std::cout << "Ignoring deleting masks for invalid image nr " << imgNr << std::endl;
321  };
322  };
323  };
324  };
325  //read masks and apply
326  for(size_t i=0; i<maskFiles.size(); i++)
327  {
328  if(maskFiles[i].imageNr<pano.getNrOfImages())
329  {
330  std::ifstream in(maskFiles[i].maskFile.c_str());
331  vigra::Size2D maskImageSize;
332  HuginBase::MaskPolygonVector loadedMasks;
333  LoadMaskFromStream(in, maskImageSize, loadedMasks, maskFiles[i].imageNr);
334  in.close();
335  if(maskImageSize.area()==0 || loadedMasks.empty())
336  {
337  std::cerr << "Error: Could not parse mask from file \"" << maskFiles[i].maskFile << "\"." << std::endl;
338  return 1;
339  };
340  double maskWidth;
341  double maskHeight;
342  if(rotate==0)
343  {
344  maskWidth=maskImageSize.width();
345  maskHeight=maskImageSize.height();
346  }
347  else
348  {
349  maskWidth=maskImageSize.height();
350  maskHeight=maskImageSize.width();
351  bool clockwise=(rotate==1);
352  for(unsigned int i=0; i<loadedMasks.size(); i++)
353  {
354  loadedMasks[i].rotate90(clockwise, maskImageSize.width(), maskImageSize.height());
355  }
356  };
357  // compare image size from file with that of current image alert user
358  // if different.
359  vigra::Size2D imageSize=pano.getImage(maskFiles[i].imageNr).getSize();
360  if (maskImageSize != imageSize)
361  {
362  switch(process)
363  {
364  case 0:
365  // clip mask
366  std::cout << "Clipping mask" << std::endl;
367  for(unsigned int i=0; i<loadedMasks.size(); i++)
368  loadedMasks[i].clipPolygon(vigra::Rect2D(-0.5*HuginBase::maskOffset, -0.5*HuginBase::maskOffset,
369  imageSize.width()+0.5*HuginBase::maskOffset, imageSize.height()+0.5*HuginBase::maskOffset));
370  break;
371  case 1:
372  // scale mask
373  std::cout << "Scaling mask" << std::endl;
374  for(unsigned int i=0; i<loadedMasks.size(); i++)
375  {
376  loadedMasks[i].scale((double)imageSize.width()/maskWidth,(double)imageSize.height()/maskHeight);
377  }
378  break;
379  case 2:
380  // proportional scale mask
381  std::cout << "Propotional scale mask" << std::endl;
382  {
383  double factor=std::min((double)imageSize.width()/maskWidth, (double)imageSize.height()/maskHeight);
384  for(unsigned int i=0; i<loadedMasks.size(); i++)
385  {
386  loadedMasks[i].scale(factor);
387  }
388  };
389  break;
390  };
391  };
392  HuginBase::MaskPolygonVector masks = pano.getImage(maskFiles[i].imageNr).getMasks();
393  for(size_t j=0; j<loadedMasks.size(); j++)
394  {
395  masks.push_back(loadedMasks[j]);
396  };
397  pano.updateMasksForImage(maskFiles[i].imageNr, masks);
398  }
399  else
400  {
401  std::cout << "Warning: Invalid image number \"" << maskFiles[i].imageNr << "\"." << std::endl
402  << " Project contains only " << pano.getNrOfImages()+1 << " images." << std::endl
403  << " Ignoring this mask." << std::endl;
404  };
405  };
406 
407  //write output
408  // Set output .pto filename if not given
409  output = hugin_utils::GetOutputFilename(output, input, "mask");
410  if (pano.WritePTOFile(output, hugin_utils::getPathPrefix(output)))
411  {
412  std::cout << std::endl << "Written project file " << output << std::endl;
413  };
414  return 0;
415 }
bool FileExists(const std::string &filename)
checks if file exists
Definition: utils.cpp:362
std::string GetOutputFilename(const std::string &out, const std::string &in, const std::string &suffix)
construct output filename, if ouput is known return this value otherwise use the input filename and a...
Definition: utils.cpp:420
const int maskOffset
polygon can exceed the image maximal maskOffset pixels in each direction bigger polygons will be clip...
Definition: Mask.h:44
void LoadMaskFromStream(std::istream &stream, vigra::Size2D &imageSize, MaskPolygonVector &newMasks, size_t imgNr)
load the mask from stream
Definition: Mask.cpp:640
std::string toupper(const std::string &s)
Definition: stl_utils.h:59
bool stringToUInt(const std::string &s, unsigned int &val)
convert string to unsigned integer value, returns true, if sucessful
Definition: utils.cpp:280
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
std::size_t getNrOfImages() const
number of images.
Definition: Panorama.h:205
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
size_t imageNr
Definition: pto_mask.cpp:34
std::vector< MaskPolygon > MaskPolygonVector
Definition: Mask.h:147
std::string maskFile
Definition: pto_mask.cpp:35
static void usage()
Definition: Main.cpp:32
std::string GetHuginVersion()
return a string with version numbers
Definition: utils.cpp:920
bool WritePTOFile(const std::string &filename, const std::string &prefix="")
write data to given pto file
Definition: Panorama.cpp:2059
const SrcPanoImage & getImage(std::size_t nr) const
get a panorama image, counting starts with 0
Definition: Panorama.h:211
void updateMasksForImage(unsigned int imgNr, MaskPolygonVector newMasks)
set complete mask list for image with number
Definition: Panorama.cpp:882
static T min(T x, T y)
Definition: svm.cpp:62
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
int main(int argc, char *argv[])
Definition: Main.cpp:167