Commit f2fd855bbca344713a5b73d803b4e282e06f92b6

Authored by Renan
Committed by GitHub
1 parent 7fae7387
Exists in master

Navigation ICP coregistration improvements (#299)

* MOD: Get raw coordinates to create points from probe

* ENH: Improve processing data with a new thread
    -ADD: Enable/Disable Done and Apply buttons

* MOD: show the amount of poses recorded

* FIX: removes ICP matrix after disconnect tracker
invesalius/data/vtk_utils.py
@@ -148,6 +148,9 @@ class Text(object): @@ -148,6 +148,9 @@ class Text(object):
148 except(UnicodeEncodeError): 148 except(UnicodeEncodeError):
149 self.mapper.SetInput(value.encode("utf-8", errors='replace')) 149 self.mapper.SetInput(value.encode("utf-8", errors='replace'))
150 150
  151 + def GetValue(self):
  152 + return self.mapper.GetInput()
  153 +
151 def SetCoilDistanceValue(self, value): 154 def SetCoilDistanceValue(self, value):
152 #TODO: Not being used anymore. Can be deleted. 155 #TODO: Not being used anymore. Can be deleted.
153 if isinstance(value, int) or isinstance(value, float): 156 if isinstance(value, int) or isinstance(value, float):
invesalius/gui/dialogs.py
@@ -59,7 +59,8 @@ import invesalius.data.coordinates as dco @@ -59,7 +59,8 @@ import invesalius.data.coordinates as dco
59 import invesalius.gui.widgets.gradient as grad 59 import invesalius.gui.widgets.gradient as grad
60 import invesalius.session as ses 60 import invesalius.session as ses
61 import invesalius.utils as utils 61 import invesalius.utils as utils
62 -import invesalius.data.bases as bases 62 +import invesalius.data.vtk_utils as vtku
  63 +import invesalius.data.coregistration as dcr
63 from invesalius.gui.widgets.inv_spinctrl import InvSpinCtrl, InvFloatSpinCtrl 64 from invesalius.gui.widgets.inv_spinctrl import InvSpinCtrl, InvFloatSpinCtrl
64 from invesalius.gui.widgets import clut_imagedata 65 from invesalius.gui.widgets import clut_imagedata
65 from invesalius.gui.widgets.clut_imagedata import CLUTImageDataWidget, EVT_CLUT_NODE_CHANGED 66 from invesalius.gui.widgets.clut_imagedata import CLUTImageDataWidget, EVT_CLUT_NODE_CHANGED
@@ -3536,10 +3537,9 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3536,10 +3537,9 @@ class ICPCorregistrationDialog(wx.Dialog):
3536 def __init__(self, nav_prop): 3537 def __init__(self, nav_prop):
3537 import invesalius.project as prj 3538 import invesalius.project as prj
3538 3539
3539 - self.__bind_events()  
3540 -  
3541 - self.tracker_id = nav_prop[0]  
3542 - self.trk_init = nav_prop[1] 3540 + self.m_change = nav_prop[0]
  3541 + self.tracker_id = nav_prop[1]
  3542 + self.trk_init = nav_prop[2]
3543 self.obj_ref_id = 2 3543 self.obj_ref_id = 2
3544 self.obj_name = None 3544 self.obj_name = None
3545 self.obj_actor = None 3545 self.obj_actor = None
@@ -3563,12 +3563,6 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3563,12 +3563,6 @@ class ICPCorregistrationDialog(wx.Dialog):
3563 3563
3564 self._init_gui() 3564 self._init_gui()
3565 3565
3566 - def __bind_events(self):  
3567 - Publisher.subscribe(self.UpdateCurrentCoord, 'Set cross focal point')  
3568 -  
3569 - def UpdateCurrentCoord(self, position):  
3570 - self.current_coord = position[:]  
3571 -  
3572 def _init_gui(self): 3566 def _init_gui(self):
3573 self.interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize()) 3567 self.interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize())
3574 self.interactor.Enable(1) 3568 self.interactor.Enable(1)
@@ -3617,12 +3611,15 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3617,12 +3611,15 @@ class ICPCorregistrationDialog(wx.Dialog):
3617 btn_reset.Bind(wx.EVT_BUTTON, self.OnReset) 3611 btn_reset.Bind(wx.EVT_BUTTON, self.OnReset)
3618 3612
3619 btn_apply_icp = wx.Button(self, -1, label=_('Apply registration')) 3613 btn_apply_icp = wx.Button(self, -1, label=_('Apply registration'))
3620 - btn_apply_icp.Bind(wx.EVT_BUTTON, self.OnICP) 3614 + btn_apply_icp.Bind(wx.EVT_BUTTON, self.thread_ICP_start, btn_apply_icp)
  3615 + btn_apply_icp.Enable(False)
  3616 + self.btn_apply_icp = btn_apply_icp
