Hugintrunk  0.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
wxPanoCommand.cpp
Go to the documentation of this file.
1 // -*- c-basic-offset: 4 -*-
2 
27 #include "hugin_config.h"
28 
29 #include "panoinc_WX.h"
30 #include "panoinc.h"
31 #include <wx/window.h>
32 #include "wxPlatform.h"
33 #include "LensTools.h"
34 
35 #include "wxImageCache.h"
36 #include "platform.h"
37 #include "wxPanoCommand.h"
38 #include "HFOVDialog.h"
40 
41 #include <vigra/cornerdetection.hxx>
42 #include <vigra/localminmax.hxx>
44 
45 #include <hugin_utils/alphanum.h>
46 
47 #ifdef HUGIN_HSI
49 #endif
50 
51 namespace PanoCommand
52 {
53 
55 {
56  // TODO: rewrite, and use same function for modular image creation.
57  const HuginBase::SrcPanoImage & i1 = pano.getImage(img1);
58  const HuginBase::SrcPanoImage & i2 = pano.getImage(img1);
59 
60  // run both images through the harris corner detector
61  ImageCache::EntryPtr eptr = ImageCache::getInstance().getSmallImage(i1.getFilename());
62 
63  vigra::BImage leftImg(eptr->get8BitImage()->size());
64 
65  vigra::GreenAccessor<vigra::RGBValue<vigra::UInt8> > ga;
66  vigra::copyImage(srcImageRange(*(eptr->get8BitImage()), ga ),
67  destImage(leftImg));
68 
69  double scale = i1.getSize().width() / (double) leftImg.width();
70 
71  //const vigra::BImage & leftImg = ImageCache::getInstance().getPyramidImage(
72  // i1.getFilename(),1);
73 
74  vigra::BImage leftCorners(leftImg.size());
75  vigra::FImage leftCornerResponse(leftImg.size());
76 
77  // empty corner image
78  leftCorners.init(0);
79 
80  DEBUG_DEBUG("running corner detector threshold: " << cornerThreshold << " scale: " << scale );
81 
82  // find corner response at scale scale
83  vigra::cornerResponseFunction(srcImageRange(leftImg),
84  destImage(leftCornerResponse),
85  scale);
86 
87  // saveScaledImage(leftCornerResponse,"corner_response.png");
88  DEBUG_DEBUG("finding local maxima");
89  // find local maxima of corner response, mark with 1
90  vigra::localMaxima(vigra::srcImageRange(leftCornerResponse), vigra::destImage(leftCorners), 255);
91 
92 // exportImage(srcImageRange(leftCorners), vigra::ImageExportInfo("c:/corner_response_maxima.png"));
93 
94  DEBUG_DEBUG("thresholding corner response");
95  // threshold corner response to keep only strong corners (above 400.0)
96  vigra::transformImage(vigra::srcImageRange(leftCornerResponse), vigra::destImage(leftCornerResponse),
97  vigra::Threshold<double, double>(
98  cornerThreshold, DBL_MAX, 0.0, 1.0));
99 
100  vigra::combineTwoImages(srcImageRange(leftCorners), srcImage(leftCornerResponse),
101  destImage(leftCorners), std::multiplies<float>());
102 
103 // exportImage(srcImageRange(leftCorners), vigra::ImageExportInfo("c:/corner_response_threshold.png"));
104 
105  // create transform from img1 -> sphere
106  HuginBase::PTools::Transform img1ToSphere;
107  HuginBase::PTools::Transform sphereToImg2;
108 
111  opts.setHFOV(360);
112  opts.setWidth(360);
113  opts.setVFOV(180);
114 
115  img1ToSphere.createInvTransform(pano, img1, opts);
116  sphereToImg2.createTransform(pano, img2, opts);
117 
118 
119  int border = 5;
120  double sphx, sphy;
121  double img2x, img2y;
122  // need to scale the images.
123  // sample grid on img1 and try to add ctrl points
124  for (unsigned int x=0; x < (unsigned int)leftImg.width(); x++ ) {
125  for (unsigned int y=0; y < (unsigned int)leftImg.height(); y++) {
126  if (leftCorners(x,y) > 0) {
127  img1ToSphere.transformImgCoord(sphx, sphy, scale*x, scale*y);
128  sphereToImg2.transformImgCoord(img2x, img2y, sphx, sphy);
129  // check if it is inside..
130  if ( img2x > border && img2x < i2.getWidth() - border
131  && img2y > border && img2y < i2.getHeight() - border )
132  {
133  // add control point
134  HuginBase::ControlPoint p(img1, scale*x, scale*y, img2, img2x, img2y);
135  pano.addCtrlPoint(p);
136  }
137  }
138  }
139  }
141  return true;
142 }
143 
145 {
146  double redBal=1;
147  double blueBal=1;
148  if(pano.getNrOfImages()>=1)
149  {
150  const HuginBase::SrcPanoImage &anchor = pano.getImage(pano.getOptions().colorReferenceImage);
151  // use EXIF Red/BlueBalance data only if image and anchor image are from the same camera
152  if(srcImg.getExifMake() == anchor.getExifMake() &&
153  srcImg.getExifModel() == anchor.getExifModel())
154  {
155  double redBalanceAnchor=pano.getImage(pano.getOptions().colorReferenceImage).getExifRedBalance();
156  double blueBalanceAnchor=pano.getImage(pano.getOptions().colorReferenceImage).getExifBlueBalance();
157  if(fabs(redBalanceAnchor)<1e-2)
158  {
159  redBalanceAnchor=1;
160  };
161  if(fabs(blueBalanceAnchor)<1e-2)
162  {
163  blueBalanceAnchor=1;
164  };
165  redBal=fabs(srcImg.getExifRedBalance()/redBalanceAnchor);
166  blueBal=fabs(srcImg.getExifBlueBalance()/blueBalanceAnchor);
167  if(redBal<1e-2)
168  {
169  redBal=1;
170  };
171  if(blueBal<1e-2)
172  {
173  blueBal=1;
174  };
175  };
176  }
177  srcImg.setWhiteBalanceRed(redBal);
178  srcImg.setWhiteBalanceBlue(blueBal);
179 };
180 
181 bool getLensDataFromUser(wxWindow * parent, HuginBase::SrcPanoImage & srcImg)
182 {
183  // display lens dialog
184  HFOVDialog dlg(parent, srcImg);
185  dlg.CenterOnParent();
186  int ret = dlg.ShowModal();
187  if (ret == wxID_OK)
188  {
189  // assume a cancel dialog.
190  srcImg = dlg.GetSrcImage();
191  if (dlg.GetCropFactor() <= 0)
192  {
193  srcImg.setCropFactor(1);
194  }
195  return true;
196  }
197  else {
198  return false;
199  }
200 }
201 
203 std::string GetICCProfileNameChecked(const std::string& iccName)
204 {
205  // if no icc profile given assume sRGB profile
206  if (iccName.empty())
207  {
208  return "sRGB";
209  };
210  // all profiles starting with sRGB are assumed the same
211  // even if there small? differences between them
212  if (iccName.compare(0, 4, "sRGB") == 0)
213  {
214  return "sRGB";
215  };
216  // otherwise return full name
217  return iccName;
218 }
220 {
221  // check if the files should be sorted by date
222  const long sort = wxConfigBase::Get()->Read(wxT("General/SortNewImgOnAdd"), HUGIN_GUI_SORT_NEW_IMG_ON_ADD);
223 
224  switch (sort) {
225  case 1:
226  // sort by filename
227  std::sort(files.begin(), files.end(), doj::alphanum_less());
228  break;
229  case 2:
230  // sort by date
231  std::sort(files.begin(), files.end(), FileIsNewer());
232  break;
233  default:
234  // no or unknown sort method
235  break;
236  }
237 
238  HuginBase::StandardImageVariableGroups variable_groups(pano);
239  HuginBase::ImageVariableGroup & lenses = variable_groups.getLenses();
240  const size_t oldImgCount = pano.getNrOfImages();
241 
242  // load additional images...
243  for (const auto& filename: files)
244  {
246  wxString fname(filename.c_str(), HUGIN_CONV_FILENAME);
247 
248  // try to read settings automatically.
249  srcImg.setFilename(filename);
250  try
251  {
252  vigra::ImageImportInfo info(filename.c_str());
253  if(info.width()==0 || info.height()==0)
254  {
255  wxMessageBox(wxString::Format(_("Could not decode image:\n%s\nAbort"), fname.c_str()), _("Unsupported image file format"));
256  return false;
257  };
258  srcImg.setSize(info.size());
259  const std::string pixelType=info.getPixelType();
260  // refuse black/white images
261  if (pixelType == "BILEVEL")
262  {
263  wxMessageBox(wxString::Format(_("File \"%s\" is a black/white image.\nHugin does not support this image type. Skipping this image.\nConvert image to grayscale image and try loading again."), fname.c_str()),
264  _("Warning"), wxOK|wxICON_EXCLAMATION);
265  continue;
266  }
267  // check if images is grayscale or RGB image, maybe with alpha channel
268  // reject CMYK or other images
269  const int bands = info.numBands();
270  const int extraBands = info.numExtraBands();
271  if (bands != 1 && bands != 3 && !(bands == 2 && extraBands == 1) && !(bands == 4 && extraBands == 1))
272  {
273  wxMessageBox(wxString::Format(_("Hugin supports only grayscale and RGB images (without and with alpha channel).\nBut file \"%s\" has %d channels and %d extra channels (probably alpha channels).\nHugin does not support this image type. Skipping this image.\nConvert this image to grayscale or RGB image and try loading again."), fname.c_str(), bands, extraBands),
274  _("Warning"), wxOK | wxICON_EXCLAMATION);
275  continue;
276  };
277  if (pano.getNrOfImages() == 0)
278  {
279  pano.setNrOfBands(bands - extraBands);
280  };
281  if (pano.getNrOfBands() != bands - extraBands)
282  {
283  wxString s(_("Hugin supports only grayscale or RGB images (without and with alpha channel)."));
284  s.Append(wxT("\n"));
285  if (pano.getNrOfBands() == 3)
286  {
287  s.Append(wxString::Format(_("File \"%s\" is a grayscale image, but other images in project are color images."), fname.c_str()));
288  }
289  else
290  {
291  s.Append(wxString::Format(_("File \"%s\" is a color image, but other images in project are grayscale images."), fname.c_str()));
292  };
293  s.Append(wxT("\n"));
294  s.Append(_("Hugin does not support this mixing. Skipping this image.\nConvert this image to grayscale or RGB image respectively and try loading again."));
295  wxMessageBox(s, _("Warning"), wxOK | wxICON_EXCLAMATION);
296  continue;
297  };
298  if((pixelType=="UINT8") || (pixelType=="UINT16") || (pixelType=="INT16"))
299  srcImg.setResponseType(HuginBase::SrcPanoImage::RESPONSE_EMOR);
300  else
301  srcImg.setResponseType(HuginBase::SrcPanoImage::RESPONSE_LINEAR);
302  if (pano.getNrOfImages() > 0)
303  {
304  const std::string newICCProfileDesc = hugin_utils::GetICCDesc(info.getICCProfile());
305  if (GetICCProfileNameChecked(newICCProfileDesc) != GetICCProfileNameChecked(pano.getICCProfileDesc()))
306  {
307  // icc profiles does not match
308  wxString warning;
309  if (newICCProfileDesc.empty())
310  {
311  warning = wxString::Format(_("File \"%s\" has no embedded icc profile, but other images in project have profile \"%s\" embedded."), fname.c_str(), wxString(pano.getICCProfileDesc().c_str(), wxConvLocal).c_str());
312  }
313  else
314  {
315  if (pano.getICCProfileDesc().empty())
316  {
317  warning = wxString::Format(_("File \"%s\" has icc profile \"%s\" embedded, but other images in project have no embedded color profile."), fname.c_str(), wxString(newICCProfileDesc.c_str(), wxConvLocal).c_str());
318  }
319  else
320  {
321  warning = wxString::Format(_("File \"%s\" has icc profile \"%s\" embedded, but other images in project have color profile \"%s\" embedded."), fname.c_str(), wxString(newICCProfileDesc.c_str(), wxConvLocal).c_str(), wxString(pano.getICCProfileDesc().c_str(), wxConvLocal).c_str());
322  }
323  }
324  warning.Append(wxT("\n"));
325  warning.Append(_("Hugin expects all images in the same color profile.\nPlease convert all images to same color profile and try again."));
326  wxMessageBox(warning, _("Warning"), wxOK | wxICON_EXCLAMATION);
327  continue;
328  }
329  }
330  else
331  {
332  // remember icc profile name
333  if (!info.getICCProfile().empty())
334  {
335  pano.setICCProfileDesc(hugin_utils::GetICCDesc(info.getICCProfile()));
336  }
337  else
338  {
339  pano.setICCProfileDesc("");
340  };
341  }
342  }
343  catch(std::exception & e)
344  {
345  std::cerr << "ERROR: caught exception: " << e.what() << std::endl;
346  std::cerr << "Could not get pixel type for file " << filename << std::endl;
347  wxMessageBox(wxString::Format(_("Could not decode image:\n%s\nAbort"), fname.c_str()), _("Unsupported image file format"));
348  return false;
349  };
350  bool ok = srcImg.readEXIF();
351  if(ok)
352  {
353  ok = srcImg.applyEXIFValues();
354  // load crop factor from database if unknown
355  if (srcImg.getCropFactor()<0.1)
356  {
357  srcImg.readCropfactorFromDB();
358  ok=(srcImg.getExifFocalLength()>0 && srcImg.getCropFactor()>0.1);
359  };
360  if (srcImg.getProjection() != HuginBase::BaseSrcPanoImage::EQUIRECTANGULAR)
361  {
362  // if projection is equirectangular, we loaded info from gpano tags
363  // in this case we don't need to look up the database
364  const bool ignoreFovRectilinear = wxConfigBase::Get()->Read(wxT("/General/IgnoreFovRectilinearOnAdd"), 1l) == 1l;
365  srcImg.readProjectionFromDB(ignoreFovRectilinear);
366  };
367  };
368  // set preferred projection if requested
369  if (m_preferredLensType != -1)
370  {
371  srcImg.setProjection(static_cast<HuginBase::BaseSrcPanoImage::Projection>(m_preferredLensType));
372  };
373  // save EXIF data for later to prevent double loading of EXIF data
374  applyColorBalanceValue(srcImg, pano);
375  if (! ok ) {
376  // search for image with matching size and exif data
377  // and re-use it.
378  bool added = false;
379  for (unsigned int i=0; i < pano.getNrOfImages(); i++) {
380  HuginBase::SrcPanoImage other = pano.getSrcImage(i);
381  if ( other.getSize() == srcImg.getSize() &&
382  other.getExifModel() == srcImg.getExifModel() &&
383  other.getExifMake() == srcImg.getExifMake() &&
384  other.getExifFocalLength() == srcImg.getExifFocalLength()
385  )
386  {
387  // add image
388  if (srcImg.getCropFactor() <= 0.1)
389  {
390  // set crop factor to 1 if not set
391  srcImg.setCropFactor(other.getCropFactor());
392  };
393  int imgNr = pano.addImage(srcImg);
394  variable_groups.update();
395  lenses.switchParts(imgNr, lenses.getPartNumber(i));
396  added=true;
397  break;
398  }
399  }
400  if (added) continue;
401  }
402  int matchingLensNr=-1;
403  // if no similar image found, ask user
404  if (! ok) {
405  if (!getLensDataFromUser(wxGetActiveWindow(), srcImg)) {
406  // assume a standard lens
407  srcImg.setHFOV(50);
408  srcImg.setCropFactor(1);
409  }
410  }
411 
412  // check the image hasn't disappeared on us since the HFOV dialog was
413  // opened
414  wxString fn(srcImg.getFilename().c_str(),HUGIN_CONV_FILENAME);
415  if (!wxFileName::FileExists(fn)) {
416  DEBUG_INFO("Image: " << fn.mb_str() << " has disappeared, skipping...");
417  continue;
418  }
419 
420  // FIXME: check if the exif information
421  // indicates this image matches a already used lens
422  variable_groups.update();
423  double ev = 0;
424  bool set_exposure = false;
425  for (unsigned int i=0; i < pano.getNrOfImages(); i++) {
426  HuginBase::SrcPanoImage other = pano.getSrcImage(i);
427  if (other.getExifFocalLength()>0) {
428  if (other.getSize() == srcImg.getSize()
429  && other.getExifModel() == srcImg.getExifModel()
430  && other.getExifMake() == srcImg.getExifMake()
431  && other.getExifFocalLength() == srcImg.getExifFocalLength()
432  // Smartphone cameras can have different crop factors, take also this case into account
433  && other.getCropFactor() == srcImg.getCropFactor()
434  )
435  {
436  matchingLensNr = lenses.getPartNumber(i);
437  break;
438  }
439  }
440  else
441  {
442  // no exiv information, just check image size.
443  if (other.getSize() == srcImg.getSize() )
444  {
445  matchingLensNr = lenses.getPartNumber(i);
446  break;
447  }
448  }
449  }
450 
451  // If matchingLensNr == -1 still, we haven't found a good lens to use.
452  // We shouldn't attach the image to a lens in this case, it will have
453  // its own new lens.
454  int imgNr = pano.addImage(srcImg);
455  variable_groups.update();
456  if (matchingLensNr != -1)
457  {
458  lenses.switchParts(imgNr, matchingLensNr);
459  }
460  if (imgNr == 0) {
461  // get initial value for output exposure
463  opts.outputExposureValue = srcImg.getExposureValue();
464  pano.setOptions(opts);
465  }
466  }
467  if (pano.hasPossibleStacks())
468  {
469  wxString message;
470  if (oldImgCount == 0)
471  {
472  // all images added
473  message = _("Hugin has image stacks detected in the added images and will assign corresponding stack numbers to the images.");
474  }
475  else
476  {
477  message = _("Hugin has image stacks detected in the whole project. Stack numbers will be re-assigned on base of this detection. Existing stack assignments will be overwritten.");
478  };
479  message.append(wxT("\n"));
480  message.append(_("Should the position of images in each stack be linked?"));
481  wxMessageDialog dialog(wxGetActiveWindow(), message,
482 #ifdef _WIN32
483  _("Hugin"),
484 #else
485  wxT(""),
486 #endif
487  wxICON_EXCLAMATION | wxYES_NO | wxCANCEL);
488  dialog.SetExtendedMessage(_("When shooting bracketed image stacks from a sturdy tripod the position of the images in each stack can be linked to help Hugin to process the panorama. But if the images in each stack require a fine tune of the position (e. g. when shooting hand held), then don't link the position."));
489  if (oldImgCount == 0)
490  {
491  dialog.SetYesNoCancelLabels(_("Link position"), _("Don't link position"), _("Don't assign stacks"));
492  }
493  else
494  {
495  dialog.SetYesNoCancelLabels(_("Link position"), _("Don't link position"), _("Keep existing stacks"));
496  };
497  switch (dialog.ShowModal())
498  {
499  case wxID_OK:
500  case wxID_YES:
501  pano.linkPossibleStacks(true);
502  break;
503  case wxID_NO:
504  pano.linkPossibleStacks(false);
505  break;
506  };
507  }
508  else
509  {
510  bool hasStacks = false;
511  for (size_t i = 0; i<pano.getNrOfImages(); i++)
512  {
513  if (pano.getImage(i).StackisLinked())
514  {
515  hasStacks = true;
516  break;
517  };
518  };
519  wxConfigBase* config = wxConfigBase::Get();
520  bool showExposureWarning = config->Read(wxT("/ShowExposureWarning"), 1l) == 1l;
521  if (!hasStacks && pano.getMaxExposureDifference() > 2 && showExposureWarning)
522  {
523  wxDialog dlg;
524  wxXmlResource::Get()->LoadDialog(&dlg, NULL, wxT("warning_exposure_dlg"));
525  if (dlg.ShowModal() == wxID_OK)
526  {
527  if (XRCCTRL(dlg, "dont_show_again_checkbox", wxCheckBox)->GetValue())
528  {
529  config->Write(wxT("/ShowExposureWarning"), 0l);
530  }
531  else
532  {
533  config->Write(wxT("/ShowExposureWarning"), 1l);
534  };
535  config->Flush();
536  }
537  };
538  };
539  return true;
540 }
541 
542 
544 {
546  int ptoVersion = 0;
547  std::ifstream in(filename.c_str());
548  if (newPano.loadPTScript(in, ptoVersion, prefix))
549  {
550  pano.setMemento(newPano);
552  // always reset to TIFF_m ...
554  // get enblend and enfuse options from preferences
555  if (ptoVersion < 2)
556  {
557  // no options stored in file, use default arguments in config file
558  opts.enblendOptions = wxConfigBase::Get()->Read(wxT("/Enblend/Args"), wxT(HUGIN_ENBLEND_ARGS)).mb_str(wxConvLocal);
559  opts.enfuseOptions = wxConfigBase::Get()->Read(wxT("/Enfuse/Args"), wxT(HUGIN_ENFUSE_ARGS)).mb_str(wxConvLocal);
560  }
561  // Set the nona gpu flag base on what is in preferences as it is not
562  // stored in the file.
563  opts.remapUsingGPU = wxConfigBase::Get()->Read(wxT("/Nona/UseGPU"),HUGIN_NONA_USEGPU) == 1;
564  pano.setOptions(opts);
565 
566  HuginBase::StandardImageVariableGroups variableGroups(pano);
567  HuginBase::ImageVariableGroup & lenses = variableGroups.getLenses();
568 
569  unsigned int nImg = pano.getNrOfImages();
570  wxString basedir;
571  bool autopanoSiftFile=false;
572  HuginBase::SrcPanoImage autopanoSiftRefImg;
573  HuginBase::VariableMapVector vars(nImg);
574  for (unsigned int i = 0; i < nImg; i++) {
575  wxFileName fname(wxString (pano.getImage(i).getFilename().c_str(), HUGIN_CONV_FILENAME));
576  while (! fname.FileExists()){
577  // Is file in the new path
578  if (basedir != wxT("")) {
579  DEBUG_DEBUG("Old filename: " << pano.getImage(i).getFilename());
580  std::string fn = hugin_utils::stripPath(pano.getImage(i).getFilename());
581  DEBUG_DEBUG("Old filename, without path): " << fn);
582  wxString newname(fn.c_str(), HUGIN_CONV_FILENAME);
583  // GetFullName does only work with local paths (not compatible across platforms)
584 // wxString newname = fname.GetFullName();
585  fname.AssignDir(basedir);
586  fname.SetFullName(newname);
587  DEBUG_TRACE("filename with new path: " << fname.GetFullPath().mb_str(wxConvLocal));
588  if (fname.FileExists()) {
589  pano.setImageFilename(i, (const char *)fname.GetFullPath().mb_str(HUGIN_CONV_FILENAME));
590  DEBUG_TRACE("New filename set: " << fname.GetFullPath().mb_str(wxConvLocal));
591  // TODO - set pano dirty flag so that new paths are saved
592  continue;
593  }
594  }
595 
596  wxMessageBox(wxString::Format(_("The project file \"%s\" refers to image \"%s\" which was not found.\nPlease manually select the correct image."), filename, fname.GetFullPath()), _("Image file not found"));
597 
598  if (basedir == wxT("")) {
599  basedir = fname.GetPath();
600  }
601 
602  // open file dialog
603  wxFileDialog dlg(wxGetActiveWindow(), wxString::Format(_("Select image %s"), fname.GetFullName()),
604  basedir, fname.GetFullName(),
605  GetFileDialogImageFilters(), wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_PREVIEW, wxDefaultPosition);
606  dlg.SetDirectory(basedir);
607  if (dlg.ShowModal() == wxID_OK) {
608  pano.setImageFilename(i, (const char *)dlg.GetPath().mb_str(HUGIN_CONV_FILENAME));
609  // save used path
610  basedir = dlg.GetDirectory();
611  DEBUG_INFO("basedir is: " << basedir.mb_str(wxConvLocal));
612  } else {
613  HuginBase::PanoramaMemento emptyPano;
614  pano.setMemento(emptyPano);
615  // set an empty panorama
616  return true;
617  }
618  fname.Assign(dlg.GetPath());
619  }
620  // check if image size is correct
621  HuginBase::SrcPanoImage srcImg = pano.getSrcImage(i);
622  //
623  vigra::ImageImportInfo imginfo(srcImg.getFilename().c_str());
624  if (srcImg.getSize() != imginfo.size()) {
625  // adjust size properly.
626  srcImg.resize(imginfo.size(), &vars[i]);
627  }
628  // check if script contains invalid HFOV
629  double hfov = pano.getImage(i).getHFOV();
630  if (pano.getImage(i).getProjection() == HuginBase::SrcPanoImage::RECTILINEAR
631  && hfov >= 180 && autopanoSiftFile == false)
632  {
633  autopanoSiftFile = true;
634  // something is wrong here, try to read from exif data (all images)
635  bool ok = srcImg.readEXIF();
636  if(ok) {
637  ok = srcImg.applyEXIFValues();
638  };
639  if (! ok) {
640  getLensDataFromUser(wxGetActiveWindow(), srcImg);
641  }
642  autopanoSiftRefImg = srcImg;
643  }
644  else
645  {
646  // load exif data
647  srcImg.readEXIF();
648  if (autopanoSiftFile)
649  {
650  // need to copy the lens parameters from the first lens.
651  srcImg.setHFOV(autopanoSiftRefImg.getHFOV());
652  };
653  };
654  // remember icc profile, only from first image
655  if (i == 0)
656  {
657  pano.setNrOfBands(imginfo.numBands() - imginfo.numExtraBands());
658  pano.setICCProfileDesc(hugin_utils::GetICCDesc(imginfo.getICCProfile()));
659  }
660  pano.setSrcImage(i, srcImg);
661  }
662  // special update routine, when images size changed
663  // and some linked variables needs update after rescaling
664  for (unsigned i = 0; i < nImg; ++i)
665  {
666  if (!vars[i].empty())
667  {
668  pano.updateVariables(i, vars[i]);
669  };
670  };
671  // Link image projection across each lens, since it is not saved.
672  const HuginBase::UIntSetVector imgSetLens = lenses.getPartsSet();
673  for (size_t i = 0; i < imgSetLens.size(); ++i)
674  {
675  const HuginBase::UIntSet imgLens = imgSetLens[i];
676  if (imgLens.size()>1)
677  {
678  HuginBase::UIntSet::const_iterator it = imgLens.begin();
679  const size_t img1 = *it;
680  ++it;
681  do
682  {
683  pano.linkImageVariableProjection(img1, *it);
684  ++it;
685  } while (it != imgLens.end());
686  };
687  };
688  } else {
689  DEBUG_ERROR("could not load panotools script");
690  in.close();
691  return false;
692  };
693  in.close();
694 
695  // Verify control points are valid
696  // loop through entire list of points, confirming they are inside the
697  // bounding box of their images
698  const HuginBase::CPVector & oldCPs = pano.getCtrlPoints();
699  HuginBase::CPVector goodCPs;
700  int bad_cp_count = 0;
701  for (HuginBase::CPVector::const_iterator it = oldCPs.begin();
702  it != oldCPs.end(); ++it)
703  {
704  HuginBase::ControlPoint point = *it;
705  const HuginBase::SrcPanoImage & img1 = pano.getImage(point.image1Nr);
706  const HuginBase::SrcPanoImage & img2 = pano.getImage(point.image2Nr);
707  if (0 > point.x1 || point.x1 > img1.getSize().x ||
708  0 > point.y1 || point.y1 > img1.getSize().y ||
709  0 > point.x2 || point.x2 > img2.getSize().x ||
710  0 > point.y2 || point.y2 > img2.getSize().y)
711  {
712  bad_cp_count++;
713  } else
714  {
715  goodCPs.push_back(point);
716  }
717  }
718 
719  if (bad_cp_count > 0)
720  {
721  wxString errMsg = wxString::Format(_("%d invalid control point(s) found.\n\nPress OK to remove."), bad_cp_count);
722  wxMessageBox(errMsg, _("Error Detected"), wxICON_ERROR);
723  pano.setCtrlPoints(goodCPs);
724  }
725 
726  // check stacks and warn users in case
727  CheckLensStacks(&pano, false);
728  // Update control point error values
730  if(markAsOptimized)
731  {
732  pano.markAsOptimized();
733  };
734  return true;
735 }
736 
738 {
739  pano.reset();
740 
741  // Setup pano with options from preferences
743  wxConfigBase* config = wxConfigBase::Get();
744  opts.quality = config->Read(wxT("/output/jpeg_quality"),HUGIN_JPEG_QUALITY);
745  switch(config->Read(wxT("/output/tiff_compression"), HUGIN_TIFF_COMPRESSION))
746  {
747  case 0:
748  default:
749  opts.outputImageTypeCompression = "NONE";
750  opts.tiffCompression = "NONE";
751  break;
752  case 1:
753  opts.outputImageTypeCompression = "PACKBITS";
754  opts.tiffCompression = "PACKBITS";
755  break;
756  case 2:
757  opts.outputImageTypeCompression = "LZW";
758  opts.tiffCompression = "LZW";
759  break;
760  case 3:
761  opts.outputImageTypeCompression = "DEFLATE";
762  opts.tiffCompression = "DEFLATE";
763  break;
764  }
765  switch (config->Read(wxT("/output/ldr_format"), HUGIN_LDR_OUTPUT_FORMAT)) {
766  case 1:
767  opts.outputImageType ="jpg";
768  break;
769  case 2:
770  opts.outputImageType ="png";
771  break;
772  case 3:
773  opts.outputImageType ="exr";
774  break;
775  default:
776  case 0:
777  opts.outputImageType ="tif";
778  break;
779  }
780  // HDR disabled because there is no real choice at the moment: HDR TIFF is broken and there is only EXR
781  // opts.outputImageTypeHDR = config->Read(wxT("/output/hdr_format"), HUGIN_HDR_OUTPUT_FORMAT);
783  opts.blendMode = static_cast<HuginBase::PanoramaOptions::BlendingMechanism>(config->Read(wxT("/default_blender"), HUGIN_DEFAULT_BLENDER));
784  opts.enblendOptions = config->Read(wxT("Enblend/Args"),wxT(HUGIN_ENBLEND_ARGS)).mb_str(wxConvLocal);
785  opts.enfuseOptions = config->Read(wxT("Enfuse/Args"),wxT(HUGIN_ENFUSE_ARGS)).mb_str(wxConvLocal);
786  opts.verdandiOptions = config->Read(wxT("/VerdandiDefaultArgs"), wxEmptyString).mb_str(wxConvLocal);
787  opts.interpolator = (vigra_ext::Interpolator)config->Read(wxT("Nona/Interpolator"),HUGIN_NONA_INTERPOLATOR);
788  opts.remapUsingGPU = config->Read(wxT("Nona/useGPU"),HUGIN_NONA_USEGPU)!=0;
789  opts.tiff_saveROI = config->Read(wxT("Nona/CroppedImages"),HUGIN_NONA_CROPPEDIMAGES)!=0;
792  pano.setOptions(opts);
793 
796  return true;
797 }
798 
799 
801 {
802  wxConfigBase* config = wxConfigBase::Get();
803 
804  if (pano.getNrOfImages() == 0) {
805  // TODO: prompt for images!
806  wxString path = config->Read(wxT("actualPath"), wxT(""));
807  wxFileDialog dlg(wxGetActiveWindow(), _("Add images"),
808  path, wxT(""),
809  GetFileDialogImageFilters(), wxFD_OPEN|wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST | wxFD_PREVIEW , wxDefaultPosition);
810  dlg.SetDirectory(path);
811 
812  // remember the image extension
813  wxString img_ext;
814  if (config->HasEntry(wxT("lastImageType"))){
815  img_ext = config->Read(wxT("lastImageType")).c_str();
816  }
817  if (img_ext == wxT("all images"))
818  dlg.SetFilterIndex(0);
819  else if (img_ext == wxT("jpg"))
820  dlg.SetFilterIndex(1);
821  else if (img_ext == wxT("tiff"))
822  dlg.SetFilterIndex(2);
823  else if (img_ext == wxT("png"))
824  dlg.SetFilterIndex(3);
825  else if (img_ext == wxT("hdr"))
826  dlg.SetFilterIndex(4);
827  else if (img_ext == wxT("exr"))
828  dlg.SetFilterIndex(5);
829  else if (img_ext == wxT("all files"))
830  dlg.SetFilterIndex(6);
831  DEBUG_INFO ( "Image extention: " << img_ext.mb_str(wxConvLocal) );
832 
833  // call the file dialog
834  if (dlg.ShowModal() == wxID_OK) {
835  // get the selections
836  wxArrayString Pathnames;
837  dlg.GetPaths(Pathnames);
838 
839  // save the current path to config
840 #ifdef __WXGTK__
841  //workaround a bug in GTK, see https://bugzilla.redhat.com/show_bug.cgi?id=849692 and http://trac.wxwidgets.org/ticket/14525
842  config->Write(wxT("/actualPath"), wxPathOnly(Pathnames[0]));
843 #else
844  config->Write(wxT("/actualPath"), dlg.GetDirectory());
845 #endif
846  DEBUG_INFO ( wxString::Format(wxT("img_ext: %d"), dlg.GetFilterIndex()).mb_str(wxConvLocal) );
847  // save the image extension
848  switch ( dlg.GetFilterIndex() ) {
849  case 0: config->Write(wxT("lastImageType"), wxT("all images")); break;
850  case 1: config->Write(wxT("lastImageType"), wxT("jpg")); break;
851  case 2: config->Write(wxT("lastImageType"), wxT("tiff")); break;
852  case 3: config->Write(wxT("lastImageType"), wxT("png")); break;
853  case 4: config->Write(wxT("lastImageType"), wxT("hdr")); break;
854  case 5: config->Write(wxT("lastImageType"), wxT("exr")); break;
855  case 6: config->Write(wxT("lastImageType"), wxT("all files")); break;
856  }
857 
858  HuginBase::StandardImageVariableGroups variable_groups(pano);
859  HuginBase::ImageVariableGroup & lenses = variable_groups.getLenses();
860  // add images.
861  for (unsigned int i=0; i< Pathnames.GetCount(); i++) {
862  std::string filename = (const char *)Pathnames[i].mb_str(HUGIN_CONV_FILENAME);
863  vigra::ImageImportInfo inf(filename.c_str());
865  img.setFilename(filename);
866  img.setSize(inf.size());
867  img.readEXIF();
868  img.applyEXIFValues();
869  int imgNr = pano.addImage(img);
870  lenses.updatePartNumbers();
871  if (i > 0) lenses.switchParts(imgNr, 0);
872  }
873 
874  }
875  }
876 
877  unsigned int nOldImg = pano.getNrOfImages();
878  HuginBase::PanoramaMemento newPanoMem;
879 
880  int ptoVersion = 0;
881  if (newPanoMem.loadPTScript(in, ptoVersion, "")) {
882  HuginBase::Panorama newPano;
883  newPano.setMemento(newPanoMem);
884 
885  unsigned int nNewImg = newPano.getNrOfImages();
886  if (nOldImg != nNewImg) {
887  wxString errMsg = wxString::Format(_("Error, template expects %d images,\ncurrent project contains %d images\n"), nNewImg, nOldImg);
888  wxMessageBox(errMsg, _("Could not apply template"), wxICON_ERROR);
889  return false;
890  }
891 
892  // check image sizes, and correct parameters if required.
893  HuginBase::VariableMapVector vars(nNewImg);
894  for (unsigned int i = 0; i < nNewImg; i++) {
895 
896  // check if image size is correct
897  const HuginBase::SrcPanoImage & oldSrcImg = pano.getImage(i);
898  HuginBase::SrcPanoImage newSrcImg = newPano.getSrcImage(i);
899 
900  // just keep the file name
901  DEBUG_DEBUG("apply template fn:" << newSrcImg.getFilename() << " real fn: " << oldSrcImg.getFilename());
902  newSrcImg.setFilename(oldSrcImg.getFilename());
903  if (oldSrcImg.getSize() != newSrcImg.getSize()) {
904  // adjust size properly.
905  newSrcImg.resize(oldSrcImg.getSize(), &(vars[i]));
906  }
907  newPano.setSrcImage(i, newSrcImg);
908  }
909  // now update all possible linked variables
910  // this has to be done separately, otherwise the linked variables
911  // would be updated several times
912  for (unsigned int i = 0; i < nNewImg; ++i)
913  {
914  if (!vars[i].empty())
915  {
916  newPano.updateVariables(i, vars[i]);
917  };
918  };
919  // keep old control points.
920  newPano.setCtrlPoints(pano.getCtrlPoints());
921  newPanoMem = newPano.getMemento();
922  // remmember setting of image type and icc profile
923  const int bands = pano.getNrOfBands();
924  const std::string iccProfile = pano.getICCProfileDesc();
925  pano.setMemento(newPanoMem);
926  pano.setNrOfBands(bands);
927  pano.setICCProfileDesc(iccProfile);
928  } else {
929  wxMessageBox(_("Error loading project file"), _("Could not apply template"), wxICON_ERROR);
930  }
932  return true;
933 }
934 
935 #ifdef HUGIN_HSI
936 bool PythonScriptPanoCmd::processPanorama(HuginBase::Panorama& pano)
937 {
938  std::cout << "run python script: " << m_scriptFile.c_str() << std::endl;
939 
940  int success = hpi::callhpi ( m_scriptFile.c_str() , 1 ,
941  "HuginBase::Panorama*" , &pano ) ;
942 
943  if(success!=0)
944  wxMessageBox(wxString::Format(wxT("Script returned %d"),success),_("Result"), wxICON_INFORMATION);
945  std::cout << "Python interface returned " << success << endl ;
946  // notify other of change in panorama
947  if(pano.getNrOfImages()>0)
948  {
949  for(unsigned int i=0;i<pano.getNrOfImages();i++)
950  {
951  pano.imageChanged(i);
952  };
953  };
955  return true;
956 }
957 #endif
958 
959 } // namespace
960 
#define DEBUG_INFO(msg)
Definition: utils.h:69
std::string GetICCDesc(const vigra::ImageImportInfo::ICCProfile &iccProfile)
returns description of given icc profile
Definition: utils.cpp:925
implementation of huginApp Class
bool FileExists(const std::string &filename)
checks if file exists
Definition: utils.cpp:362
void imageChanged(unsigned int imgNr)
mark image for change notification.
Definition: Panorama.cpp:1568
std::vector< UIntSet > UIntSetVector
Definition: PanoramaData.h:56
const int getNrOfBands() const
return number of bands of first image (without alpha channel) so it can be 1 for grayscale or 3 for r...
Definition: Panorama.cpp:1608
virtual bool processPanorama(HuginBase::Panorama &pano)
Called by execute().
#define HUGIN_NONA_CROPPEDIMAGES
bool applyEXIFValues(bool applyEVValue=true)
apply values found in EXIF data to SrcPanoImage class, call readEXIF() before to initialize some valu...
void setMemento(const PanoramaMemento &memento)
set the internal state
Definition: Panorama.cpp:1507
unsigned int getPartNumber(unsigned int imageNr) const
Get a part number from an image number.
void transformImage(vigra::triple< SrcImageIterator, SrcImageIterator, SrcAccessor > src, vigra::triple< DestImageIterator, DestImageIterator, DestAccessor > dest, std::pair< AlphaImageIterator, AlphaAccessor > alpha, vigra::Diff2D destUL, TRANSFORM &transform, PixelTransform &pixelTransform, bool warparound, Interpolator interpol, AppBase::ProgressDisplay *progress, bool singleThreaded=false)
Transform an image into the panorama.
std::vector< std::string > files
Definition: wxPanoCommand.h:57
SrcPanoImage getSrcImage(unsigned imgNr) const
get a description of a source image
Definition: Panorama.cpp:1620
#define HUGIN_CONV_FILENAME
Definition: platform.h:40
A dialog for HFOV.
Definition: HFOVDialog.h:37
#define DEBUG_TRACE(msg)
Definition: utils.h:67
#define HUGIN_JPEG_QUALITY
void setPhotometricOptimizerSwitch(const int newSwitch)
sets the photometric optimizer master switch
Definition: Panorama.cpp:311
Somewhere to specify what variables belong to what.
std::string GetICCProfileNameChecked(const std::string &iccName)
return name of icc profile with same checks for comparision
some helper classes for graphes
Functor class to compare two objects with the &quot;Alphanum Algorithm&quot;.
Definition: alphanum.h:65
include file for the hugin project
#define HUGIN_GUI_SORT_NEW_IMG_ON_ADD
const CPVector & getCtrlPoints() const
get all control point of this Panorama
Definition: Panorama.h:319
int getHeight() const
Get the height of the image in pixels.
Definition: SrcPanoImage.h:276
std::string outputImageTypeCompression
vigra::pair< typename ROIImage< Image, Mask >::image_const_traverser, typename ROIImage< Image, Mask >::ImageConstAccessor > srcImage(const ROIImage< Image, Mask > &img)
Definition: ROIImage.h:300
void setOptimizerSwitch(const int newSwitch)
set optimizer master switch
Definition: Panorama.cpp:303
represents a control point
Definition: ControlPoint.h:38
virtual void updateVariables(const VariableMapVector &vars)
Set the variables.
Definition: Panorama.cpp:171
bool readProjectionFromDB(const bool ignoreFovRectilinear=true)
tries to read projection and crop area from lens database you need to call SrcPanoImage::readEXIF bef...
std::set< unsigned int > UIntSet
Definition: PanoramaData.h:51
std::vector< VariableMap > VariableMapVector
void calcCtrlPointErrors(PanoramaData &pano)
Update the Ctrl Point errors without optimizing.
void linkPossibleStacks(bool linkPosition)
create automatically stacks as indicated by metadata
Definition: Panorama.cpp:2181
Model for a panorama.
Definition: Panorama.h:152
empirical model of response
Definition: SrcPanoImage.h:100
C++ call interface to hpi.
UIntSetVector getPartsSet() const
return a vector which contains a HuginBase::UIntSet for each group with the corresponding images numb...
unsigned int addCtrlPoint(const ControlPoint &point)
add a new control point.
Definition: Panorama.cpp:381
bool loadPTScript(std::istream &i, int &ptoVersion, const std::string &prefix="")
load a Hugin file
some definitions to work with optimizer master switches
std::size_t getNrOfImages() const
number of images.
Definition: Panorama.h:205
void combineTwoImages(SrcImageIterator1 src1_upperleft, SrcImageIterator1 src1_lowerright, SrcAccessor1 src1_acc, SrcImageIterator2 src2_upperleft, SrcAccessor2 src2_acc, DestImageIterator dest_upperleft, DestAccessor dest_acc, const Functor &func)
Definition: openmp_vigra.h:249
void createInvTransform(const vigra::Diff2D &srcSize, VariableMap srcVars, Lens::LensProjectionFormat srcProj, const vigra::Diff2D &destSize, PanoramaOptions::ProjectionFormat destProj, const std::vector< double > &destProjParam, double destHFOV, const vigra::Diff2D &origSrcSize)
create image-&gt;pano transformation
void setCtrlPoints(const CPVector &points)
set all control points (Ippei: Is this supposed to be &#39;add&#39; method?)
Definition: Panorama.cpp:449
vigra_ext::Interpolator interpolator
int callhpi(const char *plugin_name, int argc,...)
simplified call interface to the Python Plugin facility.
Definition: hpi.cpp:55
virtual bool processPanorama(HuginBase::Panorama &pano)
Called by execute().
int getWidth() const
Get the width of the image in pixels.
Definition: SrcPanoImage.h:266
bool getLensDataFromUser(wxWindow *parent, HuginBase::SrcPanoImage &srcImg)
void resize(const vigra::Size2D &size, VariableMap *potentialLinkedVars)
&quot;resize&quot; image, adjusts all distortion coefficients for usage with a source image of size size potent...
ImageVariableGroup & getLenses()
Get the ImageVariableGroup representing the group of lens variables.
#define HUGIN_NONA_INTERPOLATOR
#define HUGIN_TIFF_COMPRESSION
wxwindows specific panorama commands
void setImageFilename(unsigned int img, const std::string &fname)
set a new image filename
Definition: Panorama.cpp:373
#define HUGIN_NONA_USEGPU
vigra::pair< typename ROIImage< Image, Alpha >::image_traverser, typename ROIImage< Image, Alpha >::ImageAccessor > destImage(ROIImage< Image, Alpha > &img)
Definition: ROIImage.h:324
#define DEBUG_ERROR(msg)
Definition: utils.h:76
vigra::triple< typename ROIImage< Image, Mask >::image_const_traverser, typename ROIImage< Image, Mask >::image_const_traverser, typename ROIImage< Image, Mask >::ImageConstAccessor > srcImageRange(const ROIImage< Image, Mask > &img)
helper function for ROIImages
Definition: ROIImage.h:287
#define HUGIN_HDRMERGE_ARGS
virtual bool processPanorama(HuginBase::Panorama &pano)
Called by execute().
#define HUGIN_LDR_OUTPUT_FORMAT
unsigned int addImage(const SrcPanoImage &img)
the the number for a specific image
Definition: Panorama.cpp:319
const std::string getICCProfileDesc() const
return description of icc profile used for pano
Definition: Panorama.cpp:1598
Same as above, but use a non const panorama.
PanoramaMemento getMemento() const
get the internal state
Definition: Panorama.h:602
void setHFOV(double h, bool keepView=true)
set the horizontal field of view.
include file for the hugin project
bool transformImgCoord(double &x_dest, double &y_dest, double x_src, double y_src) const
like transform, but return image coordinates, not cartesian coordinates
void setICCProfileDesc(const std::string &newDesc)
sets the icc profile description for check of same profile
Definition: Panorama.cpp:1603
const PanoramaOptions & getOptions() const
returns the options for this panorama
Definition: Panorama.h:481
void markAsOptimized(bool optimized=true)
Definition: Panorama.h:618
Memento class for a Panorama object.
Definition: Panorama.h:49
double GetCropFactor()
Definition: HFOVDialog.cpp:355
void updatePartNumbers()
Update the part numbers, call this when the panorama changes.
Holds transformations for Image -&gt; Pano and the other way.
#define HUGIN_ENFUSE_ARGS
#define DEBUG_DEBUG(msg)
Definition: utils.h:68
bool readEXIF()
try to fill out information about the image, by examining the exif data
void setSize(vigra::Size2D val)
Set the image size in pixels.
void update()
Update part numbers for each variable group.
std::vector< ControlPoint > CPVector
Definition: ControlPoint.h:99
platform/compiler specific stuff.
void copyImage(SrcImageIterator src_upperleft, SrcImageIterator src_lowerright, SrcAccessor src_acc, DestImageIterator dest_upperleft, DestAccessor dest_acc)
Definition: openmp_vigra.h:305
void applyColorBalanceValue(HuginBase::SrcPanoImage &srcImg, HuginBase::Panorama &pano)
static void info(const char *fmt,...)
Definition: svm.cpp:95
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
void setSrcImage(unsigned int nr, const SrcPanoImage &img)
set input image parameters
bool CheckLensStacks(HuginBase::Panorama *pano, bool allowCancel)
check, if lens and stacks are correctly linked shows message box with short information if not ...
Definition: LensTools.cpp:446
void setNrOfBands(const int nrBands)
sets the number of bands
Definition: Panorama.cpp:1613
Interpolator
enum with all interpolation methods
Definition: Interpolators.h:78
HuginBase::SrcPanoImage GetSrcImage()
Definition: HFOVDialog.cpp:348
All variables of a source image.
Definition: SrcPanoImage.h:194
void setProjection(ProjectionFormat f)
set the Projection format and adjust the hfov/vfov if nessecary
wxString GetFileDialogImageFilters()
return filter for image files, needed by file open dialog it contains all image format vigra can read...
Definition: platform.cpp:69
Panorama image options.
void switchParts(unsigned int ImageNr, unsigned int partNr)
switch a given image to a different part number.
const bool hasPossibleStacks() const
return true, if the metadata indicates that the projects is a bracketet project
Definition: Panorama.cpp:2137
#define HUGIN_ENBLEND_ARGS
const double getMaxExposureDifference() const
returns the maximum exposure value difference of all images in the project
Definition: Panorama.cpp:2120
#define HUGIN_DEFAULT_BLENDER
bool readCropfactorFromDB()
tries to read cropfactor from lens database you need to call SrcPanoImage::readEXIF before to fill so...
void reset()
clear the internal state.
Definition: Panorama.cpp:69
BlendingMechanism blendMode
virtual bool processPanorama(HuginBase::Panorama &pano)
Called by execute().
void createTransform(const vigra::Diff2D &srcSize, VariableMap srcVars, Lens::LensProjectionFormat srcProj, const vigra::Diff2D &destSize, PanoramaOptions::ProjectionFormat destProj, const std::vector< double > &destProjParam, double destHFOV, const vigra::Diff2D &origSrcSize)
initialize pano-&gt;image transformation
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 setWidth(unsigned int w, bool keepView=true)
set panorama width keep the HFOV, if keepView=true
virtual bool processPanorama(HuginBase::Panorama &pano)
Called by execute().