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(&currentPoints));
476                 }
477                 else
478                 {
479                     m_cpList->SortItems(SortX1Descending, wxIntPtr(&currentPoints));
480                 };
481                 break;
482             case 2: // y1
483                 if (m_sortAscending)
484                 {
485                     m_cpList->SortItems(SortY1Ascending, wxIntPtr(&currentPoints));
486                 }
487                 else
488                 {
489                     m_cpList->SortItems(SortY1Descending, wxIntPtr(&currentPoints));
490                 };
491                 break;
492             case 3: // x2
493                 if (m_sortAscending)
494                 {
495                     m_cpList->SortItems(SortX2Ascending, wxIntPtr(&currentPoints));
496                 }
497                 else
498                 {
499                     m_cpList->SortItems(SortX2Descending, wxIntPtr(&currentPoints));
500                 };
501                 break;
502             case 4: // y2
503                 if (m_sortAscending)
504                 {
505                     m_cpList->SortItems(SortY2Ascending, wxIntPtr(&currentPoints));
506                 }
507                 else
508                 {
509                     m_cpList->SortItems(SortY2Descending, wxIntPtr(&currentPoints));
510                 };
511                 break;
512             case 5: // mode
513                 if (m_sortAscending)
514                 {
515                     m_cpList->SortItems(SortModeAscending, wxIntPtr(&currentPoints));
516                 }
517                 else
518                 {
519                     m_cpList->SortItems(SortModeDescending, wxIntPtr(&currentPoints));
520                 };
521                 break;
522             case 6: // error
523                 if (m_sortAscending)
524                 {
525                     m_cpList->SortItems(SortErrorAscending, wxIntPtr(&currentPoints));
526                 }
527                 else
528                 {
529                     m_cpList->SortItems(SortErrorDescending, wxIntPtr(&currentPoints));
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