3621 3617
3622 - # Buttons to finish or cancel object registration  
3623 tooltip = wx.ToolTip(_(u"Refine done")) 3618 tooltip = wx.ToolTip(_(u"Refine done"))
3624 btn_ok = wx.Button(self, wx.ID_OK, _(u"Done")) 3619 btn_ok = wx.Button(self, wx.ID_OK, _(u"Done"))
3625 btn_ok.SetToolTip(tooltip) 3620 btn_ok.SetToolTip(tooltip)
  3621 + btn_ok.Enable(False)
  3622 + self.btn_ok = btn_ok
3626 3623
3627 btn_cancel = wx.Button(self, wx.ID_CANCEL) 3624 btn_cancel = wx.Button(self, wx.ID_CANCEL)
3628 btn_cancel.SetHelpText("") 3625 btn_cancel.SetHelpText("")
@@ -3667,20 +3664,41 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3667,20 +3664,41 @@ class ICPCorregistrationDialog(wx.Dialog):
3667 obj_actor.SetMapper(mapper) 3664 obj_actor.SetMapper(mapper)
3668 self.obj_actor = obj_actor 3665 self.obj_actor = obj_actor
3669 3666
  3667 + poses_recorded = vtku.Text()
  3668 + poses_recorded.SetSize(const.TEXT_SIZE_LARGE)
  3669 + poses_recorded.SetPosition((const.X, const.Y))
  3670 + poses_recorded.ShadowOff()
  3671 + poses_recorded.SetValue("Poses recorded: ")
  3672 +
  3673 + collect_points = vtku.Text()
  3674 + collect_points.SetSize(const.TEXT_SIZE_LARGE)
  3675 + collect_points.SetPosition((const.X+0.35, const.Y))
  3676 + collect_points.ShadowOff()
  3677 + collect_points.SetValue("0")
  3678 + self.collect_points = collect_points
  3679 +
3670 self.ren.AddActor(obj_actor) 3680 self.ren.AddActor(obj_actor)
  3681 + self.ren.AddActor(poses_recorded.actor)
  3682 + self.ren.AddActor(collect_points.actor)
3671 self.ren.ResetCamera() 3683 self.ren.ResetCamera()
3672 self.interactor.Render() 3684 self.interactor.Render()
3673 3685
3674 def RemoveActor(self): 3686 def RemoveActor(self):
3675 - #self.ren.RemoveActor(self.obj_actor)  
3676 self.ren.RemoveAllViewProps() 3687 self.ren.RemoveAllViewProps()
3677 self.point_coord = [] 3688 self.point_coord = []
3678 self.transformed_points = [] 3689 self.transformed_points = []
3679 self.m_icp = None 3690 self.m_icp = None
3680 self.SetProgress(0) 3691 self.SetProgress(0)
  3692 + self.btn_apply_icp.Enable(False)
  3693 + self.btn_ok.Enable(False)
