1 // -*- c-basic-offset: 4 -*-
2
3 /** @file CPEditorPanel.cpp
4 *
5 * @brief implementation of CPEditorPanel Class
6 *
7 * @author Pablo d'Angelo <pablo.dangelo@web.de>
8 *
9 * $Id$
10 *
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 "hugin_config.h"
28
29 // often necessary before panoinc.h
30 #include "panoinc_WX.h"
31 // standard hugin include
32 #include "panoinc.h"
33 // both includes above need to come before other wx includes on OSX
34
35 // hugin's
36 #include "hugin/huginApp.h"
37 #include "hugin/config_defaults.h"
38 #include "base_wx/CommandHistory.h"
39 #include "base_wx/wxImageCache.h"
40 #include "hugin/CPImageCtrl.h"
41 #include "hugin/TextKillFocusHandler.h"
42 #include "hugin/CPEditorPanel.h"
43 #include "base_wx/wxPanoCommand.h"
44 #include "base_wx/MyProgressDialog.h"
45 #include "algorithms/optimizer/PTOptimizer.h"
46 #include "algorithms/basic/CalculateOptimalScale.h"
47 #include "base_wx/PTWXDlg.h"
48 #include "base_wx/wxPlatform.h"
49
50 // more standard includes if needed
51 #include <algorithm>
52 #include <float.h>
53 #include <vector>
54
55 // more vigra include if needed
56 #include "vigra/cornerdetection.hxx"
57 #include "vigra/localminmax.hxx"
58 #include "vigra_ext/openmp_vigra.h"
59 #include "vigra_ext/Correlation.h"
60 #include "vigra_ext/cms.h"
61
62 // Celeste header
63 #include "Celeste.h"
64
BEGIN_EVENT_TABLE(CPEditorPanel,wxPanel)65 BEGIN_EVENT_TABLE(CPEditorPanel, wxPanel)
66 EVT_CPEVENT(CPEditorPanel::OnCPEvent)
67 EVT_COMBOBOX(XRCID("cp_editor_left_choice"), CPEditorPanel::OnLeftChoiceChange )
68 EVT_COMBOBOX(XRCID("cp_editor_right_choice"), CPEditorPanel::OnRightChoiceChange )
69 EVT_LIST_ITEM_SELECTED(XRCID("cp_editor_cp_list"), CPEditorPanel::OnCPListSelect)
70 EVT_LIST_ITEM_DESELECTED(XRCID("cp_editor_cp_list"), CPEditorPanel::OnCPListDeselect)
71 EVT_LIST_COL_END_DRAG(XRCID("cp_editor_cp_list"), CPEditorPanel::OnColumnWidthChange)
72 EVT_LIST_COL_CLICK(XRCID("cp_editor_cp_list"), CPEditorPanel::OnColumnHeaderClick)
73 EVT_CHOICE(XRCID("cp_editor_choice_zoom"), CPEditorPanel::OnZoom)
74 EVT_TEXT_ENTER(XRCID("cp_editor_x1"), CPEditorPanel::OnTextPointChange )
75 EVT_TEXT_ENTER(XRCID("cp_editor_y1"), CPEditorPanel::OnTextPointChange )
76 EVT_TEXT_ENTER(XRCID("cp_editor_x2"), CPEditorPanel::OnTextPointChange )
77 EVT_TEXT_ENTER(XRCID("cp_editor_y2"), CPEditorPanel::OnTextPointChange )
78 EVT_CHOICE(XRCID("cp_editor_mode"), CPEditorPanel::OnTextPointChange )
79 EVT_CHAR(CPEditorPanel::OnKey)
80 EVT_BUTTON(XRCID("cp_editor_delete"), CPEditorPanel::OnDeleteButton)
81 EVT_BUTTON(XRCID("cp_editor_add"), CPEditorPanel::OnAddButton)
82 EVT_BUTTON(XRCID("cp_editor_previous_img"), CPEditorPanel::OnPrevImg)
83 EVT_BUTTON(XRCID("cp_editor_next_img"), CPEditorPanel::OnNextImg)
84 EVT_BUTTON(XRCID("cp_editor_finetune_button"), CPEditorPanel::OnFineTuneButton)
85 EVT_BUTTON(XRCID("cp_editor_action_button"), CPEditorPanel::OnActionButton)
86 EVT_MENU(XRCID("cp_menu_create_cp"), CPEditorPanel::OnActionSelectCreate)
87 EVT_MENU(XRCID("cp_menu_celeste"), CPEditorPanel::OnActionSelectCeleste)
88 EVT_MENU(XRCID("cp_menu_clean_cp"), CPEditorPanel::OnActionSelectCleanCP)
89 END_EVENT_TABLE()
90
91 CPEditorPanel::CPEditorPanel()
92 {
93 DEBUG_TRACE("**********************");
94 m_pano = 0;
95 m_countCP = 0;
96 }
97
98 // helper function to set image in header
SetColumnImage(wxListCtrl * list,int col,int image)99 void SetColumnImage(wxListCtrl* list, int col, int image)
100 {
101 wxListItem item;
102 item.SetMask(wxLIST_MASK_IMAGE);
103 item.SetImage(image);
104 list->SetColumn(col, item);
105 };
106
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style,const wxString & name)107 bool CPEditorPanel::Create(wxWindow* parent, wxWindowID id,
108 const wxPoint& pos,
109 const wxSize& size,
110 long style,
111 const wxString& name)
112 {
113 DEBUG_TRACE(" Create called *************");
114 if (! wxPanel::Create(parent, id, pos, size, style, name) ) {
115 return false;
116 }
117
118 cpCreationState = NO_POINT;
119 m_leftImageNr=UINT_MAX;
120 m_rightImageNr=UINT_MAX;
121 m_listenToPageChange=true;
122 m_detailZoomFactor=1;
123 m_selectedPoint=UINT_MAX;
124 m_leftRot=CPImageCtrl::ROT0;
125 m_rightRot=CPImageCtrl::ROT0;
126
127 DEBUG_TRACE("");
128 wxXmlResource::Get()->LoadPanel(this, wxT("cp_editor_panel"));
129 wxPanel * panel = XRCCTRL(*this, "cp_editor_panel", wxPanel);
130
131 wxBoxSizer *topsizer = new wxBoxSizer( wxVERTICAL );
132 topsizer->Add(panel, 1, wxEXPAND, 0);
133
134 m_leftChoice = XRCCTRL(*this, "cp_editor_left_choice", CPImagesComboBox);
135 m_leftImg = XRCCTRL(*this, "cp_editor_left_img", CPImageCtrl);
136 assert(m_leftImg);
137 m_leftImg->Init(this);
138 m_leftImg->setTransforms(&m_leftTransform, &m_leftInvTransform, &m_rightInvTransform);
139
140 // right image
141 m_rightChoice = XRCCTRL(*this, "cp_editor_right_choice", CPImagesComboBox);
142 m_rightImg = XRCCTRL(*this, "cp_editor_right_img", CPImageCtrl);
143 assert(m_rightImg);
144 m_rightImg->Init(this);
145 m_rightImg->setTransforms(&m_rightTransform, &m_rightInvTransform, &m_leftInvTransform);
146
147 // setup list view
148 m_cpList = XRCCTRL(*this, "cp_editor_cp_list", wxListCtrl);
149 m_cpList->Connect(wxEVT_CHAR,wxKeyEventHandler(CPEditorPanel::OnKey),NULL,this);
150 m_cpList->InsertColumn( 0, _("#"), wxLIST_FORMAT_RIGHT, 35);
151 m_cpList->InsertColumn( 1, _("left x"), wxLIST_FORMAT_RIGHT, 65);
152 m_cpList->InsertColumn( 2, _("left y"), wxLIST_FORMAT_RIGHT, 65);
153 m_cpList->InsertColumn( 3, _("right x"), wxLIST_FORMAT_RIGHT, 65);
154 m_cpList->InsertColumn( 4, _("right y"), wxLIST_FORMAT_RIGHT, 65);
155 m_cpList->InsertColumn( 5, _("Alignment"), wxLIST_FORMAT_LEFT,110 );
156 m_cpList->InsertColumn( 6, _("Distance"), wxLIST_FORMAT_RIGHT, 110);
157
158 //get saved width
159 wxConfigBase* config = wxConfig::Get();
160 for ( int j=0; j < m_cpList->GetColumnCount() ; j++ )
161 {
162 // -1 is auto
163 int width = config->Read(wxString::Format( wxT("/CPEditorPanel/ColumnWidth%d"), j ), -1);
164 if(width != -1)
165 m_cpList->SetColumnWidth(j, width);
166 }
167 m_sortCol = config->Read(wxT("/CPEditorPanel/SortColumn"), -1);
168 m_sortAscending = config->Read(wxT("/CPEditorPanel/SortAscending"), 1) == 1 ? true : false;
169 if (m_sortCol != -1)
170 {
171 SetColumnImage(m_cpList, m_sortCol, m_sortAscending ? 0 : 1);
172 };
173
174 // creating bitmaps for indicating sorting order
175 wxMemoryDC memDC;
176 memDC.SetFont(GetFont());
177 wxSize fontSize = memDC.GetTextExtent(wxT("\u25b3"));
178 wxCoord charSize = std::max(fontSize.GetWidth(), fontSize.GetHeight());
179 wxImageList* sortIcons = new wxImageList(charSize, charSize, true, 0);
180 {
181 wxBitmap bmp(charSize, charSize);
182 wxMemoryDC dc(bmp);
183 dc.SetBackgroundMode(wxPENSTYLE_TRANSPARENT);
184 dc.SetBackground(GetBackgroundColour());
185 dc.Clear();
186 dc.SetFont(GetFont());
187 dc.DrawText(wxT("\u25b3"), (charSize - fontSize.GetWidth()) / 2, (charSize - fontSize.GetHeight()) / 2);
188 dc.SelectObject(wxNullBitmap);
189 sortIcons->Add(bmp, GetBackgroundColour());
190 };
191 {
192 wxBitmap bmp(charSize, charSize);
193 wxMemoryDC dc(bmp);
194 dc.SetBackgroundMode(wxPENSTYLE_TRANSPARENT);
195 dc.SetBackground(GetBackgroundColour());
196 dc.Clear();
197 dc.SetFont(GetFont());
198 dc.DrawText(wxT("\u25bd"), (charSize - fontSize.GetWidth()) / 2, (charSize - fontSize.GetHeight()) / 2);
199 dc.SelectObject(wxNullBitmap);
200 sortIcons->Add(bmp, GetBackgroundColour());
201 };
202 m_cpList->AssignImageList(sortIcons, wxIMAGE_LIST_SMALL);
203
204 // other controls
205 m_x1Text = XRCCTRL(*this,"cp_editor_x1", wxTextCtrl);
206 m_x1Text->PushEventHandler(new TextKillFocusHandler(this));
207 m_y1Text = XRCCTRL(*this,"cp_editor_y1", wxTextCtrl);
208 m_y1Text->PushEventHandler(new TextKillFocusHandler(this));
209 m_x2Text = XRCCTRL(*this,"cp_editor_x2", wxTextCtrl);
210 m_x2Text->PushEventHandler(new TextKillFocusHandler(this));
211 m_y2Text = XRCCTRL(*this,"cp_editor_y2", wxTextCtrl);
212 m_y2Text->PushEventHandler(new TextKillFocusHandler(this));
213
214 m_cpModeChoice = XRCCTRL(*this, "cp_editor_mode", wxChoice);
215 m_addButton = XRCCTRL(*this, "cp_editor_add", wxButton);
216 m_delButton = XRCCTRL(*this, "cp_editor_delete", wxButton);
217
218 m_autoAddCB = XRCCTRL(*this,"cp_editor_auto_add", wxCheckBox);
219 DEBUG_ASSERT(m_autoAddCB);
220 m_fineTuneCB = XRCCTRL(*this,"cp_editor_fine_tune_check",wxCheckBox);
221 DEBUG_ASSERT(m_fineTuneCB);
222
223 m_estimateCB = XRCCTRL(*this,"cp_editor_auto_estimate", wxCheckBox);
224 DEBUG_ASSERT(m_estimateCB);
225
226 m_actionButton = XRCCTRL(*this, "cp_editor_action_button", wxButton);
227 m_actionButton->Connect(wxEVT_CONTEXT_MENU, wxContextMenuEventHandler(CPEditorPanel::OnActionContextMenu), NULL, this);
228 m_cpActionContextMenu = wxXmlResource::Get()->LoadMenu(wxT("cp_menu_action"));
229 // setup scroll window for the controls under the images
230 m_cp_ctrls = XRCCTRL(*this, "cp_controls_panel", wxPanel);
231 DEBUG_ASSERT(m_cp_ctrls);
232
233 m_autoAddCB->SetValue(config->Read(wxT("/CPEditorPanel/autoAdd"),0l) != 0 );
234 m_fineTuneCB->SetValue(config->Read(wxT("/CPEditorPanel/autoFineTune"),1l) != 0 );
235 m_estimateCB->SetValue(config->Read(wxT("/CPEditorPanel/autoEstimate"),1l) != 0 );
236
237 // disable controls by default
238 m_cpModeChoice->Disable();
239 m_addButton->Disable();
240 m_delButton->Disable();
241 m_autoAddCB->Disable();
242 m_fineTuneCB->Disable();
243 m_estimateCB->Disable();
244 XRCCTRL(*this, "cp_editor_finetune_button", wxButton)->Disable();
245 m_actionButton->Disable();
246 XRCCTRL(*this, "cp_editor_choice_zoom", wxChoice)->Disable();
247 XRCCTRL(*this, "cp_editor_previous_img", wxButton)->Disable();
248 XRCCTRL(*this, "cp_editor_next_img", wxButton)->Disable();
249 m_leftChoice->Disable();
250 m_rightChoice->Disable();
251
252 // apply zoom specified in xrc file
253 wxCommandEvent dummy;
254 dummy.SetInt(XRCCTRL(*this,"cp_editor_choice_zoom",wxChoice)->GetSelection());
255 OnZoom(dummy);
256
257 SetSizer( topsizer );
258 // read last used action setting
259 m_cpActionButtonMode = static_cast<CPTabActionButtonMode>(config->Read(wxT("/CPEditorPanel/ActionMode"), 1l));
260 switch (m_cpActionButtonMode)
261 {
262 case CPTAB_ACTION_CREATE_CP:
263 m_cpActionContextMenu->Check(XRCID("cp_menu_create_cp"), true);
264 {
265 wxCommandEvent e;
266 OnActionSelectCreate(e);
267 };
268 break;
269 case CPTAB_ACTION_CLEAN_CP:
270 m_cpActionContextMenu->Check(XRCID("cp_menu_clean_cp"), true);
271 {
272 wxCommandEvent e;
273 OnActionSelectCleanCP(e);
274 };
275 break;
276 case CPTAB_ACTION_CELESTE:
277 default:
278 m_cpActionContextMenu->Check(XRCID("cp_menu_celeste"), true);
279 {
280 wxCommandEvent e;
281 OnActionSelectCeleste(e);
282 };
283 break;
284 };
285
286 return true;
287 }
288
Init(HuginBase::Panorama * pano)289 void CPEditorPanel::Init(HuginBase::Panorama * pano)
290 {
291 m_pano=pano;
292 // observe the panorama
293 m_pano->addObserver(this);
294 }
295
~CPEditorPanel()296 CPEditorPanel::~CPEditorPanel()
297 {
298 DEBUG_TRACE("dtor");
299
300 m_x1Text->PopEventHandler(true);
301 m_y1Text->PopEventHandler(true);
302 m_x2Text->PopEventHandler(true);
303 m_y2Text->PopEventHandler(true);
304
305 wxConfigBase* config = wxConfig::Get();
306 config->Write(wxT("/CPEditorPanel/autoAdd"), m_autoAddCB->IsChecked() ? 1 : 0);
307 config->Write(wxT("/CPEditorPanel/autoFineTune"), m_fineTuneCB->IsChecked() ? 1 : 0);
308 config->Write(wxT("/CPEditorPanel/autoEstimate"), m_estimateCB->IsChecked() ? 1 : 0);
309 config->Write(wxT("/CPEditorPanel/SortColumn"), m_sortCol);
310 config->Write(wxT("/CPEditorPanel/SortAscending"), m_sortAscending ? 1 : 0);
311 config->Flush();
312
313 m_pano->removeObserver(this);
314 DEBUG_TRACE("dtor end");
315 }
316
setLeftImage(unsigned int imgNr)317 void CPEditorPanel::setLeftImage(unsigned int imgNr)
318 {
319 DEBUG_TRACE("image " << imgNr);
320 if (imgNr == UINT_MAX) {
321 m_leftImg->setImage("", CPImageCtrl::ROT0);
322 m_leftImageNr = imgNr;
323 m_leftFile = "";
324 changeState(NO_POINT);
325 UpdateDisplay(true);
326 } else if (m_leftImageNr != imgNr) {
327 double yaw = const_map_get(m_pano->getImageVariables(imgNr),"y").getValue();
328 double pitch = const_map_get(m_pano->getImageVariables(imgNr),"p").getValue();
329 double roll = const_map_get(m_pano->getImageVariables(imgNr),"r").getValue();
330 m_leftRot = GetRot(yaw, pitch, roll);
331 m_leftImg->setImage(m_pano->getImage(imgNr).getFilename(), m_leftRot);
332 m_leftImageNr = imgNr;
333 if (m_leftChoice->GetSelection() != (int) imgNr) {
334 m_leftChoice->SetSelection(imgNr);
335 }
336 m_rightChoice->SetRefImage(m_pano,m_leftImageNr);
337 m_rightChoice->Refresh();
338 m_leftFile = m_pano->getImage(imgNr).getFilename();
339 changeState(NO_POINT);
340 UpdateDisplay(true);
341 }
342 m_selectedPoint = UINT_MAX;
343 // FIXME: lets hope that nobody holds references to these images..
344 ImageCache::getInstance().softFlush();
345 UpdateTransforms();
346 }
347
348
setRightImage(unsigned int imgNr)349 void CPEditorPanel::setRightImage(unsigned int imgNr)
350 {
351 DEBUG_TRACE("image " << imgNr);
352 if (imgNr == UINT_MAX) {
353 m_rightImg->setImage("", CPImageCtrl::ROT0);
354 m_rightImageNr = imgNr;
355 m_rightFile = "";
356 m_rightRot = CPImageCtrl::ROT0;
357 changeState(NO_POINT);
358 UpdateDisplay(true);
359 } else if (m_rightImageNr != imgNr) {
360 // set the new image
361 double yaw = const_map_get(m_pano->getImageVariables(imgNr),"y").getValue();
362 double pitch = const_map_get(m_pano->getImageVariables(imgNr),"p").getValue();
363 double roll = const_map_get(m_pano->getImageVariables(imgNr),"r").getValue();
364 m_rightRot = GetRot(yaw, pitch, roll);
365 m_rightImg->setImage(m_pano->getImage(imgNr).getFilename(), m_rightRot);
366 // select tab
367 m_rightImageNr = imgNr;
368 if (m_rightChoice->GetSelection() != (int) imgNr) {
369 m_rightChoice->SetSelection(imgNr);
370 }
371 m_leftChoice->SetRefImage(m_pano,m_rightImageNr);
372 m_leftChoice->Refresh();
373 m_rightFile = m_pano->getImage(imgNr).getFilename();
374 // update the rest of the display (new control points etc)
375 changeState(NO_POINT);
376 UpdateDisplay(true);
377 }
378 m_selectedPoint = UINT_MAX;
379
380 // FIXME: lets hope that nobody holds references to these images..
381 ImageCache::getInstance().softFlush();
382 UpdateTransforms();
383 }
384
UpdateTransforms()385 void CPEditorPanel::UpdateTransforms()
386 {
387 if(m_leftImageNr<m_pano->getNrOfImages())
388 {
389 m_leftTransform.createTransform(m_pano->getImage(m_leftImageNr), m_pano->getOptions());
390 m_leftInvTransform.createInvTransform(m_pano->getImage(m_leftImageNr), m_pano->getOptions());
391 };
392 if(m_rightImageNr<m_pano->getNrOfImages())
393 {
394 m_rightTransform.createTransform(m_pano->getImage(m_rightImageNr), m_pano->getOptions());
395 m_rightInvTransform.createInvTransform(m_pano->getImage(m_rightImageNr), m_pano->getOptions());
396 };
397 }
398
399 // define sorting callbacks
SortIndexDescending(wxIntPtr item1,wxIntPtr item2,wxIntPtr WXUNUSED (sortData))400 int wxCALLBACK SortIndexDescending(wxIntPtr item1, wxIntPtr item2, wxIntPtr WXUNUSED(sortData))
401 {
402 if (item1 < item2)
403 return 1;
404 if (item1 > item2)
405 return -1;
406 return 0;
407 }
408
SortIndexAscending(wxIntPtr item1,wxIntPtr item2,wxIntPtr WXUNUSED (sortData))409 int wxCALLBACK SortIndexAscending(wxIntPtr item1, wxIntPtr item2, wxIntPtr WXUNUSED(sortData))
410 {
411 if (item1 > item2)
412 return 1;
413 if (item1 < item2)
414 return -1;
415 return 0;
416 }
417
418 #define SORTASCENDING(functionName, var) \
419 int wxCALLBACK functionName(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData)\
420 {\
421 HuginBase::CPointVector* data = (HuginBase::CPointVector*)(sortData);\
422 if (data->at(item1).second.var > data->at(item2).second.var)\
423 return 1;\
424 if (data->at(item1).second.var < data->at(item2).second.var)\
425 return -1;\
426 return 0;\
427 }
428
429 #define SORTDESCENDING(functionName, var) \
430 int wxCALLBACK functionName(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData)\
431 {\
432 HuginBase::CPointVector* data = (HuginBase::CPointVector*)(sortData); \
433 if (data->at(item1).second.var < data->at(item2).second.var)\
434 return 1; \
435 if (data->at(item1).second.var > data->at(item2).second.var)\
436 return -1; \
437 return 0; \
438 }
439
SORTASCENDING(SortX1Ascending,x1)440 SORTASCENDING(SortX1Ascending, x1)
441 SORTDESCENDING(SortX1Descending, x1)
442 SORTASCENDING(SortY1Ascending, y1)
443 SORTDESCENDING(SortY1Descending, y1)
444 SORTASCENDING(SortX2Ascending, x2)
445 SORTDESCENDING(SortX2Descending, x2)
446 SORTASCENDING(SortY2Ascending, y2)
447 SORTDESCENDING(SortY2Descending, y2)
448 SORTASCENDING(SortModeAscending, mode)
449 SORTDESCENDING(SortModeDescending, mode)
450 SORTASCENDING(SortErrorAscending, error)
451 SORTDESCENDING(SortErrorDescending, error)
452
453 #undef SORTASCENDING
454 #undef SORTDESCENDING
455
456 void CPEditorPanel::SortList()
457 {
458 if (m_sortCol > -1)
459 {
460 switch (m_sortCol)
461 {
462 case 0: // index
463 if (m_sortAscending)
464 {
465 m_cpList->SortItems(SortIndexAscending, 0);
466 }
467 else
468 {
469 m_cpList->SortItems(SortIndexDescending, 0);
470 };
471 break;
472 case 1: // x1
473 if (m_sortAscending)
474 {
475 m_cpList->SortItems(SortX1Ascending, wxIntPtr(¤tPoints));
476 }
477 else
478 {
479 m_cpList->SortItems(SortX1Descending, wxIntPtr(¤tPoints));
480 };
481 break;
482 case 2: // y1
483 if (m_sortAscending)
484 {
485 m_cpList->SortItems(SortY1Ascending, wxIntPtr(¤tPoints));
486 }
487 else
488 {
489 m_cpList->SortItems(SortY1Descending, wxIntPtr(¤tPoints));
490 };
491 break;
492 case 3: // x2
493 if (m_sortAscending)
494 {
495 m_cpList->SortItems(SortX2Ascending, wxIntPtr(¤tPoints));
496 }
497 else
498 {
499 m_cpList->SortItems(SortX2Descending, wxIntPtr(¤tPoints));
500 };
501 break;
502 case 4: // y2
503 if (m_sortAscending)
504 {
505 m_cpList->SortItems(SortY2Ascending, wxIntPtr(¤tPoints));
506 }
507 else
508 {
509 m_cpList->SortItems(SortY2Descending, wxIntPtr(¤tPoints));
510 };
511 break;
512 case 5: // mode
513 if (m_sortAscending)
514 {
515 m_cpList->SortItems(SortModeAscending, wxIntPtr(¤tPoints));
516 }
517 else
518 {
519 m_cpList->SortItems(SortModeDescending, wxIntPtr(¤tPoints));
520 };
521 break;
522 case 6: // error
523 if (m_sortAscending)
524 {
525 m_cpList->SortItems(SortErrorAscending, wxIntPtr(¤tPoints));
526 }
527 else
528 {
529 m_cpList->SortItems(SortErrorDescending, wxIntPtr(¤tPoints));
530 };
531 break;
532 };
533 };
534 }
535
OnCPEvent(CPEvent & ev)536 void CPEditorPanel::OnCPEvent( CPEvent& ev)
537 {
538 DEBUG_TRACE("");
539 wxString text;
540 unsigned int nr = ev.getPointNr();
541 hugin_utils::FDiff2D point = ev.getPoint();
542 bool left (TRUE);
543 if (ev.GetEventObject() == m_leftImg) {
544 left = true;
545 } else if (ev.GetEventObject() == m_rightImg){
546 left = false;
547 } else {
548 DEBUG_FATAL("UNKNOWN SOURCE OF CPEvent");
549 }
550
551 switch (ev.getMode()) {
552 case CPEvent::NONE:
553 text = wxT("NONE");
554 break;
555 case CPEvent::NEW_POINT_CHANGED:
556 NewPointChange(ev.getPoint(),left);
557 break;
558 case CPEvent::POINT_SELECTED:
559 // need to reset cpEditState
560 DEBUG_DEBUG("selected point " << nr);
561 SelectLocalPoint(nr);
562 changeState(NO_POINT);
563 break;
564 case CPEvent::NEW_LINE_ADDED:
565 {
566 float vertBias = getVerticalCPBias();
567 HuginBase::ControlPoint cp = ev.getControlPoint();
568 cp.image1Nr=m_leftImageNr;
569 cp.image2Nr=m_rightImageNr;
570 bool hor = std::abs(cp.x1 - cp.x2) > (std::abs(cp.y1 - cp.y2) * vertBias);
571 switch (m_leftRot)
572 {
573 case CPImageCtrl::ROT0:
574 case CPImageCtrl::ROT180:
575 if (hor)
576 cp.mode = HuginBase::ControlPoint::Y;
577 else
578 cp.mode = HuginBase::ControlPoint::X;
579 break;
580 default:
581 if (hor)
582 cp.mode = HuginBase::ControlPoint::X;
583 else
584 cp.mode = HuginBase::ControlPoint::Y;
585 break;
586 }
587 changeState(NO_POINT);
588 // create points
589 PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::AddCtrlPointCmd(*m_pano, cp));
590 // select new control Point
591 unsigned int lPoint = m_pano->getNrOfCtrlPoints()-1;
592 SelectGlobalPoint(lPoint);
593 changeState(NO_POINT);
594 MainFrame::Get()->SetStatusText(_("new control point added"));
595 m_leftChoice->CalcCPDistance(m_pano);
596 m_rightChoice->CalcCPDistance(m_pano);
597 break;
598 };
599 case CPEvent::POINT_CHANGED:
600 {
601 DEBUG_DEBUG("move point("<< nr << ")");
602 if (nr >= currentPoints.size()) {
603 DEBUG_ERROR("invalid point number while moving point")
604 return;
605 }
606 HuginBase::ControlPoint cp = ev.getControlPoint();
607 changeState(NO_POINT);
608 DEBUG_DEBUG("changing point to: " << cp.x1 << "," << cp.y1
609 << " " << cp.x2 << "," << cp.y2);
610
611 PanoCommand::GlobalCmdHist::getInstance().addCommand(
612 new PanoCommand::ChangeCtrlPointCmd(*m_pano, currentPoints[nr].first, cp)
613 );
614
615 break;
616 }
617 case CPEvent::RIGHT_CLICK:
618 {
619 if (cpCreationState == BOTH_POINTS_SELECTED) {
620 DEBUG_DEBUG("right click -> adding point");
621 CreateNewPoint();
622 } else {
623 DEBUG_DEBUG("right click without two points..");
624 changeState(NO_POINT);
625 }
626 break;
627 }
628 case CPEvent::CANCELED:
629 {
630 if (cpCreationState != NO_POINT)
631 {
632 changeState(NO_POINT);
633 };
634 break;
635 };
636 case CPEvent::SCROLLED:
637 {
638 wxPoint d(hugin_utils::roundi(point.x), hugin_utils::roundi(point.y));
639 d = m_rightImg->MaxScrollDelta(d);
640 d = m_leftImg->MaxScrollDelta(d);
641 m_rightImg->ScrollDelta(d);
642 m_leftImg->ScrollDelta(d);
643 }
644 break;
645 case CPEvent::DELETE_REGION_SELECTED:
646 {
647 HuginBase::UIntSet cpToRemove;
648 if(!currentPoints.empty())
649 {
650 wxRect rect=ev.getRect();
651 for(unsigned int i=0;i<currentPoints.size();i++)
652 {
653 HuginBase::ControlPoint cp = currentPoints[i].second;
654 if (cp.mode == HuginBase::ControlPoint::X_Y)
655 {
656 //checking only normal control points
657 if(left)
658 {
659 if (rect.Contains(hugin_utils::roundi(cp.x1), hugin_utils::roundi(cp.y1)))
660 {
661 cpToRemove.insert(localPNr2GlobalPNr(i));
662 };
663 }
664 else
665 {
666 if (rect.Contains(hugin_utils::roundi(cp.x2), hugin_utils::roundi(cp.y2)))
667 {
668 cpToRemove.insert(localPNr2GlobalPNr(i));
669 };
670 };
671 };
672 };
673 };
674 changeState(NO_POINT);
675 if(!cpToRemove.empty())
676 {
677 PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::RemoveCtrlPointsCmd(*m_pano, cpToRemove));
678 };
679 break;
680 }
681 } //end switch
682 m_leftImg->update();
683 m_rightImg->update();
684 }
685
686
CreateNewPoint()687 void CPEditorPanel::CreateNewPoint()
688 {
689 DEBUG_TRACE("");
690 hugin_utils::FDiff2D p1 = m_leftImg->getNewPoint();
691 hugin_utils::FDiff2D p2 = m_rightImg->getNewPoint();
692 HuginBase::ControlPoint point;
693 point.image1Nr = m_leftImageNr;
694 point.x1 = p1.x;
695 point.y1 = p1.y;
696 point.image2Nr = m_rightImageNr;
697 point.x2 = p2.x;
698 point.y2 = p2.y;
699 if (point.image1Nr == point.image2Nr) {
700 if (m_cpModeChoice->GetSelection()>=3) {
701 // keep line until user chooses new mode
702 point.mode = m_cpModeChoice->GetSelection();
703 } else {
704 // Most projections will have a bias to creating vertical
705 // constraints.
706 float vertBias = getVerticalCPBias();
707 bool hor = std::abs(p1.x - p2.x) > (std::abs(p1.y - p2.y) * vertBias);
708 switch (m_leftRot) {
709 case CPImageCtrl::ROT0:
710 case CPImageCtrl::ROT180:
711 if (hor)
712 point.mode = HuginBase::ControlPoint::Y;
713 else
714 point.mode = HuginBase::ControlPoint::X;
715 break;
716 default:
717 if (hor)
718 point.mode = HuginBase::ControlPoint::X;
719 else
720 point.mode = HuginBase::ControlPoint::Y;
721 break;
722 }
723 }
724 } else {
725 point.mode = HuginBase::ControlPoint::X_Y;
726 }
727
728 changeState(NO_POINT);
729
730 // create points
731 PanoCommand::GlobalCmdHist::getInstance().addCommand(
732 new PanoCommand::AddCtrlPointCmd(*m_pano, point)
733 );
734
735
736 // select new control Point
737 unsigned int lPoint = m_pano->getNrOfCtrlPoints() -1;
738 SelectGlobalPoint(lPoint);
739 changeState(NO_POINT);
740 MainFrame::Get()->SetStatusText(_("new control point added"));
741 m_leftChoice->CalcCPDistance(m_pano);
742 m_rightChoice->CalcCPDistance(m_pano);
743 }
744
745
getVerticalCPBias()746 const float CPEditorPanel::getVerticalCPBias()
747 {
748 HuginBase::PanoramaOptions opts = m_pano->getOptions();
749 HuginBase::PanoramaOptions::ProjectionFormat projFormat = opts.getProjection();
750 float bias;
751 switch (projFormat)
752 {
753 case HuginBase::PanoramaOptions::RECTILINEAR:
754 bias = 1.0;
755 break;
756 default:
757 bias = 2.0;
758 break;
759 }
760 return bias;
761 }
762
763
ClearSelection()764 void CPEditorPanel::ClearSelection()
765 {
766 if (m_selectedPoint == UINT_MAX) {
767 // no point selected, no need to select one.
768 return;
769 }
770 const long selectedPoint = m_cpList->FindItem(-1, m_selectedPoint);
771 if (selectedPoint != wxNOT_FOUND)
772 {
773 m_cpList->SetItemState(selectedPoint, 0, wxLIST_STATE_SELECTED);
774 };
775
776 m_selectedPoint=UINT_MAX;
777 changeState(NO_POINT);
778 m_leftImg->deselect();
779 m_rightImg->deselect();
780 UpdateDisplay(false);
781 }
782
SelectLocalPoint(unsigned int LVpointNr)783 void CPEditorPanel::SelectLocalPoint(unsigned int LVpointNr)
784 {
785 DEBUG_TRACE("selectLocalPoint(" << LVpointNr << ")");
786
787 if ( m_selectedPoint == LVpointNr) {
788 DEBUG_DEBUG("already selected");
789 m_leftImg->selectPoint(LVpointNr);
790 m_rightImg->selectPoint(LVpointNr);
791 return;
792 }
793 m_selectedPoint = LVpointNr;
794
795 const HuginBase::ControlPoint & p = currentPoints[LVpointNr].second;
796 m_x1Text->SetValue(wxString::Format(wxT("%.2f"),p.x1));
797 m_y1Text->SetValue(wxString::Format(wxT("%.2f"),p.y1));
798 m_x2Text->SetValue(wxString::Format(wxT("%.2f"),p.x2));
799 m_y2Text->SetValue(wxString::Format(wxT("%.2f"),p.y2));
800 m_cpModeChoice->SetSelection(p.mode);
801 m_leftImg->selectPoint(LVpointNr);
802 m_rightImg->selectPoint(LVpointNr);
803 const long selectedPoint = m_cpList->FindItem(-1, LVpointNr);
804 m_cpList->SetItemState(selectedPoint, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
805 m_cpList->EnsureVisible(selectedPoint);
806
807 EnablePointEdit(true);
808 }
809
SelectGlobalPoint(unsigned int globalNr)810 void CPEditorPanel::SelectGlobalPoint(unsigned int globalNr)
811 {
812 unsigned int localNr;
813 if (globalPNr2LocalPNr(localNr,globalNr)) {
814 DEBUG_DEBUG("CPEditor::setGlobalPoint(" << globalNr << ") found local point " << localNr);
815 SelectLocalPoint(localNr);
816 } else {
817 DEBUG_ERROR("CPEditor::setGlobalPoint: point " << globalNr << " not found in currentPoints");
818 }
819 }
820
globalPNr2LocalPNr(unsigned int & localNr,unsigned int globalNr) const821 bool CPEditorPanel::globalPNr2LocalPNr(unsigned int & localNr, unsigned int globalNr) const
822 {
823 HuginBase::CPointVector::const_iterator it = currentPoints.begin();
824
825 while(it != currentPoints.end() && (*it).first != globalNr) {
826 ++it;
827 }
828
829 if (it != currentPoints.end()) {
830 localNr = it - currentPoints.begin();
831 return true;
832 } else {
833 return false;
834 }
835 }
836
localPNr2GlobalPNr(unsigned int localNr) const837 unsigned int CPEditorPanel::localPNr2GlobalPNr(unsigned int localNr) const
838 {
839 assert(localNr < currentPoints.size());
840 return currentPoints[localNr].first;
841 }
842
843
estimateAndAddOtherPoint(const hugin_utils::FDiff2D & p,bool left,CPImageCtrl * thisImg,unsigned int thisImgNr,CPCreationState THIS_POINT,CPCreationState THIS_POINT_RETRY,CPImageCtrl * otherImg,unsigned int otherImgNr,CPCreationState OTHER_POINT,CPCreationState OTHER_POINT_RETRY)844 void CPEditorPanel::estimateAndAddOtherPoint(const hugin_utils::FDiff2D & p,
845 bool left,
846 CPImageCtrl * thisImg,
847 unsigned int thisImgNr,
848 CPCreationState THIS_POINT,
849 CPCreationState THIS_POINT_RETRY,
850 CPImageCtrl * otherImg,
851 unsigned int otherImgNr,
852 CPCreationState OTHER_POINT,
853 CPCreationState OTHER_POINT_RETRY)
854 {
855 hugin_utils::FDiff2D op;
856 op = EstimatePoint(hugin_utils::FDiff2D(p.x, p.y), left);
857 // check if point is in image.
858 const HuginBase::SrcPanoImage & pImg = m_pano->getImage(otherImgNr);
859 if (op.x < (int) pImg.getSize().width() && op.x >= 0
860 && op.y < (int) pImg.getSize().height() && op.y >= 0)
861 {
862 otherImg->setNewPoint(op);
863 // if fine-tune is checked, run a fine-tune session as well.
864 // hmm probably there should be another separate function for this..
865 if (m_fineTuneCB->IsChecked()) {
866 MainFrame::Get()->SetStatusText(_("searching similar points..."),0);
867 hugin_utils::FDiff2D newPoint = otherImg->getNewPoint();
868
869 long templWidth = wxConfigBase::Get()->Read(wxT("/Finetune/TemplateSize"), HUGIN_FT_TEMPLATE_SIZE);
870 const HuginBase::SrcPanoImage & img = m_pano->getImage(thisImgNr);
871 double sAreaPercent = wxConfigBase::Get()->Read(wxT("/Finetune/SearchAreaPercent"),HUGIN_FT_SEARCH_AREA_PERCENT);
872 int sWidth = std::min((int)(img.getWidth() * sAreaPercent / 100.0), 500);
873 vigra_ext::CorrelationResult corrPoint;
874 bool corrOk=false;
875 vigra::Diff2D roundp(p.toDiff2D());
876 try {
877 corrOk = PointFineTune(thisImgNr,
878 roundp,
879 templWidth,
880 otherImgNr,
881 newPoint,
882 sWidth,
883 corrPoint);
884 } catch (std::exception & e) {
885 wxMessageBox(wxString (e.what(), wxConvLocal), _("Error during Fine-tune"));
886 }
887 if (! corrOk) {
888 // just set point, PointFineTune already complained
889 if (corrPoint.corrPos.x >= 0 && corrPoint.corrPos.y >= 0 && corrPoint.maxpos.x > 0 && corrPoint.maxpos.y > 0)
890 {
891 otherImg->setScale(m_detailZoomFactor);
892 otherImg->setNewPoint(corrPoint.maxpos);
893 thisImg->setNewPoint(corrPoint.corrPos);
894 changeState(BOTH_POINTS_SELECTED);
895 };
896 } else {
897 // show point & zoom in if auto add is not set
898 if (!m_autoAddCB->IsChecked()) {
899 otherImg->setScale(m_detailZoomFactor);
900 otherImg->setNewPoint(corrPoint.maxpos);
901 thisImg->setNewPoint(corrPoint.corrPos);
902 changeState(BOTH_POINTS_SELECTED);
903 wxString s1;
904 s1.Printf(_("Point fine-tuned, angle: %.0f deg, correlation coefficient: %0.3f, curvature: %0.3f %0.3f"),
905 corrPoint.maxAngle, corrPoint.maxi, corrPoint.curv.x, corrPoint.curv.y );
906
907 wxString s2 = s1 + wxT(" -- ") + wxString(_("change points, or press right mouse button to add the pair"));
908 MainFrame::Get()->SetStatusText(s2,0);
909 } else {
910 // add point
911 otherImg->setNewPoint(corrPoint.maxpos);
912 thisImg->setNewPoint(corrPoint.corrPos);
913 changeState(BOTH_POINTS_SELECTED);
914 CreateNewPoint();
915 }
916 }
917 } else {
918 // no fine-tune, set 100% scale and set both points to selected
919 otherImg->setScale(m_detailZoomFactor);
920 otherImg->showPosition(op);
921 changeState(BOTH_POINTS_SELECTED);
922 }
923
924 } else {
925 // estimate was outside of image
926 // do nothing special
927 wxBell();
928 MainFrame::Get()->SetStatusText(_("Estimated point outside image"),0);
929 }
930 }
931
NewPointChange(hugin_utils::FDiff2D p,bool left)932 void CPEditorPanel::NewPointChange(hugin_utils::FDiff2D p, bool left)
933 {
934 DEBUG_TRACE("");
935
936 wxString corrMsg;
937
938 CPImageCtrl * thisImg = m_leftImg;
939 unsigned int thisImgNr = m_leftImageNr;
940 CPImageCtrl * otherImg = m_rightImg;
941 unsigned int otherImgNr = m_rightImageNr;
942 CPCreationState THIS_POINT = LEFT_POINT;
943 CPCreationState THIS_POINT_RETRY = LEFT_POINT_RETRY;
944 CPCreationState OTHER_POINT = RIGHT_POINT;
945 CPCreationState OTHER_POINT_RETRY = RIGHT_POINT_RETRY;
946
947 bool estimate = m_estimateCB->IsChecked();
948
949 if (!left) {
950 thisImg = m_rightImg;
951 thisImgNr = m_rightImageNr;
952 otherImg = m_leftImg;
953 otherImgNr = m_leftImageNr;
954 THIS_POINT = RIGHT_POINT;
955 THIS_POINT_RETRY = RIGHT_POINT_RETRY;
956 OTHER_POINT = LEFT_POINT;
957 OTHER_POINT_RETRY = LEFT_POINT_RETRY;
958 }
959
960
961 if (cpCreationState == NO_POINT) {
962 //case NO_POINT
963 changeState(THIS_POINT);
964 // zoom into our window
965 if (thisImg->getScale() < 1) {
966 thisImg->setScale(m_detailZoomFactor);
967 thisImg->showPosition(p);
968 } else {
969 // run auto-estimate procedure?
970 bool hasNormalCP = false;
971 for (HuginBase::CPointVector::const_iterator it = currentPoints.begin(); it != currentPoints.end() && !hasNormalCP; ++it)
972 {
973 hasNormalCP = (it->second.mode == HuginBase::ControlPoint::X_Y);
974 };
975 if (estimate && (thisImgNr != otherImgNr) && hasNormalCP) {
976 estimateAndAddOtherPoint(p, left,
977 thisImg, thisImgNr, THIS_POINT, THIS_POINT_RETRY,
978 otherImg, otherImgNr, OTHER_POINT, OTHER_POINT_RETRY);
979 };
980 }
981
982 } else if (cpCreationState == OTHER_POINT_RETRY) {
983 thisImg->showPosition(p);
984 } else if (cpCreationState == THIS_POINT) {
985 thisImg->showPosition(p);
986
987 bool hasNormalCP = false;
988 for (HuginBase::CPointVector::const_iterator it = currentPoints.begin(); it != currentPoints.end() && !hasNormalCP; ++it)
989 {
990 hasNormalCP = (it->second.mode == HuginBase::ControlPoint::X_Y);
991 };
992 if (estimate && (thisImgNr != otherImgNr) && hasNormalCP) {
993 estimateAndAddOtherPoint(p, left,
994 thisImg, thisImgNr, THIS_POINT, THIS_POINT_RETRY,
995 otherImg, otherImgNr, OTHER_POINT, OTHER_POINT_RETRY);
996 }
997 } else if (cpCreationState == OTHER_POINT || cpCreationState == THIS_POINT_RETRY) {
998 // the try for the second point.
999 if (cpCreationState == OTHER_POINT) {
1000 // other point already selected, finalize point.
1001
1002 // TODO: option to ignore the auto fine tune button when multiple images are selected.
1003 if (m_fineTuneCB->IsChecked() ) {
1004 vigra_ext::CorrelationResult corrRes;
1005
1006 hugin_utils::FDiff2D newPoint = otherImg->getNewPoint();
1007
1008 long templWidth = wxConfigBase::Get()->Read(wxT("/Finetune/TemplateSize"),HUGIN_FT_TEMPLATE_SIZE);
1009 const HuginBase::SrcPanoImage & img = m_pano->getImage(thisImgNr);
1010 double sAreaPercent = wxConfigBase::Get()->Read(wxT("/Finetune/SearchAreaPercent"),
1011 HUGIN_FT_SEARCH_AREA_PERCENT);
1012 int sWidth = std::min((int) (img.getWidth() * sAreaPercent / 100.0), 500);
1013 bool corrOk = false;
1014 // corr point
1015 vigra::Diff2D newPoint_round = newPoint.toDiff2D();
1016 try {
1017 corrOk = PointFineTune(otherImgNr,
1018 newPoint_round,
1019 templWidth,
1020 thisImgNr,
1021 p,
1022 sWidth,
1023 corrRes);
1024 } catch (std::exception & e) {
1025 wxMessageBox(wxString (e.what(), wxConvLocal), _("Error during Fine-tune"));
1026 }
1027
1028 if (! corrOk) {
1029 // low xcorr
1030 // zoom to 100 percent. & set second stage
1031 // to abandon finetune this time.
1032 if (corrRes.corrPos.x >= 0 && corrRes.corrPos.y >= 0 && corrRes.maxpos.x >= 0 && corrRes.maxpos.y >= 0)
1033 {
1034 thisImg->setScale(m_detailZoomFactor);
1035 thisImg->setNewPoint(corrRes.maxi > -1 ? corrRes.maxpos : p);
1036 thisImg->update();
1037 otherImg->setNewPoint(corrRes.maxi > -1 ? corrRes.corrPos : newPoint);
1038 changeState(BOTH_POINTS_SELECTED);
1039 };
1040 } else {
1041 // show point & zoom in if auto add is not set
1042 changeState(BOTH_POINTS_SELECTED);
1043 if (!m_autoAddCB->IsChecked()) {
1044 thisImg->setScale(m_detailZoomFactor);
1045 }
1046 thisImg->setNewPoint(corrRes.maxpos);
1047 otherImg->setNewPoint(corrRes.corrPos);
1048 wxString s1;
1049 s1.Printf(_("Point fine-tuned, angle: %.0f deg, correlation coefficient: %0.3f, curvature: %0.3f %0.3f"),
1050 corrRes.maxAngle, corrRes.maxi, corrRes.curv.x, corrRes.curv.y );
1051
1052 corrMsg = s1 + wxT(" -- ") + wxString(_("change points, or press right mouse button to add the pair"));
1053 MainFrame::Get()->SetStatusText(corrMsg,0);
1054
1055 }
1056 } else {
1057 // no finetune. but zoom into picture, when we where zoomed out
1058 if (thisImg->getScale() < 1) {
1059 // zoom to 100 percent. & set second stage
1060 // to abandon finetune this time.
1061 thisImg->setScale(m_detailZoomFactor);
1062 thisImg->clearNewPoint();
1063 thisImg->showPosition(p);
1064 //thisImg->setNewPoint(p.x, p.y);
1065 changeState(THIS_POINT_RETRY);
1066 return;
1067 } else {
1068 // point is already set. no need to move.
1069 // setNewPoint(p);
1070 changeState(BOTH_POINTS_SELECTED);
1071 }
1072 }
1073 } else {
1074 // selection retry
1075 // nothing special, no second stage fine-tune yet.
1076 }
1077
1078 // ok, we have determined the other point.. apply if auto add is on
1079 if (m_autoAddCB->IsChecked()) {
1080 CreateNewPoint();
1081 } else {
1082 // keep both point floating around, until they are
1083 // added with a right mouse click or the add button
1084 changeState(BOTH_POINTS_SELECTED);
1085 if (corrMsg != wxT("")) {
1086 MainFrame::Get()->SetStatusText(corrMsg,0);
1087 }
1088 }
1089
1090 } else if (cpCreationState == BOTH_POINTS_SELECTED) {
1091 // nothing to do.. maybe a special fine-tune with
1092 // a small search region
1093
1094 } else {
1095 // should never reach this, else state machine is broken.
1096 DEBUG_ASSERT(0);
1097 }
1098 }
1099
1100 // return a SrcPanoImage so that the given point is in the center
GetImageRotatedTo(const HuginBase::SrcPanoImage & img,const vigra::Diff2D & point,int testWidth,double & neededHFOV)1101 HuginBase::SrcPanoImage GetImageRotatedTo(const HuginBase::SrcPanoImage& img, const vigra::Diff2D& point, int testWidth, double& neededHFOV)
1102 {
1103 // copy only necessary information into temporary SrcPanoImage
1104 HuginBase::SrcPanoImage imgMod;
1105 imgMod.setSize(img.getSize());
1106 imgMod.setProjection(img.getProjection());
1107 imgMod.setHFOV(img.getHFOV());
1108 // calculate, where the interest point lies
1109 HuginBase::PanoramaOptions opt;
1110 opt.setProjection(HuginBase::PanoramaOptions::EQUIRECTANGULAR);
1111 opt.setHFOV(360);
1112 opt.setWidth(360);
1113 opt.setHeight(180);
1114
1115 HuginBase::PTools::Transform transform;
1116 transform.createInvTransform(imgMod, opt);
1117 double x1, y1;
1118 if (!transform.transformImgCoord(x1, y1, point.x, point.y))
1119 {
1120 neededHFOV = -1;
1121 return imgMod;
1122 }
1123 // equirect image coordinates -> equirectangular coordinates
1124 // transformImgCoord places the origin at the upper left corner and negate
1125 Matrix3 rotY;
1126 rotY.SetRotationPT(DEG_TO_RAD(180 - (x1 + 0.5)), 0, 0);
1127 Matrix3 rotP;
1128 rotP.SetRotationPT(0, DEG_TO_RAD((y1 + 0.5) - 90), 0);
1129 double y, p, r;
1130 // calculate the necessary rotation angles and remember
1131 Matrix3 rot = rotP * rotY;
1132 rot.GetRotationPT(y, p, r);
1133 imgMod.setYaw(RAD_TO_DEG(y));
1134 imgMod.setPitch(RAD_TO_DEG(p));
1135 imgMod.setRoll(RAD_TO_DEG(r));
1136
1137 // new we calculate the needed HFOV for template/search area width
1138 double x2, y2;
1139 // check a point left from our interest point
1140 if (transform.transformImgCoord(x2, y2, point.x - testWidth / 2.0, point.y))
1141 {
1142 if (x2 < x1)
1143 {
1144 neededHFOV = 2.0 * (x1 - x2);
1145 }
1146 else
1147 {
1148 // we crossed the 360 deg border
1149 neededHFOV = 2.0 * (360 - x2 + x1);
1150 };
1151 // limit maximum HFOV for remapping to stereographic projection done as next step
1152 if (neededHFOV > 90)
1153 {
1154 neededHFOV = 90;
1155 };
1156 return imgMod;
1157 };
1158 // this goes wrong, maybe the tested point is outside the image area of a fisheye image
1159 // now test the right point
1160 if (transform.transformImgCoord(x2, y2, point.x + testWidth / 2.0, point.y))
1161 {
1162 if (x1 < x2)
1163 {
1164 neededHFOV = 2.0 * (x2 - x1);
1165 }
1166 else
1167 {
1168 // we crossed the 360 deg border
1169 neededHFOV = 2.0 * (360 + x2 - x1);
1170 };
1171 // limit maximum HFOV for remapping to stereographic projection done as next step
1172 if (neededHFOV > 90)
1173 {
1174 neededHFOV = 90;
1175 };
1176 return imgMod;
1177 };
1178 // we can't calculate the needed HFOV, return -1
1179 neededHFOV = -1;
1180 return imgMod;
1181 };
1182
PointFineTuneProjectionAware(const HuginBase::SrcPanoImage & templ,const vigra::UInt8RGBImage & templImg,vigra::Diff2D templPos,int templSize,const HuginBase::SrcPanoImage & search,const vigra::UInt8RGBImage & searchImg,vigra::Diff2D searchPos,int sWidth)1183 vigra_ext::CorrelationResult PointFineTuneProjectionAware(const HuginBase::SrcPanoImage& templ, const vigra::UInt8RGBImage& templImg,
1184 vigra::Diff2D templPos, int templSize,
1185 const HuginBase::SrcPanoImage& search, const vigra::UInt8RGBImage& searchImg,
1186 vigra::Diff2D searchPos, int sWidth)
1187 {
1188 wxBusyCursor busy;
1189 // read settings
1190 wxConfigBase *cfg = wxConfigBase::Get();
1191 bool rotatingFinetune = cfg->Read(wxT("/Finetune/RotationSearch"), HUGIN_FT_ROTATION_SEARCH) == 1;
1192 double startAngle = HUGIN_FT_ROTATION_START_ANGLE;
1193 cfg->Read(wxT("/Finetune/RotationStartAngle"), &startAngle, HUGIN_FT_ROTATION_START_ANGLE);
1194 startAngle = DEG_TO_RAD(startAngle);
1195 double stopAngle = HUGIN_FT_ROTATION_STOP_ANGLE;
1196 cfg->Read(wxT("/Finetune/RotationStopAngle"), &stopAngle, HUGIN_FT_ROTATION_STOP_ANGLE);
1197 stopAngle = DEG_TO_RAD(stopAngle);
1198 int nSteps = cfg->Read(wxT("/Finetune/RotationSteps"), HUGIN_FT_ROTATION_STEPS);
1199 // if both images have the same projection and the angle does not differ to much use normal point fine-tune
1200 if (templ.getProjection() == search.getProjection()
1201 && templ.getHFOV() < 65 && search.getHFOV() < 65
1202 && fabs(templ.getHFOV() - search.getHFOV()) < 5)
1203 {
1204 vigra_ext::CorrelationResult res;
1205 if (rotatingFinetune)
1206 {
1207 res = vigra_ext::PointFineTuneRotSearch(templImg, templPos, templSize,
1208 searchImg, searchPos, sWidth, startAngle, stopAngle, nSteps);
1209 }
1210 else
1211 {
1212 res = vigra_ext::PointFineTune(templImg, vigra::RGBToGrayAccessor<vigra::RGBValue<vigra::UInt8> >(), templPos, templSize,
1213 searchImg, vigra::RGBToGrayAccessor<vigra::RGBValue<vigra::UInt8> >(), searchPos, sWidth);
1214 };
1215 res.corrPos = templPos;
1216 return res;
1217 };
1218 // images have different projections or the HFOV is different
1219 // so we reproject the image to stereographic projection and fine tune point there
1220 // rotate image so that interest point is in the center
1221 double templHFOV = 0;
1222 double searchHFOV = 0;
1223 HuginBase::SrcPanoImage templMod = GetImageRotatedTo(templ, templPos, templSize, templHFOV);
1224 HuginBase::SrcPanoImage searchMod = GetImageRotatedTo(search, searchPos, sWidth + templSize + 5, searchHFOV);
1225 vigra_ext::CorrelationResult res;
1226 res.maxpos = hugin_utils::FDiff2D(-1, -1);
1227 res.corrPos = hugin_utils::FDiff2D(-1, -1);
1228 if (templHFOV < 0 || searchHFOV < 0)
1229 {
1230 //something went wrong, e.g. image outside of projection circle for fisheye lenses
1231 return res;
1232 }
1233 // populate PanoramaOptions
1234 HuginBase::PanoramaOptions opts;
1235 opts.setProjection(HuginBase::PanoramaOptions::STEREOGRAPHIC);
1236 opts.setHFOV(std::max(templHFOV, searchHFOV));
1237 // calculate a sensible scale factor
1238 double scaleTempl = HuginBase::CalculateOptimalScale::calcOptimalPanoScale(templMod, opts);
1239 double scaleSearch = HuginBase::CalculateOptimalScale::calcOptimalPanoScale(searchMod, opts);
1240 opts.setWidth(std::max<unsigned int>(opts.getWidth()*std::min(scaleTempl, scaleSearch), 3 * templSize));
1241 opts.setHeight(opts.getWidth());
1242 // transform coordinates to transform system
1243 HuginBase::PTools::Transform transform;
1244 transform.createInvTransform(templMod, opts);
1245 double templX, templY, searchX, searchY;
1246 transform.transformImgCoord(templX, templY, templPos.x, templPos.y);
1247 transform.createInvTransform(searchMod, opts);
1248 transform.transformImgCoord(searchX, searchY, searchPos.x, searchPos.y);
1249 // now transform the images
1250 vigra_ext::PassThroughFunctor<vigra::UInt8> ptf;
1251 AppBase::DummyProgressDisplay dummy;
1252 transform.createTransform(searchMod, opts);
1253 vigra::UInt8RGBImage searchImgMod(opts.getSize());
1254 vigra::BImage alpha(opts.getSize());
1255 vigra_ext::transformImage(srcImageRange(searchImg), destImageRange(searchImgMod), destImage(alpha),
1256 vigra::Diff2D(0, 0), transform, ptf, false, vigra_ext::INTERP_CUBIC, &dummy);
1257 // now remap template, we need to remap a little bigger area to have enough information when the template
1258 // is rotated in PointFineTuneRotSearch
1259 vigra::Diff2D templPointInt(hugin_utils::roundi(templX), hugin_utils::roundi(templY));
1260 vigra::Rect2D rect(templPointInt.x - templSize - 2, templPointInt.y - templSize - 2,
1261 templPointInt.x + templSize + 2, templPointInt.y + templSize + 2);
1262 rect &= vigra::Rect2D(opts.getSize());
1263 transform.createTransform(templMod, opts);
1264 vigra::UInt8RGBImage templImgMod(opts.getSize());
1265 vigra_ext::transformImage(srcImageRange(templImg), destImageRange(templImgMod, rect), destImage(alpha),
1266 vigra::Diff2D(rect.left(), rect.top()), transform, ptf, false, vigra_ext::INTERP_CUBIC, &dummy);
1267 #if defined DEBUG_EXPORT_FINE_TUNE_REMAPPING
1268 {
1269 vigra::ImageExportInfo templExport("template_remapped.tif");
1270 vigra::exportImage(srcImageRange(templImgMod), templExport.setPixelType("UINT8"));
1271 vigra::ImageExportInfo searchExport("search_remapped.tif");
1272 vigra::exportImage(srcImageRange(searchImgMod), searchExport.setPixelType("UINT8"));
1273 }
1274 #endif
1275 // now we can finetune the point in stereographic projection
1276 // we are always using the rotate fine-tune algorithm, because for this case
1277 // often a rotation is involved
1278 res = vigra_ext::PointFineTuneRotSearch(templImgMod, templPointInt, templSize,
1279 searchImgMod, vigra::Diff2D(hugin_utils::roundi(searchX), hugin_utils::roundi(searchY)), sWidth, startAngle, stopAngle, nSteps);
1280 // we transfer also the new found template position back to the original image
1281 transform.createTransform(templMod, opts);
1282 transform.transformImgCoord(res.corrPos.x, res.corrPos.y, templPointInt.x + 0.00001, templPointInt.y + 0.00001);
1283 // we need to move the finetune point back to position in original image
1284 transform.createTransform(searchMod, opts);
1285 transform.transformImgCoord(res.maxpos.x, res.maxpos.y, res.maxpos.x, res.maxpos.y);
1286 return res;
1287 };
1288
PointFineTune(unsigned int tmplImgNr,const vigra::Diff2D & tmplPoint,int templSize,unsigned int subjImgNr,const hugin_utils::FDiff2D & o_subjPoint,int sWidth,vigra_ext::CorrelationResult & res)1289 bool CPEditorPanel::PointFineTune(unsigned int tmplImgNr,
1290 const vigra::Diff2D & tmplPoint,
1291 int templSize,
1292 unsigned int subjImgNr,
1293 const hugin_utils::FDiff2D & o_subjPoint,
1294 int sWidth,
1295 vigra_ext::CorrelationResult & res)
1296 {
1297 DEBUG_TRACE("tmpl img nr: " << tmplImgNr << " corr src: "
1298 << subjImgNr);
1299 if (tmplImgNr == subjImgNr)
1300 {
1301 // for line control points (assumed when both points are in the same image)
1302 // check the distance before running fine-tune
1303 // if both points are in the search area decrease search area
1304 const double distance = (tmplPoint - o_subjPoint.toDiff2D()).magnitude();
1305 if (distance < sWidth)
1306 {
1307 sWidth = 0.9 * distance;
1308 };
1309 if (sWidth < 1.1 * templSize)
1310 {
1311 MainFrame::Get()->SetStatusText(_("Distance between line control points too short, skipping fine-tune."));
1312 wxBell();
1313 return false;
1314 };
1315 };
1316 MainFrame::Get()->SetStatusText(_("searching similar points..."),0);
1317
1318 double corrThresh=HUGIN_FT_CORR_THRESHOLD;
1319 wxConfigBase::Get()->Read(wxT("/Finetune/CorrThreshold"),&corrThresh,
1320 HUGIN_FT_CORR_THRESHOLD);
1321
1322 double curvThresh = HUGIN_FT_CURV_THRESHOLD;
1323 // use default curvature threshold for line control points
1324 if (tmplImgNr != subjImgNr)
1325 {
1326 wxConfigBase::Get()->Read(wxT("/Finetune/CurvThreshold"), &curvThresh, HUGIN_FT_CURV_THRESHOLD);
1327 };
1328
1329 // fixme: just cutout suitable gray
1330 ImageCache::ImageCacheRGB8Ptr subjImg = ImageCache::getInstance().getImage(m_pano->getImage(subjImgNr).getFilename())->get8BitImage();
1331 ImageCache::ImageCacheRGB8Ptr tmplImg = ImageCache::getInstance().getImage(m_pano->getImage(tmplImgNr).getFilename())->get8BitImage();
1332
1333 res = PointFineTuneProjectionAware(m_pano->getImage(tmplImgNr), *(tmplImg), tmplPoint, templSize,
1334 m_pano->getImage(subjImgNr), *(subjImg), o_subjPoint.toDiff2D(), sWidth);
1335
1336 // invert curvature. we always assume its a maxima, the curvature there is negative
1337 // however, we allow the user to specify a positive threshold, so we need to
1338 // invert it
1339 res.curv.x = - res.curv.x;
1340 res.curv.y = - res.curv.y;
1341
1342 MainFrame::Get()->SetStatusText(wxString::Format(_("Point fine-tuned, angle: %.0f deg, correlation coefficient: %0.3f, curvature: %0.3f %0.3f"),
1343 res.maxAngle, res.maxi, res.curv.x, res.curv.y ),0);
1344 if (res.corrPos.x < 0 || res.corrPos.y < 0 || res.maxpos.x < 0 || res.maxpos.y < 0)
1345 {
1346 // invalid transformation in fine tune
1347 wxMessageDialog dlg(this,
1348 _("No similar point found."),
1349 #ifdef _WIN32
1350 _("Hugin"),
1351 #else
1352 wxT(""),
1353 #endif
1354 wxICON_ERROR | wxOK);
1355 dlg.SetExtendedMessage(_("An internal transformation went wrong.\nCheck that the point is inside the image."));
1356 dlg.ShowModal();
1357 return false;
1358 }
1359 if (res.maxi < corrThresh || res.curv.x < curvThresh || res.curv.y < curvThresh )
1360 {
1361 // Bad correlation result.
1362 wxMessageDialog dlg(this,
1363 _("No similar point found."),
1364 #ifdef _WIN32
1365 _("Hugin"),
1366 #else
1367 wxT(""),
1368 #endif
1369 wxICON_ERROR | wxOK);
1370 dlg.SetExtendedMessage(wxString::Format(_("Check the similarity visually.\nCorrelation coefficient (%.3f) is lower than the threshold set in the preferences."),
1371 res.maxi));
1372 dlg.ShowModal();
1373 return false;
1374 }
1375
1376 return true;
1377 }
1378
panoramaChanged(HuginBase::Panorama & pano)1379 void CPEditorPanel::panoramaChanged(HuginBase::Panorama &pano)
1380 {
1381 int nGui = m_cpModeChoice->GetCount();
1382 int nPano = pano.getNextCPTypeLineNumber()+1;
1383 DEBUG_DEBUG("mode choice: " << nGui << " entries, required: " << nPano);
1384
1385 if (nGui > nPano)
1386 {
1387 m_cpModeChoice->Freeze();
1388 // remove some items.
1389 for (int i = nGui-1; i > nPano-1; --i) {
1390 m_cpModeChoice->Delete(i);
1391 }
1392 if (nPano > 3) {
1393 m_cpModeChoice->SetString(nPano-1, _("Add new Line"));
1394 }
1395 m_cpModeChoice->Thaw();
1396 } else if (nGui < nPano) {
1397 m_cpModeChoice->Freeze();
1398 if (nGui > 3) {
1399 m_cpModeChoice->SetString(nGui-1, wxString::Format(_("Line %d"), nGui-1));
1400 }
1401 for (int i = nGui; i < nPano-1; i++) {
1402 m_cpModeChoice->Append(wxString::Format(_("Line %d"), i));
1403 }
1404 m_cpModeChoice->Append(_("Add new Line"));
1405 m_cpModeChoice->Thaw();
1406 }
1407 UpdateTransforms();
1408 // check if number of control points has changed, if so we need to update our variables
1409 if (pano.getNrOfCtrlPoints() != m_countCP)
1410 {
1411 m_countCP = pano.getNrOfCtrlPoints();
1412 UpdateDisplay(false);
1413 };
1414 // update header if necessary
1415 wxListItem item;
1416 if (m_cpList->GetColumn(6, item))
1417 {
1418 if (MainFrame::Get()->IsShowingCorrelation())
1419 {
1420 item.SetText(_("Correlation"));
1421 }
1422 else
1423 {
1424 item.SetText(_("Distance"));
1425 }
1426 m_cpList->SetColumn(6, item);
1427 };
1428
1429 DEBUG_TRACE("");
1430 }
1431
panoramaImagesChanged(HuginBase::Panorama & pano,const HuginBase::UIntSet & changed)1432 void CPEditorPanel::panoramaImagesChanged(HuginBase::Panorama &pano, const HuginBase::UIntSet &changed)
1433 {
1434 unsigned int nrImages = pano.getNrOfImages();
1435 unsigned int nrTabs = m_leftChoice->GetCount();
1436 DEBUG_TRACE("nrImages:" << nrImages << " nrTabs:" << nrTabs);
1437
1438 #ifdef __WXMSW__
1439 int oldLeftSelection = m_leftChoice->GetSelection();
1440 int oldRightSelection = m_rightChoice->GetSelection();
1441 #endif
1442
1443 if (nrImages == 0)
1444 {
1445 // disable controls
1446 m_cpModeChoice->Disable();
1447 m_addButton->Disable();
1448 m_delButton->Disable();
1449 m_autoAddCB->Disable();
1450 m_fineTuneCB->Disable();
1451 m_estimateCB->Disable();
1452 XRCCTRL(*this, "cp_editor_finetune_button", wxButton)->Disable();
1453 m_actionButton->Disable();
1454 XRCCTRL(*this, "cp_editor_choice_zoom", wxChoice)->Disable();
1455 XRCCTRL(*this, "cp_editor_previous_img", wxButton)->Disable();
1456 XRCCTRL(*this, "cp_editor_next_img", wxButton)->Disable();
1457 m_leftChoice->Disable();
1458 m_rightChoice->Disable();
1459 }
1460 else
1461 {
1462 // enable controls
1463 m_cpModeChoice->Enable();
1464 m_autoAddCB->Enable();
1465 m_fineTuneCB->Enable();
1466 m_estimateCB->Enable();
1467 XRCCTRL(*this, "cp_editor_finetune_button", wxButton)->Enable();
1468 m_actionButton->Enable();
1469 XRCCTRL(*this, "cp_editor_choice_zoom", wxChoice)->Enable();
1470 XRCCTRL(*this, "cp_editor_previous_img", wxButton)->Enable();
1471 XRCCTRL(*this, "cp_editor_next_img", wxButton)->Enable();
1472 m_leftChoice->Enable();
1473 m_rightChoice->Enable();
1474
1475 ImageCache::getInstance().softFlush();
1476
1477 for (unsigned int i=0; i < ((nrTabs < nrImages)? nrTabs: nrImages); i++) {
1478 wxFileName fileName(wxString (pano.getImage(i).getFilename().c_str(), HUGIN_CONV_FILENAME));
1479 m_leftChoice->SetString(i, wxString::Format(wxT("%d"), i) + wxT(". - ") + fileName.GetFullName());
1480 m_rightChoice->SetString(i, wxString::Format(wxT("%d"), i) + wxT(". - ") + fileName.GetFullName());
1481 }
1482 // wxChoice on windows looses the selection when setting new labels. Restore selection
1483 #ifdef __WXMSW__
1484 m_leftChoice->SetSelection(oldLeftSelection);
1485 m_rightChoice->SetSelection(oldRightSelection);
1486 #endif
1487 // add tab bar entries, if needed
1488 if (nrTabs < nrImages)
1489 {
1490 for (unsigned int i=nrTabs; i < nrImages; i++)
1491 {
1492 wxFileName fileName(wxString (pano.getImage(i).getFilename().c_str(), HUGIN_CONV_FILENAME));
1493 m_leftChoice->Append(wxString::Format(wxT("%d"), i) + wxT(". - ") + fileName.GetFullName());
1494 m_rightChoice->Append(wxString::Format(wxT("%d"), i) + wxT(". - ") + fileName.GetFullName());
1495 }
1496 }
1497 }
1498 if (nrTabs > nrImages)
1499 {
1500 // remove tab bar entries if needed
1501 // we have to disable listening to notebook selection events,
1502 // else we might update to a noexisting image
1503 m_listenToPageChange = false;
1504 for (int i=nrTabs-1; i >= (int)nrImages; i--) {
1505 m_leftChoice->Delete(i);
1506 m_rightChoice->Delete(i);
1507 }
1508 m_listenToPageChange = true;
1509 if (nrImages > 0) {
1510 // select some other image if we deleted the current image
1511 if (m_leftImageNr >= nrImages) {
1512 setLeftImage(nrImages -1);
1513 }
1514 if (m_rightImageNr >= nrImages) {
1515 setRightImage(nrImages -1);
1516 }
1517 } else {
1518 DEBUG_DEBUG("setting no images");
1519 m_leftImageNr = UINT_MAX;
1520 m_leftFile = "";
1521 m_rightImageNr = UINT_MAX;
1522 m_rightFile = "";
1523 // no image anymore..
1524 m_leftImg->setImage(m_leftFile, CPImageCtrl::ROT0);
1525 m_rightImg->setImage(m_rightFile, CPImageCtrl::ROT0);
1526 }
1527 }
1528
1529 // update changed images
1530 bool update(false);
1531 for(HuginBase::UIntSet::const_iterator it = changed.begin(); it != changed.end(); ++it) {
1532 unsigned int imgNr = *it;
1533 // we only need to update the view if the currently
1534 // selected images were changed.
1535 // changing the images via the tabbar will always
1536 // take the current state directly from the pano
1537 // object
1538 DEBUG_DEBUG("image changed "<< imgNr);
1539 double yaw = const_map_get(m_pano->getImageVariables(imgNr), "y").getValue();
1540 double pitch = const_map_get(m_pano->getImageVariables(imgNr), "p").getValue();
1541 double roll = const_map_get(m_pano->getImageVariables(imgNr), "r").getValue();
1542 CPImageCtrl::ImageRotation rot = GetRot(yaw, pitch, roll);
1543 if (m_leftImageNr == imgNr) {
1544 DEBUG_DEBUG("left image dirty "<< imgNr);
1545 if (m_leftFile != pano.getImage(imgNr).getFilename()
1546 || m_leftRot != rot )
1547 {
1548 m_leftRot = rot;
1549 m_leftFile = pano.getImage(imgNr).getFilename();
1550 m_leftImg->setImage(m_leftFile, m_leftRot);
1551 }
1552 update=true;
1553 }
1554
1555 if (m_rightImageNr == imgNr) {
1556 DEBUG_DEBUG("right image dirty "<< imgNr);
1557 if (m_rightFile != pano.getImage(imgNr).getFilename()
1558 || m_rightRot != rot )
1559 {
1560 m_rightRot = rot;
1561 m_rightFile = pano.getImage(imgNr).getFilename();
1562 m_rightImg->setImage(m_rightFile, m_rightRot);
1563 }
1564 update=true;
1565 }
1566 }
1567 // check if number of control points has changed, if so we need to update our variables
1568 if (pano.getNrOfCtrlPoints() != m_countCP)
1569 {
1570 m_countCP = pano.getNrOfCtrlPoints();
1571 update = true;
1572 };
1573
1574 // if there is no selection, select the first one.
1575 if (m_rightImageNr == UINT_MAX && nrImages > 0) {
1576 setRightImage(0);
1577 }
1578 if (m_leftImageNr == UINT_MAX && nrImages > 0) {
1579 setLeftImage(0);
1580 }
1581
1582 if (update || nrImages == 0) {
1583 UpdateDisplay(false);
1584 }
1585 m_leftChoice->CalcCPDistance(m_pano);
1586 m_rightChoice->CalcCPDistance(m_pano);
1587 }
1588
UpdateDisplay(bool newPair)1589 void CPEditorPanel::UpdateDisplay(bool newPair)
1590 {
1591 DEBUG_DEBUG("")
1592 int fI = m_leftChoice->GetSelection();
1593 int sI = m_rightChoice->GetSelection();
1594
1595 // valid selection and already set left image
1596 if (fI >= 0 && m_leftImageNr != UINT_MAX)
1597 {
1598 // set image number to selection
1599 m_leftImageNr = (unsigned int) fI;
1600 }
1601 // valid selection and already set right image
1602 if (sI >= 0 && m_rightImageNr != UINT_MAX)
1603 {
1604 // set image number to selection
1605 m_rightImageNr = (unsigned int) sI;
1606 }
1607 // reset selection
1608 m_x1Text->Clear();
1609 m_y1Text->Clear();
1610 m_x2Text->Clear();
1611 m_y2Text->Clear();
1612 if (m_cpModeChoice->GetSelection() < 3) {
1613 m_cpModeChoice->SetSelection(0);
1614 }
1615
1616 m_leftImg->setSameImage(m_leftImageNr==m_rightImageNr);
1617 m_rightImg->setSameImage(m_leftImageNr==m_rightImageNr);
1618
1619 // update control points
1620 const HuginBase::CPVector & controlPoints = m_pano->getCtrlPoints();
1621 const size_t oldCurrentPointSize = currentPoints.size();
1622 currentPoints.clear();
1623 mirroredPoints.clear();
1624
1625 // create a list of all control points
1626 HuginBase::CPVector::size_type i = 0;
1627 m_leftImg->clearCtrlPointList();
1628 m_rightImg->clearCtrlPointList();
1629 for (HuginBase::CPVector::size_type index = 0; index < controlPoints.size(); ++index)
1630 {
1631 HuginBase::ControlPoint point(controlPoints[index]);
1632 if ((point.image1Nr == m_leftImageNr) && (point.image2Nr == m_rightImageNr)){
1633 m_leftImg->setCtrlPoint(point, false);
1634 m_rightImg->setCtrlPoint(point, true);
1635 currentPoints.push_back(std::make_pair(index, point));
1636 i++;
1637 } else if ((point.image2Nr == m_leftImageNr) && (point.image1Nr == m_rightImageNr)){
1638 m_leftImg->setCtrlPoint(point, true);
1639 m_rightImg->setCtrlPoint(point, false);
1640 point.mirror();
1641 mirroredPoints.insert(i);
1642 currentPoints.push_back(std::make_pair(index, point));
1643 i++;
1644 }
1645 }
1646 m_leftImg->update();
1647 m_rightImg->update();
1648
1649 // put these control points into our listview.
1650 unsigned int selectedItem = UINT_MAX;
1651 long selectedCP = -1;
1652 for (int i = 0; i < m_cpList->GetItemCount(); i++)
1653 {
1654 if ( m_cpList->GetItemState( i, wxLIST_STATE_SELECTED))
1655 {
1656 selectedItem = (unsigned int) i; // remembers the old selection, only one can be selected
1657 selectedCP = m_cpList->GetItemData(i);
1658 break;
1659 }
1660 }
1661 m_cpList->Freeze();
1662 m_cpList->DeleteAllItems();
1663
1664 for (unsigned int i=0; i < currentPoints.size(); ++i) {
1665 const HuginBase::ControlPoint & p(currentPoints[i].second);
1666 DEBUG_DEBUG("inserting LVItem " << i);
1667 long item = m_cpList->InsertItem(i, wxString::Format(wxT("%d"), i), -1);
1668 // store index in list data field
1669 m_cpList->SetItemData(item, i);
1670 m_cpList->SetItem(i,1,wxString::Format(wxT("%.2f"),p.x1));
1671 m_cpList->SetItem(i,2,wxString::Format(wxT("%.2f"),p.y1));
1672 m_cpList->SetItem(i,3,wxString::Format(wxT("%.2f"),p.x2));
1673 m_cpList->SetItem(i,4,wxString::Format(wxT("%.2f"),p.y2));
1674 wxString mode;
1675 switch (p.mode) {
1676 case HuginBase::ControlPoint::X_Y:
1677 mode = _("normal");
1678 break;
1679 case HuginBase::ControlPoint::X:
1680 mode = _("vert. Line");
1681 break;
1682 case HuginBase::ControlPoint::Y:
1683 mode = _("horiz. Line");
1684 break;
1685 default:
1686 mode = wxString::Format(_("Line %d"), p.mode);
1687 break;
1688 }
1689 m_cpList->SetItem(i,5,mode);
1690 m_cpList->SetItem(i,6,wxString::Format(wxT("%.2f"),p.error));
1691 }
1692 SortList();
1693
1694 if (selectedItem <= (unsigned int)m_cpList->GetItemCount() && !newPair && m_cpList->GetItemCount() > 0)
1695 {
1696 // sets an old selection again, only if the images have not changed
1697 if (oldCurrentPointSize == currentPoints.size())
1698 {
1699 // number of control points is not changed, so select the same cp
1700 // as before
1701 long selected = m_cpList->FindItem(-1, selectedCP);
1702 m_cpList->SetItemState(selected, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1703 m_cpList->EnsureVisible(selected);
1704 m_selectedPoint = selectedCP;
1705 }
1706 else
1707 {
1708 // number of cp has change, so keep the next item seleteced
1709 if (selectedItem == (unsigned int)m_cpList->GetItemCount())
1710 {
1711 // previously last item was selected, move selected to now last item
1712 selectedItem = m_cpList->GetItemCount() - 1;
1713 };
1714 m_cpList->SetItemState(selectedItem, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED);
1715 m_cpList->EnsureVisible(selectedItem);
1716 m_selectedPoint = m_cpList->GetItemData(selectedItem);
1717 };
1718 EnablePointEdit(true);
1719
1720 const HuginBase::ControlPoint & p = currentPoints[m_selectedPoint].second;
1721 m_x1Text->SetValue(wxString::Format(wxT("%.2f"),p.x1));
1722 m_y1Text->SetValue(wxString::Format(wxT("%.2f"),p.y1));
1723 m_x2Text->SetValue(wxString::Format(wxT("%.2f"),p.x2));
1724 m_y2Text->SetValue(wxString::Format(wxT("%.2f"),p.y2));
1725 m_cpModeChoice->SetSelection(p.mode);
1726 m_leftImg->selectPoint(m_selectedPoint);
1727 m_rightImg->selectPoint(m_selectedPoint);
1728
1729 } else {
1730 m_selectedPoint = UINT_MAX;
1731 EnablePointEdit(false);
1732 }
1733
1734 for ( int j=0; j < m_cpList->GetColumnCount() ; j++ )
1735 {
1736 //get saved width
1737 // -1 is auto
1738 int width = wxConfigBase::Get()->Read(wxString::Format( wxT("/CPEditorPanel/ColumnWidth%d"), j ), -1);
1739 if(width != -1)
1740 m_cpList->SetColumnWidth(j, width);
1741 }
1742
1743 m_cpList->Thaw();
1744 }
1745
EnablePointEdit(bool state)1746 void CPEditorPanel::EnablePointEdit(bool state)
1747 {
1748 m_delButton->Enable(state);
1749 XRCCTRL(*this, "cp_editor_finetune_button", wxButton)->Enable(state);
1750 m_x1Text->Enable(state);
1751 m_y1Text->Enable(state);
1752 m_x2Text->Enable(state);
1753 m_y2Text->Enable(state);
1754 m_cpModeChoice->Enable(state);
1755 }
1756
OnTextPointChange(wxCommandEvent & e)1757 void CPEditorPanel::OnTextPointChange(wxCommandEvent &e)
1758 {
1759 DEBUG_TRACE("");
1760 // find selected point
1761 long item = -1;
1762 item = m_cpList->GetNextItem(item,
1763 wxLIST_NEXT_ALL,
1764 wxLIST_STATE_SELECTED);
1765 // no selected item.
1766 if (item == -1) {
1767 return;
1768 }
1769 const unsigned int nr = m_cpList->GetItemData(item);
1770 assert(nr < currentPoints.size());
1771 HuginBase::ControlPoint cp = currentPoints[nr].second;
1772
1773 // update point state
1774 double oldValue=cp.x1;
1775 bool valid_input=hugin_utils::str2double(m_x1Text->GetValue(), cp.x1);
1776 if(valid_input)
1777 valid_input=(cp.x1>=0) && (cp.x1<=m_pano->getSrcImage(cp.image1Nr).getWidth());
1778 if (!valid_input) {
1779 m_x1Text->Clear();
1780 *m_x1Text << oldValue;
1781 return;
1782 }
1783 oldValue=cp.y1;
1784 valid_input = hugin_utils::str2double(m_y1Text->GetValue(), cp.y1);
1785 if(valid_input)
1786 valid_input=(cp.y1>=0) && (cp.y1<=m_pano->getSrcImage(cp.image1Nr).getHeight());
1787 if (!valid_input) {
1788 m_y1Text->Clear();
1789 *m_y1Text << oldValue;
1790 return;
1791 }
1792 oldValue=cp.x2;
1793 valid_input = hugin_utils::str2double(m_x2Text->GetValue(), cp.x2);
1794 if(valid_input)
1795 valid_input=(cp.x2>=0) && (cp.x2<=m_pano->getSrcImage(cp.image2Nr).getWidth());
1796 if (!valid_input) {
1797 m_x2Text->Clear();
1798 *m_x2Text << oldValue;
1799 return;
1800 }
1801 oldValue=cp.y2;
1802 valid_input = hugin_utils::str2double(m_y2Text->GetValue(), cp.y2);
1803 if(valid_input)
1804 valid_input=(cp.y2>=0) && (cp.y2<=m_pano->getSrcImage(cp.image1Nr).getHeight());
1805 if (!valid_input) {
1806 m_y2Text->Clear();
1807 *m_y2Text << oldValue;
1808 return;
1809 }
1810
1811 cp.mode = m_cpModeChoice->GetSelection();
1812 // if point was mirrored, reverse before setting it.
1813 if (set_contains(mirroredPoints, nr)) {
1814 cp.mirror();
1815 }
1816 PanoCommand::GlobalCmdHist::getInstance().addCommand(
1817 new PanoCommand::ChangeCtrlPointCmd(*m_pano, currentPoints[nr].first, cp)
1818 );
1819
1820 }
1821
OnLeftChoiceChange(wxCommandEvent & e)1822 void CPEditorPanel::OnLeftChoiceChange(wxCommandEvent & e)
1823 {
1824 DEBUG_TRACE("OnLeftChoiceChange() to " << e.GetSelection());
1825 if (m_listenToPageChange && e.GetSelection() >= 0) {
1826 setLeftImage((unsigned int) e.GetSelection());
1827 }
1828 }
1829
OnRightChoiceChange(wxCommandEvent & e)1830 void CPEditorPanel::OnRightChoiceChange(wxCommandEvent & e)
1831 {
1832 DEBUG_TRACE("OnRightChoiceChange() to " << e.GetSelection());
1833 if (m_listenToPageChange && e.GetSelection() >= 0) {
1834 setRightImage((unsigned int) e.GetSelection());
1835 }
1836 }
OnCPListSelect(wxListEvent & ev)1837 void CPEditorPanel::OnCPListSelect(wxListEvent & ev)
1838 {
1839 int t = ev.GetIndex();
1840 DEBUG_TRACE("selected: " << t);
1841 if (t >=0) {
1842 SelectLocalPoint(m_cpList->GetItemData(t));
1843 changeState(NO_POINT);
1844 }
1845 EnablePointEdit(true);
1846 }
1847
OnCPListDeselect(wxListEvent & ev)1848 void CPEditorPanel::OnCPListDeselect(wxListEvent & ev)
1849 {
1850 // disable controls
1851 // when doing changes to this procedure do also check
1852 // interaction with control point table
1853 // e.g. m_selectedPoint=UINT_MAX will result in a endless loop and crash
1854 changeState(NO_POINT);
1855 EnablePointEdit(false);
1856 m_leftImg->deselect();
1857 m_rightImg->deselect();
1858 }
1859
OnZoom(wxCommandEvent & e)1860 void CPEditorPanel::OnZoom(wxCommandEvent & e)
1861 {
1862 int leftX = 0;
1863 int leftY = 0;
1864 int rightX = 0;
1865 int rightY = 0;
1866 const wxSize leftSize = m_leftImg->GetClientSize();
1867 const wxSize rightSize = m_rightImg->GetClientSize();
1868 if (m_leftImg->getScale() > 0)
1869 {
1870 // remember old scroll position
1871 leftX = (m_leftImg->GetScrollPos(wxHORIZONTAL) + leftSize.GetWidth() / 2 )/ m_leftImg->getScale();
1872 leftY = (m_leftImg->GetScrollPos(wxVERTICAL) + leftSize.GetHeight() / 2) / m_leftImg->getScale();
1873 rightX = (m_rightImg->GetScrollPos(wxHORIZONTAL) + rightSize.GetWidth() / 2) / m_rightImg->getScale();
1874 rightY = (m_rightImg->GetScrollPos(wxVERTICAL) + rightSize.GetHeight() / 2) / m_rightImg->getScale();
1875 };
1876 double factor;
1877 switch (e.GetSelection()) {
1878 case 0:
1879 factor = 1;
1880 m_detailZoomFactor = factor;
1881 break;
1882 case 1:
1883 // fit to window
1884 factor = 0;
1885 break;
1886 case 2:
1887 factor = 2;
1888 m_detailZoomFactor = factor;
1889 break;
1890 case 3:
1891 factor = 1.5;
1892 m_detailZoomFactor = factor;
1893 break;
1894 case 4:
1895 factor = 0.75;
1896 break;
1897 case 5:
1898 factor = 0.5;
1899 break;
1900 case 6:
1901 factor = 0.25;
1902 break;
1903 default:
1904 DEBUG_ERROR("unknown scale factor");
1905 factor = 1;
1906 }
1907 m_leftImg->setScale(factor);
1908 m_rightImg->setScale(factor);
1909 // if a point is selected, keep it in view
1910 if (m_selectedPoint < UINT_MAX) {
1911 SelectLocalPoint(m_selectedPoint);
1912 }
1913 else
1914 {
1915 if (factor > 0)
1916 {
1917 // scroll to keep old position in view
1918 m_leftImg->Scroll(leftX*factor - leftSize.GetWidth() / 2, leftY*factor - leftSize.GetHeight() / 2);
1919 m_rightImg->Scroll(rightX*factor - rightSize.GetWidth() / 2, rightY*factor - rightSize.GetHeight() / 2);
1920 };
1921 }
1922 }
1923
OnKey(wxKeyEvent & e)1924 void CPEditorPanel::OnKey(wxKeyEvent & e)
1925 {
1926 DEBUG_DEBUG("key " << e.GetKeyCode()
1927 << " origin: id:" << e.GetId() << " obj: "
1928 << e.GetEventObject());
1929
1930 if (e.m_keyCode == WXK_DELETE){
1931 DEBUG_DEBUG("Delete pressed");
1932 // remove working points..
1933 if (cpCreationState != NO_POINT) {
1934 changeState(NO_POINT);
1935 } else {
1936 // remove selected point
1937 // find selected point
1938 long item = -1;
1939 item = m_cpList->GetNextItem(item,
1940 wxLIST_NEXT_ALL,
1941 wxLIST_STATE_SELECTED);
1942 // no selected item.
1943 if (item == -1) {
1944 wxBell();
1945 return;
1946 }
1947 unsigned int pNr = localPNr2GlobalPNr(m_cpList->GetItemData(item));
1948 DEBUG_DEBUG("about to delete point " << pNr);
1949 PanoCommand::GlobalCmdHist::getInstance().addCommand(
1950 new PanoCommand::RemoveCtrlPointCmd(*m_pano,pNr)
1951 );
1952 }
1953 } else if (e.m_keyCode == '0') {
1954 wxCommandEvent dummy;
1955 dummy.SetInt(1);
1956 OnZoom(dummy);
1957 XRCCTRL(*this,"cp_editor_choice_zoom",wxChoice)->SetSelection(1);
1958 } else if (e.m_keyCode == '1') {
1959 wxCommandEvent dummy;
1960 dummy.SetInt(0);
1961 OnZoom(dummy);
1962 XRCCTRL(*this,"cp_editor_choice_zoom",wxChoice)->SetSelection(0);
1963 } else if (e.m_keyCode == '2') {
1964 wxCommandEvent dummy;
1965 dummy.SetInt(2);
1966 OnZoom(dummy);
1967 XRCCTRL(*this,"cp_editor_choice_zoom",wxChoice)->SetSelection(2);
1968 } else if (e.CmdDown() && e.GetKeyCode() == WXK_LEFT) {
1969 // move to previous
1970 wxCommandEvent dummy;
1971 OnPrevImg(dummy);
1972 } else if (e.CmdDown() && e.GetKeyCode() == WXK_RIGHT) {
1973 // move to next
1974 wxCommandEvent dummy;
1975 OnNextImg(dummy);
1976 } else if (e.GetKeyCode() == 'f') {
1977 bool left = e.GetEventObject() == m_leftImg;
1978 if (cpCreationState == NO_POINT) {
1979 FineTuneSelectedPoint(left);
1980 } else if (cpCreationState == BOTH_POINTS_SELECTED) {
1981 FineTuneNewPoint(left);
1982 }
1983 } else if (e.GetKeyCode() == 'g') {
1984 // generate keypoints
1985 long th = wxGetNumberFromUser(_("Create control points.\nTo create less points,\nenter a higher number."), _("Corner Detection threshold"), _("Create control points"), 400, 0, 32000);
1986 if (th == -1) {
1987 return;
1988 }
1989 long scale = wxGetNumberFromUser(_("Create control points"), _("Corner Detection scale"), _("Create control points"), 2);
1990 if (scale == -1) {
1991 return;
1992 }
1993
1994 try {
1995 wxBusyCursor busy;
1996 DEBUG_DEBUG("corner threshold: " << th << " scale: " << scale);
1997 PanoCommand::GlobalCmdHist::getInstance().addCommand(
1998 new PanoCommand::wxAddCtrlPointGridCmd(*m_pano, m_leftImageNr, m_rightImageNr, scale, th)
1999 );
2000 } catch (std::exception & e) {
2001 wxLogError(_("Error during control point creation:\n") + wxString(e.what(), wxConvLocal));
2002 }
2003 } else {
2004 e.Skip();
2005 }
2006 }
2007
OnAddButton(wxCommandEvent & e)2008 void CPEditorPanel::OnAddButton(wxCommandEvent & e)
2009 {
2010 // check if the point can be created..
2011 if (cpCreationState == BOTH_POINTS_SELECTED) {
2012 CreateNewPoint();
2013 }
2014 }
2015
OnDeleteButton(wxCommandEvent & e)2016 void CPEditorPanel::OnDeleteButton(wxCommandEvent & e)
2017 {
2018 DEBUG_TRACE("");
2019 // check if a point has been selected, but not added.
2020 if (cpCreationState != NO_POINT) {
2021 changeState(NO_POINT);
2022 } else {
2023 // find selected point
2024 long item = -1;
2025 item = m_cpList->GetNextItem(item,
2026 wxLIST_NEXT_ALL,
2027 wxLIST_STATE_SELECTED);
2028 // no selected item.
2029 if (item == -1) {
2030 wxBell();
2031 return;
2032 }
2033 // get the global point number
2034 unsigned int pNr = localPNr2GlobalPNr(m_cpList->GetItemData(item));
2035
2036 PanoCommand::GlobalCmdHist::getInstance().addCommand(
2037 new PanoCommand::RemoveCtrlPointCmd(*m_pano,pNr )
2038 );
2039 m_leftChoice->CalcCPDistance(m_pano);
2040 m_rightChoice->CalcCPDistance(m_pano);
2041 }
2042 }
2043
2044 // show a global control point
ShowControlPoint(unsigned int cpNr)2045 void CPEditorPanel::ShowControlPoint(unsigned int cpNr)
2046 {
2047 const HuginBase::ControlPoint & p = m_pano->getCtrlPoint(cpNr);
2048 setLeftImage(p.image1Nr);
2049 setRightImage(p.image2Nr);
2050 // FIXME reset display state
2051 changeState(NO_POINT);
2052
2053 SelectGlobalPoint(cpNr);
2054 }
2055
changeState(CPCreationState newState)2056 void CPEditorPanel::changeState(CPCreationState newState)
2057 {
2058 DEBUG_TRACE(cpCreationState << " --> " << newState);
2059 // handle global state changes.
2060 bool fineTune = m_fineTuneCB->IsChecked() && (m_leftImageNr != m_rightImageNr);
2061 switch(newState) {
2062 case NO_POINT:
2063 // disable all drawing search boxes.
2064 m_leftImg->showSearchArea(false);
2065 m_rightImg->showSearchArea(false);
2066 // but draw template size, if fine-tune enabled
2067 m_leftImg->showTemplateArea(fineTune);
2068 m_rightImg->showTemplateArea(fineTune);
2069 m_addButton->Enable(false);
2070 if (m_selectedPoint < UINT_MAX) {
2071 m_delButton->Enable(true);
2072 } else {
2073 m_delButton->Enable(false);
2074 }
2075 if (cpCreationState != NO_POINT) {
2076 // reset zoom to previous setting
2077 wxCommandEvent tmpEvt;
2078 tmpEvt.SetInt(XRCCTRL(*this,"cp_editor_choice_zoom",wxChoice)->GetSelection());
2079 OnZoom(tmpEvt);
2080 m_leftImg->clearNewPoint();
2081 m_rightImg->clearNewPoint();
2082 }
2083 break;
2084 case LEFT_POINT:
2085 // disable search area on left window
2086 m_leftImg->showSearchArea(false);
2087 // show search area on right window
2088 m_rightImg->showSearchArea(fineTune);
2089
2090 // show template area
2091 m_leftImg->showTemplateArea(fineTune);
2092 m_rightImg->showTemplateArea(false);
2093
2094 // unselect point
2095 ClearSelection();
2096 m_addButton->Enable(false);
2097 m_delButton->Enable(false);
2098 MainFrame::Get()->SetStatusText(_("Select point in right image"),0);
2099 break;
2100 case RIGHT_POINT:
2101 m_leftImg->showSearchArea(fineTune);
2102 m_rightImg->showSearchArea(false);
2103
2104 m_leftImg->showTemplateArea(false);
2105 m_rightImg->showTemplateArea(fineTune);
2106
2107 ClearSelection();
2108 m_addButton->Enable(false);
2109 m_delButton->Enable(false);
2110 MainFrame::Get()->SetStatusText(_("Select point in left image"),0);
2111 break;
2112 case LEFT_POINT_RETRY:
2113 case RIGHT_POINT_RETRY:
2114 m_leftImg->showSearchArea(false);
2115 m_rightImg->showSearchArea(false);
2116 // but draw template size, if fine-tune enabled
2117 m_leftImg->showTemplateArea(false);
2118 m_rightImg->showTemplateArea(false);
2119 m_addButton->Enable(false);
2120 m_delButton->Enable(false);
2121 break;
2122 case BOTH_POINTS_SELECTED:
2123 m_leftImg->showTemplateArea(false);
2124 m_rightImg->showTemplateArea(false);
2125 m_leftImg->showSearchArea(false);
2126 m_rightImg->showSearchArea(false);
2127 m_addButton->Enable(true);
2128 m_delButton->Enable(false);
2129 }
2130 // apply the change
2131 cpCreationState = newState;
2132 }
2133
OnPrevImg(wxCommandEvent & e)2134 void CPEditorPanel::OnPrevImg(wxCommandEvent & e)
2135 {
2136 if (m_pano->getNrOfImages() < 2) return;
2137 int nImgs = m_pano->getNrOfImages();
2138 int left = m_leftImageNr -1;
2139 int right = m_rightImageNr -1;
2140 if (left < 0) {
2141 left += nImgs;
2142 } else if (left >= nImgs) {
2143 left -= nImgs;
2144 }
2145
2146 if (right < 0) {
2147 right += nImgs;
2148 } else if (right >= nImgs) {
2149 right -= nImgs;
2150 }
2151 setLeftImage((unsigned int) left);
2152 setRightImage((unsigned int) right);
2153 }
2154
OnNextImg(wxCommandEvent & e)2155 void CPEditorPanel::OnNextImg(wxCommandEvent & e)
2156 {
2157 if (m_pano->getNrOfImages() < 2) return;
2158 int nImgs = m_pano->getNrOfImages();
2159 int left = m_leftImageNr + 1;
2160 int right = m_rightImageNr + 1;
2161 if (left < 0) {
2162 left += nImgs;
2163 } else if (left >= nImgs) {
2164 left -= nImgs;
2165 }
2166
2167 if (right < 0) {
2168 right += nImgs;
2169 } else if (right >= nImgs) {
2170 right -= nImgs;
2171 }
2172 setLeftImage((unsigned int) left);
2173 setRightImage((unsigned int) right);
2174 }
2175
OnFineTuneButton(wxCommandEvent & e)2176 void CPEditorPanel::OnFineTuneButton(wxCommandEvent & e)
2177 {
2178 if (cpCreationState == NO_POINT) {
2179 FineTuneSelectedPoint(false);
2180 } else if (cpCreationState == BOTH_POINTS_SELECTED) {
2181 FineTuneNewPoint(false);
2182 }
2183 }
2184
OnActionContextMenu(wxContextMenuEvent & e)2185 void CPEditorPanel::OnActionContextMenu(wxContextMenuEvent& e)
2186 {
2187 m_cpActionContextMenu->SetLabel(XRCID("cp_menu_create_cp"), wxString::Format(_("Create cp (Current setting: %s)"), MainFrame::Get()->GetSelectedCPGenerator().c_str()));
2188 PopupMenu(m_cpActionContextMenu);
2189 };
2190
OnActionButton(wxCommandEvent & e)2191 void CPEditorPanel::OnActionButton(wxCommandEvent& e)
2192 {
2193 switch (m_cpActionButtonMode)
2194 {
2195 case CPTAB_ACTION_CREATE_CP:
2196 OnCreateCPButton(e);
2197 break;
2198 case CPTAB_ACTION_CLEAN_CP:
2199 OnCleanCPButton(e);
2200 break;
2201 case CPTAB_ACTION_CELESTE:
2202 default:
2203 OnCelesteButton(e);
2204 break;
2205 };
2206 };
2207
OnCreateCPButton(wxCommandEvent & e)2208 void CPEditorPanel::OnCreateCPButton(wxCommandEvent& e)
2209 {
2210 if (m_leftImageNr == m_rightImageNr)
2211 {
2212 // when the same image is selected left and right we are running linefind
2213 // with default parameters
2214 CPDetectorSetting linefindSetting;
2215 #ifdef __WXMSW__
2216 linefindSetting.SetProg(wxT("linefind.exe"));
2217 #else
2218 linefindSetting.SetProg(wxT("linefind"));
2219 #endif
2220 linefindSetting.SetArgs(wxT("-o %o %s"));
2221 HuginBase::UIntSet imgs;
2222 imgs.insert(m_leftImageNr);
2223 MainFrame::Get()->RunCPGenerator(linefindSetting, imgs);
2224 }
2225 else
2226 {
2227 HuginBase::UIntSet imgs;
2228 imgs.insert(m_leftImageNr);
2229 imgs.insert(m_rightImageNr);
2230 MainFrame::Get()->RunCPGenerator(imgs);
2231 };
2232 };
2233
OnCelesteButton(wxCommandEvent & e)2234 void CPEditorPanel::OnCelesteButton(wxCommandEvent & e)
2235 {
2236 if (currentPoints.empty())
2237 {
2238 wxMessageBox(_("Cannot run celeste without at least one control point connecting the two images"),_("Error"));
2239 std::cout << "Cannot run celeste without at least one control point connecting the two images" << std::endl;
2240 }
2241 else
2242 {
2243 ProgressReporterDialog progress(4, _("Running Celeste"), _("Running Celeste"), this);
2244 progress.updateDisplayValue(_("Loading model file"));
2245
2246 struct celeste::svm_model* model=MainFrame::Get()->GetSVMModel();
2247 if(model==NULL)
2248 {
2249 return;
2250 };
2251
2252 // Get Celeste parameters
2253 wxConfigBase *cfg = wxConfigBase::Get();
2254 // SVM threshold
2255 double threshold = HUGIN_CELESTE_THRESHOLD;
2256 cfg->Read(wxT("/Celeste/Threshold"), &threshold, HUGIN_CELESTE_THRESHOLD);
2257
2258 // Mask resolution - 1 sets it to fine
2259 bool t = (cfg->Read(wxT("/Celeste/Filter"), HUGIN_CELESTE_FILTER) == 0);
2260 int radius=(t)?10:20;
2261 DEBUG_TRACE("Running Celeste");
2262
2263 if (!progress.updateDisplayValue(_("Running Celeste")))
2264 {
2265 return;
2266 }
2267 // Image to analyse
2268 ImageCache::EntryPtr img=ImageCache::getInstance().getImage(m_pano->getImage(m_leftImageNr).getFilename());
2269 vigra::UInt16RGBImage in;
2270 if(img->image16->width()>0)
2271 {
2272 in.resize(img->image16->size());
2273 vigra::omp::copyImage(srcImageRange(*(img->image16)),destImage(in));
2274 }
2275 else
2276 {
2277 ImageCache::ImageCacheRGB8Ptr im8=img->get8BitImage();
2278 in.resize(im8->size());
2279 vigra::omp::transformImage(srcImageRange(*im8),destImage(in),vigra::functor::Arg1()*vigra::functor::Param(65535/255));
2280 };
2281 // convert to sRGB if icc profile found in file
2282 if (!img->iccProfile->empty())
2283 {
2284 HuginBase::Color::ApplyICCProfile(in, *(img->iccProfile), TYPE_RGB_16);
2285 };
2286 if (!progress.updateDisplayValue())
2287 {
2288 return;
2289 };
2290 HuginBase::UIntSet cloudCP = celeste::getCelesteControlPoints(model, in, currentPoints, radius, threshold, 800);
2291 in.resize(0,0);
2292 if (!progress.updateDisplay())
2293 {
2294 return;
2295 }
2296
2297 if(!cloudCP.empty())
2298 {
2299 PanoCommand::GlobalCmdHist::getInstance().addCommand(
2300 new PanoCommand::RemoveCtrlPointsCmd(*m_pano,cloudCP)
2301 );
2302 };
2303
2304 progress.updateDisplayValue();
2305 wxMessageBox(wxString::Format(_("Removed %lu control points"), static_cast<unsigned long int>(cloudCP.size())), _("Celeste result"), wxOK | wxICON_INFORMATION, this);
2306 DEBUG_TRACE("Finished running Celeste");
2307 }
2308 }
2309
OnCleanCPButton(wxCommandEvent & e)2310 void CPEditorPanel::OnCleanCPButton(wxCommandEvent& e)
2311 {
2312 if (currentPoints.size() < 2)
2313 {
2314 wxBell();
2315 return;
2316 };
2317 // calculate mean and variance only for currently active cp
2318 double mean = 0;
2319 double var = 0;
2320 size_t n = 0;
2321 for (HuginBase::CPointVector::const_iterator it = currentPoints.begin(); it != currentPoints.end(); ++it)
2322 {
2323 n++;
2324 double x = it->second.error;
2325 double delta = x - mean;
2326 mean += delta / n;
2327 var += delta*(x - mean);
2328 }
2329 var = var / (n - 1);
2330 const double limit = (sqrt(var) > mean) ? mean : (mean + sqrt(var));
2331 HuginBase::UIntSet removedCPs;
2332 for (HuginBase::CPointVector::const_iterator it = currentPoints.begin(); it != currentPoints.end(); ++it)
2333 {
2334 if (it->second.error > limit)
2335 {
2336 removedCPs.insert(it->first);
2337 };
2338 };
2339 if (!removedCPs.empty())
2340 {
2341 wxMessageBox(wxString::Format(_("Removed %lu control points"), (unsigned long int)removedCPs.size()), _("Cleaning"), wxOK | wxICON_INFORMATION, this);
2342 PanoCommand::GlobalCmdHist::getInstance().addCommand(new PanoCommand::RemoveCtrlPointsCmd(*m_pano, removedCPs));
2343 }
2344 else
2345 {
2346 wxBell();
2347 }
2348 };
2349
OnActionSelectCreate(wxCommandEvent & e)2350 void CPEditorPanel::OnActionSelectCreate(wxCommandEvent& e)
2351 {
2352 m_cpActionButtonMode = CPTAB_ACTION_CREATE_CP;
2353 wxString s(_("Create cp"));
2354 s.Append(wxT("\u25bc"));
2355 m_actionButton->SetLabel(s);
2356 m_actionButton->SetToolTip(_("Create control points for image pair with currently selected control point detector on photos tab."));
2357 Layout();
2358 wxConfig::Get()->Write(wxT("/CPEditorPanel/ActionMode"), static_cast<long>(m_cpActionButtonMode));
2359 };
2360
OnActionSelectCeleste(wxCommandEvent & e)2361 void CPEditorPanel::OnActionSelectCeleste(wxCommandEvent& e)
2362 {
2363 m_cpActionButtonMode = CPTAB_ACTION_CELESTE;
2364 wxString s(_("Celeste"));
2365 s.Append(wxT("\u25bc"));
2366 m_actionButton->SetLabel(s);
2367 m_actionButton->SetToolTip(_("Tries to remove control points from clouds"));
2368 Layout();
2369 wxConfig::Get()->Write(wxT("/CPEditorPanel/ActionMode"), static_cast<long>(m_cpActionButtonMode));
2370 };
2371
OnActionSelectCleanCP(wxCommandEvent & e)2372 void CPEditorPanel::OnActionSelectCleanCP(wxCommandEvent& e)
2373 {
2374 m_cpActionButtonMode = CPTAB_ACTION_CLEAN_CP;
2375 wxString s(_("Clean cp"));
2376 s.Append(wxT("\u25bc"));
2377 m_actionButton->SetLabel(s);
2378 m_actionButton->SetToolTip(_("Remove outlying control points by statistical method"));
2379 Layout();
2380 wxConfig::Get()->Write(wxT("/CPEditorPanel/ActionMode"), static_cast<long>(m_cpActionButtonMode));
2381 };
2382
LocalFineTunePoint(unsigned int srcNr,const vigra::Diff2D & srcPnt,hugin_utils::FDiff2D & movedSrcPnt,unsigned int moveNr,const hugin_utils::FDiff2D & movePnt)2383 hugin_utils::FDiff2D CPEditorPanel::LocalFineTunePoint(unsigned int srcNr,
2384 const vigra::Diff2D & srcPnt,
2385 hugin_utils::FDiff2D & movedSrcPnt,
2386 unsigned int moveNr,
2387 const hugin_utils::FDiff2D & movePnt)
2388 {
2389 long templWidth = wxConfigBase::Get()->Read(wxT("/Finetune/TemplateSize"),HUGIN_FT_TEMPLATE_SIZE);
2390 long sWidth = templWidth + wxConfigBase::Get()->Read(wxT("/Finetune/LocalSearchWidth"),HUGIN_FT_LOCAL_SEARCH_WIDTH);
2391 vigra_ext::CorrelationResult result;
2392 if (!PointFineTune(srcNr, srcPnt, templWidth, moveNr, movePnt, sWidth, result))
2393 {
2394 return hugin_utils::FDiff2D(-1, -1);
2395 };
2396 movedSrcPnt = result.corrPos;
2397 if (result.corrPos.x < 0 || result.corrPos.y < 0 || result.maxpos.x < 0 || result.maxpos.y < 0)
2398 {
2399 return hugin_utils::FDiff2D(-1, -1);
2400 }
2401 return result.maxpos;
2402 }
2403
FineTuneSelectedPoint(bool left)2404 void CPEditorPanel::FineTuneSelectedPoint(bool left)
2405 {
2406 DEBUG_DEBUG(" selected Point: " << m_selectedPoint);
2407 if (m_selectedPoint == UINT_MAX) return;
2408 DEBUG_ASSERT(m_selectedPoint < currentPoints.size());
2409
2410 HuginBase::ControlPoint cp = currentPoints[m_selectedPoint].second;
2411
2412 unsigned int srcNr = cp.image1Nr;
2413 unsigned int moveNr = cp.image2Nr;
2414 vigra::Diff2D srcPnt(hugin_utils::roundi(cp.x1), hugin_utils::roundi(cp.y1));
2415 vigra::Diff2D movePnt(hugin_utils::roundi(cp.x2), hugin_utils::roundi(cp.y2));
2416 if (left) {
2417 srcNr = cp.image2Nr;
2418 moveNr = cp.image1Nr;
2419 srcPnt = vigra::Diff2D(hugin_utils::roundi(cp.x2), hugin_utils::roundi(cp.y2));
2420 movePnt = vigra::Diff2D(hugin_utils::roundi(cp.x1), hugin_utils::roundi(cp.y1));
2421 }
2422
2423 hugin_utils::FDiff2D movedSrcPnt;
2424 hugin_utils::FDiff2D result = LocalFineTunePoint(srcNr, srcPnt, movedSrcPnt, moveNr, movePnt);
2425
2426 if (result.x < 0 || result.y < 0)
2427 {
2428 wxBell();
2429 return;
2430 };
2431
2432 if (left) {
2433 cp.x1 = result.x;
2434 cp.y1 = result.y;
2435 cp.x2 = movedSrcPnt.x;
2436 cp.y2 = movedSrcPnt.y;
2437 } else {
2438 cp.x2 = result.x;
2439 cp.y2 = result.y;
2440 cp.x1 = movedSrcPnt.x;
2441 cp.y1 = movedSrcPnt.y;
2442 }
2443
2444 // if point was mirrored, reverse before setting it.
2445 if (set_contains(mirroredPoints, m_selectedPoint)) {
2446 cp.mirror();
2447 }
2448 PanoCommand::GlobalCmdHist::getInstance().addCommand(
2449 new PanoCommand::ChangeCtrlPointCmd(*m_pano, currentPoints[m_selectedPoint].first, cp)
2450 );
2451 }
2452
2453
FineTuneNewPoint(bool left)2454 void CPEditorPanel::FineTuneNewPoint(bool left)
2455 {
2456 if (!(cpCreationState == RIGHT_POINT_RETRY ||
2457 cpCreationState == LEFT_POINT_RETRY ||
2458 cpCreationState == BOTH_POINTS_SELECTED))
2459 {
2460 return;
2461 }
2462
2463 hugin_utils::FDiff2D leftP = m_leftImg->getNewPoint();
2464 hugin_utils::FDiff2D rightP = m_rightImg->getNewPoint();
2465
2466 unsigned int srcNr = m_leftImageNr;
2467 vigra::Diff2D srcPnt(leftP.toDiff2D());
2468 unsigned int moveNr = m_rightImageNr;
2469 vigra::Diff2D movePnt(rightP.toDiff2D());
2470 if (left) {
2471 srcNr = m_rightImageNr;
2472 srcPnt = rightP.toDiff2D();
2473 moveNr = m_leftImageNr;
2474 movePnt = leftP.toDiff2D();
2475 }
2476
2477 hugin_utils::FDiff2D movedSrcPnt;
2478 hugin_utils::FDiff2D result = LocalFineTunePoint(srcNr, srcPnt, movedSrcPnt, moveNr, movePnt);
2479
2480 if (result.x < 0 || result.y < 0)
2481 {
2482 wxBell();
2483 return;
2484 };
2485 if (left) {
2486 m_leftImg->setNewPoint(result);
2487 m_leftImg->update();
2488 m_rightImg->setNewPoint(movedSrcPnt);
2489 m_rightImg->update();
2490
2491 } else {
2492 m_rightImg->setNewPoint(result);
2493 m_rightImg->update();
2494 m_leftImg->setNewPoint(movedSrcPnt);
2495 m_leftImg->update();
2496 }
2497 }
2498
EstimatePoint(const hugin_utils::FDiff2D & p,bool left)2499 hugin_utils::FDiff2D CPEditorPanel::EstimatePoint(const hugin_utils::FDiff2D & p, bool left)
2500 {
2501 size_t nrNormalCp = 0;
2502 for (HuginBase::CPointVector::const_iterator it = currentPoints.begin(); it != currentPoints.end(); ++it)
2503 {
2504 if (it->second.mode == HuginBase::ControlPoint::X_Y)
2505 {
2506 ++nrNormalCp;
2507 };
2508 };
2509 if (nrNormalCp==0)
2510 {
2511 DEBUG_WARN("Cannot estimate position without at least one point");
2512 return hugin_utils::FDiff2D(0, 0);
2513 }
2514
2515 // get copy of SrcPanoImage and reset position
2516 HuginBase::SrcPanoImage leftImg = m_pano->getSrcImage(left ? m_leftImageNr : m_rightImageNr);
2517 leftImg.setYaw(0);
2518 leftImg.setPitch(0);
2519 leftImg.setRoll(0);
2520 leftImg.setX(0);
2521 leftImg.setY(0);
2522 leftImg.setZ(0);
2523 HuginBase::SrcPanoImage rightImg = m_pano->getSrcImage(left ? m_rightImageNr : m_leftImageNr);
2524 rightImg.setYaw(0);
2525 rightImg.setPitch(0);
2526 rightImg.setRoll(0);
2527 rightImg.setX(0);
2528 rightImg.setY(0);
2529 rightImg.setZ(0);
2530 // generate a temporary pano
2531 HuginBase::Panorama optPano;
2532 optPano.addImage(leftImg);
2533 optPano.addImage(rightImg);
2534 // construct OptimizeVector
2535 HuginBase::OptimizeVector optVec;
2536 std::set<std::string> opt;
2537 optVec.push_back(opt);
2538 opt.insert("y");
2539 opt.insert("p");
2540 if (nrNormalCp > 1)
2541 {
2542 opt.insert("r");
2543 };
2544 optVec.push_back(opt);
2545 optPano.setOptimizeVector(optVec);
2546 // now add control points, need to check image numbers
2547 HuginBase::CPVector cps;
2548 for (HuginBase::CPointVector::const_iterator it = currentPoints.begin(); it != currentPoints.end(); ++it)
2549 {
2550 HuginBase::ControlPoint cp(it->second);
2551 if (cp.mode == HuginBase::ControlPoint::X_Y)
2552 {
2553 cp.image1Nr = left ? 0 : 1;
2554 cp.image2Nr = left ? 1 : 0;
2555 cps.push_back(cp);
2556 };
2557 };
2558 optPano.setCtrlPoints(cps);
2559 deregisterPTWXDlgFcn();
2560 HuginBase::PTools::optimize(optPano);
2561 registerPTWXDlgFcn();
2562
2563 // now transform the wanted point p to other image
2564 HuginBase::PTools::Transform transformBackward;
2565 transformBackward.createInvTransform(optPano.getImage(0), optPano.getOptions());
2566 HuginBase::PTools::Transform transformForward;
2567 transformForward.createTransform(optPano.getImage(1), optPano.getOptions());
2568 hugin_utils::FDiff2D t;
2569 if (transformBackward.transformImgCoord(t, p))
2570 {
2571 if (transformForward.transformImgCoord(t, t))
2572 {
2573 // clip to fit to
2574 if (t.x < 0) t.x = 0;
2575 if (t.y < 0) t.y = 0;
2576 if (t.x > optPano.getImage(1).getWidth()) t.x = optPano.getImage(1).getWidth();
2577 if (t.y > optPano.getImage(1).getHeight()) t.y = optPano.getImage(1).getHeight();
2578 DEBUG_DEBUG("estimated point " << t.x << "," << t.y);
2579 return t;
2580 };
2581 };
2582 wxBell();
2583 return hugin_utils::FDiff2D(0, 0);
2584 }
2585
OnColumnWidthChange(wxListEvent & e)2586 void CPEditorPanel::OnColumnWidthChange( wxListEvent & e )
2587 {
2588 int colNum = e.GetColumn();
2589 wxConfigBase::Get()->Write( wxString::Format(wxT("/CPEditorPanel/ColumnWidth%d"),colNum), m_cpList->GetColumnWidth(colNum) );
2590 }
2591
OnColumnHeaderClick(wxListEvent & e)2592 void CPEditorPanel::OnColumnHeaderClick(wxListEvent & e)
2593 {
2594 const int newCol = e.GetColumn();
2595 if (m_sortCol == newCol)
2596 {
2597 m_sortAscending = !m_sortAscending;
2598 SetColumnImage(m_cpList, m_sortCol, m_sortAscending ? 0 : 1);
2599 }
2600 else
2601 {
2602 if (m_sortCol != -1)
2603 {
2604 SetColumnImage(m_cpList, m_sortCol, -1);
2605 };
2606 m_sortCol = newCol;
2607 SetColumnImage(m_cpList, m_sortCol, 0);
2608 m_sortAscending = true;
2609 }
2610 SortList();
2611 Refresh();
2612 }
2613
GetRot(double yaw,double pitch,double roll)2614 CPImageCtrl::ImageRotation CPEditorPanel::GetRot(double yaw, double pitch, double roll)
2615 {
2616 CPImageCtrl::ImageRotation rot = CPImageCtrl::ROT0;
2617 // normalize roll angle
2618 while (roll > 360) roll-= 360;
2619 while (roll < 0) roll += 360;
2620
2621 while (pitch > 180) pitch -= 360;
2622 while (pitch < -180) pitch += 360;
2623 bool headOver = (pitch > 90 || pitch < -90);
2624
2625 if (wxConfig::Get()->Read(wxT("/CPEditorPanel/AutoRot"),1L)) {
2626 if (roll >= 315 || roll < 45) {
2627 rot = headOver ? CPImageCtrl::ROT180 : CPImageCtrl::ROT0;
2628 } else if (roll >= 45 && roll < 135) {
2629 rot = headOver ? CPImageCtrl::ROT270 : CPImageCtrl::ROT90;
2630 } else if (roll >= 135 && roll < 225) {
2631 rot = headOver ? CPImageCtrl::ROT0 : CPImageCtrl::ROT180;
2632 } else {
2633 rot = headOver ? CPImageCtrl::ROT90 : CPImageCtrl::ROT270;
2634 }
2635 }
2636 return rot;
2637 }
2638
IMPLEMENT_DYNAMIC_CLASS(CPEditorPanel,wxPanel)2639 IMPLEMENT_DYNAMIC_CLASS(CPEditorPanel, wxPanel)
2640
2641 CPEditorPanelXmlHandler::CPEditorPanelXmlHandler()
2642 : wxXmlResourceHandler()
2643 {
2644 AddWindowStyles();
2645 }
2646
DoCreateResource()2647 wxObject *CPEditorPanelXmlHandler::DoCreateResource()
2648 {
2649 XRC_MAKE_INSTANCE(cp, CPEditorPanel)
2650
2651 cp->Create(m_parentAsWindow,
2652 GetID(),
2653 GetPosition(), GetSize(),
2654 GetStyle(wxT("style")),
2655 GetName());
2656
2657 SetupWindow( cp);
2658
2659 return cp;
2660 }
2661
CanHandle(wxXmlNode * node)2662 bool CPEditorPanelXmlHandler::CanHandle(wxXmlNode *node)
2663 {
2664 return IsOfClass(node, wxT("CPEditorPanel"));
2665 }
2666
2667 IMPLEMENT_DYNAMIC_CLASS(CPEditorPanelXmlHandler, wxXmlResourceHandler)
2668
2669