3681 self.ren.ResetCamera() 3694 self.ren.ResetCamera()
3682 self.interactor.Render() 3695 self.interactor.Render()
3683 3696
  3697 + def GetCurrentCoord(self):
  3698 + coord_raw = dco.GetCoordinates(self.trk_init, self.tracker_id, const.DYNAMIC_REF)
  3699 + coord, _ = dcr.corregistrate_dynamic((self.m_change, 0), coord_raw, const.DEFAULT_REF_MODE, [None, None])
  3700 + return coord[:3]
  3701 +
3684 def AddMarker(self, size, colour, coord): 3702 def AddMarker(self, size, colour, coord):
3685 """ 3703 """
3686 Points are rendered into the scene. These points give visual information about the registration. 3704 Points are rendered into the scene. These points give visual information about the registration.
@@ -3713,11 +3731,19 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3713,11 +3731,19 @@ class ICPCorregistrationDialog(wx.Dialog):
3713 self.ren.AddActor(sphere_actor) 3731 self.ren.AddActor(sphere_actor)
3714 self.point_coord.append([x, y, z]) 3732 self.point_coord.append([x, y, z])
3715 3733
3716 - self.Refresh() 3734 + self.collect_points.SetValue(str(int(self.collect_points.GetValue()) + 1))
  3735 +
  3736 + self.interactor.Render()
  3737 +
  3738 + if len(self.point_coord) >= 5 and self.btn_apply_icp.IsEnabled() is False:
  3739 + self.btn_apply_icp.Enable(True)
  3740 +
  3741 + if self.progress.GetValue() != 0:
  3742 + self.SetProgress(0)
3717 3743
3718 def SetProgress(self, progress): 3744 def SetProgress(self, progress):
3719 self.progress.SetValue(progress * 100) 3745 self.progress.SetValue(progress * 100)
3720 - self.Refresh() 3746 + self.interactor.Render()
3721 3747
3722 def vtkmatrix_to_numpy(self, matrix): 3748 def vtkmatrix_to_numpy(self, matrix):
3723 """ 3749 """
@@ -3759,7 +3785,8 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3759,7 +3785,8 @@ class ICPCorregistrationDialog(wx.Dialog):
3759 3785
3760 cam.SetFocalPoint(cam_focus) 3786 cam.SetFocalPoint(cam_focus)
3761 cam.SetPosition(cam_pos) 3787 cam.SetPosition(cam_pos)
3762 - self.Refresh() 3788 +
  3789 + self.interactor.Render()
3763 3790
3764 def ErrorEstimation(self, surface, points): 3791 def ErrorEstimation(self, surface, points):
3765 """ 3792 """
@@ -3804,22 +3831,30 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3804,22 +3831,30 @@ class ICPCorregistrationDialog(wx.Dialog):
3804 self.timer.Stop() 3831 self.timer.Stop()
3805 3832
3806 def OnUpdate(self, evt): 3833 def OnUpdate(self, evt):
3807 - self.AddMarker(3, (1, 0, 0), self.current_coord[:3])  
3808 - self.SetCameraVolume(self.current_coord[:3]) 3834 + current_coord = self.GetCurrentCoord()
  3835 + self.AddMarker(3, (1, 0, 0), current_coord)
  3836 + self.SetCameraVolume(current_coord)
3809 3837
3810 def OnCreatePoint(self, evt): 3838 def OnCreatePoint(self, evt):
3811 - self.AddMarker(3,(1,0,0),self.current_coord[:3])  
3812 - self.SetCameraVolume(self.current_coord[:3]) 3839 + current_coord = self.GetCurrentCoord()
  3840 + self.AddMarker(3, (1, 0, 0), current_coord)
  3841 + self.SetCameraVolume(current_coord)
3813 3842
3814 def OnReset(self, evt): 3843 def OnReset(self, evt):
  3844 + if self.cont_point:
  3845 + self.cont_point.SetValue(False)
  3846 + self.OnContinuousAcquisition(evt=None, btn=self.cont_point)
  3847 +
3815 self.RemoveActor() 3848 self.RemoveActor()
3816 self.LoadActor() 3849 self.LoadActor()
3817 3850
3818 - def OnICP(self, evt):  
3819 - self.SetProgress(0.3) 3851 + def OnICP(self):
3820 if self.cont_point: 3852 if self.cont_point:
3821 self.cont_point.SetValue(False) 3853 self.cont_point.SetValue(False)
3822 self.OnContinuousAcquisition(evt=None, btn=self.cont_point) 3854 self.OnContinuousAcquisition(evt=None, btn=self.cont_point)
  3855 +
  3856 + self.SetProgress(0.3)
  3857 +
3823 sourcePoints = np.array(self.point_coord) 3858 sourcePoints = np.array(self.point_coord)
3824 sourcePoints_vtk = vtk.vtkPoints() 3859 sourcePoints_vtk = vtk.vtkPoints()
3825 3860
@@ -3833,6 +3868,8 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3833,6 +3868,8 @@ class ICPCorregistrationDialog(wx.Dialog):
3833 icp.SetSource(source) 3868 icp.SetSource(source)
3834 icp.SetTarget(self.surface) 3869 icp.SetTarget(self.surface)
3835 3870
  3871 + self.SetProgress(0.5)
  3872 +
3836 if self.icp_mode == 0: 3873 if self.icp_mode == 0:
3837 print("Affine mode") 3874 print("Affine mode")
3838 icp.GetLandmarkTransform().SetModeToAffine() 3875 icp.GetLandmarkTransform().SetModeToAffine()
@@ -3860,7 +3897,6 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3860,7 +3897,6 @@ class ICPCorregistrationDialog(wx.Dialog):
3860 3897
3861 transformedSource = icpTransformFilter.GetOutput() 3898 transformedSource = icpTransformFilter.GetOutput()
3862 3899
3863 - self.SetProgress(1)  
3864 3900
3865 for i in range(transformedSource.GetNumberOfPoints()): 3901 for i in range(transformedSource.GetNumberOfPoints()):
3866 p = [0, 0, 0] 3902 p = [0, 0, 0]
@@ -3884,7 +3920,16 @@ class ICPCorregistrationDialog(wx.Dialog): @@ -3884,7 +3920,16 @@ class ICPCorregistrationDialog(wx.Dialog):
3884 self.prev_error = self.ErrorEstimation(self.surface, sourcePoints) 3920 self.prev_error = self.ErrorEstimation(self.surface, sourcePoints)
3885 self.final_error = self.ErrorEstimation(self.surface, self.transformed_points) 3921 self.final_error = self.ErrorEstimation(self.surface, self.transformed_points)
3886 3922
3887 - self.Refresh() 3923 + self.interactor.Render()
  3924 +
  3925 + self.SetProgress(1)
  3926 +
  3927 + self.btn_ok.Enable(True)
  3928 +
  3929 + def thread_ICP_start(self, evt):
  3930 + import threading
  3931 + th = threading.Thread(target=self.OnICP, args=[])
  3932 + th.start()
3888 3933
3889 def GetValue(self): 3934 def GetValue(self):
3890 return self.m_icp, self.point_coord, self.transformed_points, self.prev_error, self.final_error 3935 return self.m_icp, self.point_coord, self.transformed_points, self.prev_error, self.final_error
invesalius/gui/task_navigator.py
@@ -691,6 +691,7 @@ class NeuronavigationPanel(wx.Panel): @@ -691,6 +691,7 @@ class NeuronavigationPanel(wx.Panel):
691 # has been initialized before 691 # has been initialized before
692 if trck and choice != const.DISCTRACK: 692 if trck and choice != const.DISCTRACK:
693 self.ResetTrackerFiducials() 693 self.ResetTrackerFiducials()
  694 + self.ResetIcp()
694 Publisher.sendMessage('Update status text in GUI', 695 Publisher.sendMessage('Update status text in GUI',
695 label=_("Disconnecting tracker...")) 696 label=_("Disconnecting tracker..."))
696 Publisher.sendMessage('Remove sensors ID') 697 Publisher.sendMessage('Remove sensors ID')
@@ -711,6 +712,7 @@ class NeuronavigationPanel(wx.Panel): @@ -711,6 +712,7 @@ class NeuronavigationPanel(wx.Panel):
711 elif choice == const.DISCTRACK: 712 elif choice == const.DISCTRACK:
712 if trck: 713 if trck:
713 self.ResetTrackerFiducials() 714 self.ResetTrackerFiducials()
  715 + self.ResetIcp()
714 Publisher.sendMessage('Update status text in GUI', 716 Publisher.sendMessage('Update status text in GUI',
715 label=_("Disconnecting tracker ...")) 717 label=_("Disconnecting tracker ..."))
716 Publisher.sendMessage('Remove sensors ID') 718 Publisher.sendMessage('Remove sensors ID')
@@ -749,6 +751,7 @@ class NeuronavigationPanel(wx.Panel): @@ -749,6 +751,7 @@ class NeuronavigationPanel(wx.Panel):
749 # When ref mode is changed the tracker coordinates are set to zero 751 # When ref mode is changed the tracker coordinates are set to zero
750 self.ref_mode_id = evt.GetSelection() 752 self.ref_mode_id = evt.GetSelection()
751 self.ResetTrackerFiducials() 753 self.ResetTrackerFiducials()
  754 + self.ResetIcp()
752 # Some trackers do not accept restarting within this time window 755 # Some trackers do not accept restarting within this time window
753 # TODO: Improve the restarting of trackers after changing reference mode 756 # TODO: Improve the restarting of trackers after changing reference mode
754 # self.OnChoiceTracker(None, ctrl) 757 # self.OnChoiceTracker(None, ctrl)
@@ -780,8 +783,8 @@ class NeuronavigationPanel(wx.Panel): @@ -780,8 +783,8 @@ class NeuronavigationPanel(wx.Panel):
780 fiducial_name = const.TRACKER_FIDUCIALS[n]['fiducial_name'] 783 fiducial_name = const.TRACKER_FIDUCIALS[n]['fiducial_name']
781 Publisher.sendMessage('Set tracker fiducial', fiducial_name=fiducial_name) 784 Publisher.sendMessage('Set tracker fiducial', fiducial_name=fiducial_name)
782 785
783 - def OnICP(self):  
784 - dialog = dlg.ICPCorregistrationDialog(nav_prop=(self.tracker_id, self.trk_init, self.ref_mode_id)) 786 + def OnICP(self, m_change):
  787 + dialog = dlg.ICPCorregistrationDialog(nav_prop=(m_change, self.tracker_id, self.trk_init, self.ref_mode_id))
785 if dialog.ShowModal() == wx.ID_OK: 788 if dialog.ShowModal() == wx.ID_OK:
786 self.m_icp, point_coord, transformed_points, prev_error, final_error = dialog.GetValue() 789 self.m_icp, point_coord, transformed_points, prev_error, final_error = dialog.GetValue()
787 #TODO: checkbox in the dialog to transfer the icp points to 3D viewer 790 #TODO: checkbox in the dialog to transfer the icp points to 3D viewer
@@ -972,7 +975,7 @@ class NeuronavigationPanel(wx.Panel): @@ -972,7 +975,7 @@ class NeuronavigationPanel(wx.Panel):
972 975
973 if not self.checkicp.GetValue(): 976 if not self.checkicp.GetValue():
974 if dlg.ICPcorregistration(self.fre): 977 if dlg.ICPcorregistration(self.fre):
975 - m_icp = self.OnICP() 978 + m_icp = self.OnICP(m_change)
976 self.icp_fre = db.calculate_fre(self.fiducials_raw, self.fiducials, self.ref_mode_id, 979 self.icp_fre = db.calculate_fre(self.fiducials_raw, self.fiducials, self.ref_mode_id,
977 m_change, m_icp) 980 m_change, m_icp)
978 self.ctrl_icp() 981 self.ctrl_icp()