Commit 29ce17eb290f1a613260249cec833455c19bfa74

Authored by okahilak
Committed by GitHub
1 parent c5fe4f20
Exists in master

MOD: Allow setting object fiducials using pedal (#355)

* Add OBJECT_FIDUCIALS dict to constants.py, replacing BTNS_OBJ
  and TIPS_OBJ, and facilitating cleaning up the code.

* Pass PedalConnection object to ObjectCalibrationDialog so that
  the pedal can be used for object calibration.

* Copy and slightly adapt the code that was used to set tracker
  fiducials using pedal (in OnTrackerFiducials in NeuronavigationPanel
  class) to do the same for object fiducials. Mark code duplication with
  TODO comment for later clean-up.

* Rename a function (OnGetObjectFiducials -> OnObjectFiducialButton),
  and rename a few variables (e.g., btn_id -> fiducial_index).
invesalius/constants.py
@@ -761,17 +761,38 @@ OBJA = wx.NewId() @@ -761,17 +761,38 @@ OBJA = wx.NewId()
761 OBJC = wx.NewId() 761 OBJC = wx.NewId()
762 OBJF = wx.NewId() 762 OBJF = wx.NewId()
763 763
764 -BTNS_OBJ = {OBJL: {0: _('Left')},  
765 - OBJR: {1: _('Right')},  
766 - OBJA: {2: _('Anterior')},  
767 - OBJC: {3: _('Center')},  
768 - OBJF: {4: _('Fixed')}}  
769 -  
770 -TIPS_OBJ = [_("Select left object fiducial"),  
771 - _("Select right object fiducial"),  
772 - _("Select anterior object fiducial"),  
773 - _("Select object center"),  
774 - _("Attach sensor to object")] 764 +OBJECT_FIDUCIALS = [
  765 + {
  766 + 'fiducial_index': 0,
  767 + 'button_id': OBJL,
  768 + 'label': _('Left'),
  769 + 'tip': _("Select left object fiducial"),
  770 + },
  771 + {
  772 + 'fiducial_index': 1,
  773 + 'button_id': OBJR,
  774 + 'label': _('Right'),
  775 + 'tip': _("Select right object fiducial"),
  776 + },
  777 + {
  778 + 'fiducial_index': 2,
  779 + 'button_id': OBJA,
  780 + 'label': _('Anterior'),
  781 + 'tip': _("Select anterior object fiducial"),
  782 + },
  783 + {
  784 + 'fiducial_index': 3,
  785 + 'button_id': OBJC,
  786 + 'label': _('Center'),
  787 + 'tip': _("Select object center"),
  788 + },
  789 + {
  790 + 'fiducial_index': 4,
  791 + 'button_id': OBJF,
  792 + 'label': _('Fixed'),
  793 + 'tip': _("Attach sensor to object"),
  794 + },
  795 +]
775 796
776 MTC_PROBE_NAME = "1Probe" 797 MTC_PROBE_NAME = "1Probe"
777 MTC_REF_NAME = "2Ref" 798 MTC_REF_NAME = "2Ref"
invesalius/gui/dialogs.py
@@ -3304,8 +3304,9 @@ class MaskDensityDialog(wx.Dialog): @@ -3304,8 +3304,9 @@ class MaskDensityDialog(wx.Dialog):
3304 3304
3305 class ObjectCalibrationDialog(wx.Dialog): 3305 class ObjectCalibrationDialog(wx.Dialog):
3306 3306
3307 - def __init__(self, tracker): 3307 + def __init__(self, tracker, pedal_connection):
3308 self.tracker = tracker 3308 self.tracker = tracker
  3309 + self.pedal_connection = pedal_connection
3309 3310
3310 self.trk_init, self.tracker_id = tracker.GetTrackerInfo() 3311 self.trk_init, self.tracker_id = tracker.GetTrackerInfo()
3311 3312
@@ -3313,6 +3314,7 @@ class ObjectCalibrationDialog(wx.Dialog): @@ -3313,6 +3314,7 @@ class ObjectCalibrationDialog(wx.Dialog):
3313 self.obj_name = None 3314 self.obj_name = None
3314 self.polydata = None 3315 self.polydata = None
3315 self.use_default_object = False 3316 self.use_default_object = False
  3317 + self.object_fiducial_being_set = None
3316 3318
3317 self.obj_fiducials = np.full([5, 3], np.nan) 3319 self.obj_fiducials = np.full([5, 3], np.nan)
3318 self.obj_orients = np.full([5, 3], np.nan) 3320 self.obj_orients = np.full([5, 3], np.nan)
@@ -3323,6 +3325,11 @@ class ObjectCalibrationDialog(wx.Dialog): @@ -3323,6 +3325,11 @@ class ObjectCalibrationDialog(wx.Dialog):
3323 self._init_gui() 3325 self._init_gui()
3324 self.LoadObject() 3326 self.LoadObject()
3325 3327
  3328 + self.__bind_events()
  3329 +
  3330 + def __bind_events(self):
  3331 + Publisher.subscribe(self.SetObjectFiducial, 'Set object fiducial')
  3332 +
3326 def _init_gui(self): 3333 def _init_gui(self):
3327 self.interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize()) 3334 self.interactor = wxVTKRenderWindowInteractor(self, -1, size=self.GetSize())
3328 self.interactor.Enable(1) 3335 self.interactor.Enable(1)
@@ -3373,15 +3380,17 @@ class ObjectCalibrationDialog(wx.Dialog): @@ -3373,15 +3380,17 @@ class ObjectCalibrationDialog(wx.Dialog):
3373 choice_sensor]) 3380 choice_sensor])
3374 3381
3375 # Push buttons for object fiducials 3382 # Push buttons for object fiducials
3376 - btns_obj = const.BTNS_OBJ  
3377 - tips_obj = const.TIPS_OBJ 3383 + for object_fiducial in const.OBJECT_FIDUCIALS:
  3384 + index = object_fiducial['fiducial_index']
  3385 + label = object_fiducial['label']
  3386 + button_id = object_fiducial['button_id']
  3387 + tip = object_fiducial['tip']
3378 3388
3379 - for k in btns_obj:  
3380 - n = list(btns_obj[k].keys())[0]  
3381 - lab = list(btns_obj[k].values())[0]  
3382 - self.btns_coord[n] = wx.Button(self, k, label=lab, size=wx.Size(60, 23))  
3383 - self.btns_coord[n].SetToolTip(wx.ToolTip(tips_obj[n]))  
3384 - self.btns_coord[n].Bind(wx.EVT_BUTTON, self.OnGetObjectFiducials) 3389 + ctrl = wx.ToggleButton(self, button_id, label=label, size=wx.Size(60, 23))
  3390 + ctrl.SetToolTip(wx.ToolTip(tip))
  3391 + ctrl.Bind(wx.EVT_TOGGLEBUTTON, partial(self.OnObjectFiducialButton, index, ctrl=ctrl))
  3392 +
  3393 + self.btns_coord[index] = ctrl
3385 3394
3386 for m in range(0, 5): 3395 for m in range(0, 5):
3387 for n in range(0, 3): 3396 for n in range(0, 3):
@@ -3525,13 +3534,42 @@ class ObjectCalibrationDialog(wx.Dialog): @@ -3525,13 +3534,42 @@ class ObjectCalibrationDialog(wx.Dialog):
3525 self.ren.AddActor(ball_actor) 3534 self.ren.AddActor(ball_actor)
3526 return ball_actor, tactor 3535 return ball_actor, tactor
3527 3536
3528 - def OnGetObjectFiducials(self, evt): 3537 + def OnObjectFiducialButton(self, index, evt, ctrl):
3529 if not self.tracker.IsTrackerInitialized(): 3538 if not self.tracker.IsTrackerInitialized():
3530 ShowNavigationTrackerWarning(0, 'choose') 3539 ShowNavigationTrackerWarning(0, 'choose')
3531 return 3540 return
3532 3541
3533 - btn_id = list(const.BTNS_OBJ[evt.GetId()].keys())[0] 3542 + # TODO: The code below until the end of the function is essentially copy-paste from
  3543 + # OnTrackerFiducials function in NeuronavigationPanel class. Probably the easiest
  3544 + # way to deduplicate this would be to create a Fiducial class, which would contain
  3545 + # this code just once.
  3546 + #
  3547 +
  3548 + # Do not allow several object fiducials to be set at the same time.
  3549 + if self.object_fiducial_being_set is not None and self.object_fiducial_being_set != index:
  3550 + ctrl.SetValue(False)
  3551 + return
  3552 +
  3553 + # Called when the button for setting the object fiducial is enabled and either pedal is pressed
  3554 + # or the button is pressed again.
  3555 + #
  3556 + def set_fiducial_callback():
  3557 + Publisher.sendMessage('Set object fiducial', fiducial_index=index)
  3558 + if self.pedal_connection is not None:
  3559 + self.pedal_connection.remove_callback('fiducial')
  3560 +
  3561 + ctrl.SetValue(False)
  3562 + self.object_fiducial_being_set = None
  3563 +
  3564 + if ctrl.GetValue():
  3565 + self.object_fiducial_being_set = index
  3566 +
  3567 + if self.pedal_connection is not None:
  3568 + self.pedal_connection.add_callback('fiducial', set_fiducial_callback)
  3569 + else:
  3570 + set_fiducial_callback()
3534 3571
  3572 + def SetObjectFiducial(self, fiducial_index):
3535 coord, coord_raw = self.tracker.GetTrackerCoordinates( 3573 coord, coord_raw = self.tracker.GetTrackerCoordinates(
3536 # XXX: Always use static reference mode when getting the coordinates. This is what the 3574 # XXX: Always use static reference mode when getting the coordinates. This is what the
3537 # code did previously, as well. At some point, it should probably be thought through 3575 # code did previously, as well. At some point, it should probably be thought through
@@ -3549,22 +3587,22 @@ class ObjectCalibrationDialog(wx.Dialog): @@ -3549,22 +3587,22 @@ class ObjectCalibrationDialog(wx.Dialog):
3549 # mode" principle above, but it's hard to come up with a simple change to increase the consistency 3587 # mode" principle above, but it's hard to come up with a simple change to increase the consistency
3550 # and not change the function to the point of potentially breaking it.) 3588 # and not change the function to the point of potentially breaking it.)
3551 # 3589 #
3552 - if self.obj_ref_id and btn_id == 4: 3590 + if self.obj_ref_id and fiducial_index == 4:
3553 coord = coord_raw[self.obj_ref_id, :] 3591 coord = coord_raw[self.obj_ref_id, :]
3554 coord[2] = -coord[2] 3592 coord[2] = -coord[2]
3555 3593
3556 - if btn_id == 3: 3594 + if fiducial_index == 3:
3557 coord = np.zeros([6,]) 3595 coord = np.zeros([6,])
3558 3596
3559 # Update text controls with tracker coordinates 3597 # Update text controls with tracker coordinates
3560 if coord is not None or np.sum(coord) != 0.0: 3598 if coord is not None or np.sum(coord) != 0.0:
3561 - self.obj_fiducials[btn_id, :] = coord[:3]  
3562 - self.obj_orients[btn_id, :] = coord[3:]  
3563 - for n in [0, 1, 2]:  
3564 - self.txt_coord[btn_id][n].SetLabel(str(round(coord[n], 1)))  
3565 - if self.text_actors[btn_id]:  
3566 - self.text_actors[btn_id].GetProperty().SetColor(0.0, 1.0, 0.0)  
3567 - self.ball_actors[btn_id].GetProperty().SetColor(0.0, 1.0, 0.0) 3599 + self.obj_fiducials[fiducial_index, :] = coord[:3]
  3600 + self.obj_orients[fiducial_index, :] = coord[3:]
  3601 + for i in [0, 1, 2]:
  3602 + self.txt_coord[fiducial_index][i].SetLabel(str(round(coord[i], 1)))
  3603 + if self.text_actors[fiducial_index]:
  3604 + self.text_actors[fiducial_index].GetProperty().SetColor(0.0, 1.0, 0.0)
  3605 + self.ball_actors[fiducial_index].GetProperty().SetColor(0.0, 1.0, 0.0)
3568 self.Refresh() 3606 self.Refresh()
3569 else: 3607 else:
3570 ShowNavigationTrackerWarning(0, 'choose') 3608 ShowNavigationTrackerWarning(0, 'choose')
invesalius/gui/task_navigator.py
@@ -162,9 +162,10 @@ class InnerFoldPanel(wx.Panel): @@ -162,9 +162,10 @@ class InnerFoldPanel(wx.Panel):
162 fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition, 162 fold_panel = fpb.FoldPanelBar(self, -1, wx.DefaultPosition,
163 (10, 310), 0, fpb.FPB_SINGLE_FOLD) 163 (10, 310), 0, fpb.FPB_SINGLE_FOLD)
164 164
165 - # Initialize Tracker object here so that it is available to several panels. 165 + # Initialize Tracker and PedalConnection objects here so that they are available to several panels.
166 # 166 #
167 tracker = Tracker() 167 tracker = Tracker()
  168 + pedal_connection = PedalConnection() if HAS_PEDAL_CONNECTION else None
168 169
169 # Fold panel style 170 # Fold panel style
170 style = fpb.CaptionBarStyle() 171 style = fpb.CaptionBarStyle()
@@ -174,7 +175,7 @@ class InnerFoldPanel(wx.Panel): @@ -174,7 +175,7 @@ class InnerFoldPanel(wx.Panel):
174 175
175 # Fold 1 - Navigation panel 176 # Fold 1 - Navigation panel
176 item = fold_panel.AddFoldPanel(_("Neuronavigation"), collapsed=True) 177 item = fold_panel.AddFoldPanel(_("Neuronavigation"), collapsed=True)
177 - ntw = NeuronavigationPanel(item, tracker) 178 + ntw = NeuronavigationPanel(item, tracker, pedal_connection)
178 179
179 fold_panel.ApplyCaptionStyle(item, style) 180 fold_panel.ApplyCaptionStyle(item, style)
180 fold_panel.AddFoldPanelWindow(item, ntw, spacing=0, 181 fold_panel.AddFoldPanelWindow(item, ntw, spacing=0,
@@ -183,7 +184,7 @@ class InnerFoldPanel(wx.Panel): @@ -183,7 +184,7 @@ class InnerFoldPanel(wx.Panel):
183 184
184 # Fold 2 - Object registration panel 185 # Fold 2 - Object registration panel
185 item = fold_panel.AddFoldPanel(_("Object registration"), collapsed=True) 186 item = fold_panel.AddFoldPanel(_("Object registration"), collapsed=True)
186 - otw = ObjectRegistrationPanel(item, tracker) 187 + otw = ObjectRegistrationPanel(item, tracker, pedal_connection)
187 188
188 fold_panel.ApplyCaptionStyle(item, style) 189 fold_panel.ApplyCaptionStyle(item, style)
189 fold_panel.AddFoldPanelWindow(item, otw, spacing=0, 190 fold_panel.AddFoldPanelWindow(item, otw, spacing=0,
@@ -372,7 +373,7 @@ class ICP(): @@ -372,7 +373,7 @@ class ICP():
372 self.icp_fre = None 373 self.icp_fre = None
373 374
374 class NeuronavigationPanel(wx.Panel): 375 class NeuronavigationPanel(wx.Panel):
375 - def __init__(self, parent, tracker): 376 + def __init__(self, parent, tracker, pedal_connection):
376 wx.Panel.__init__(self, parent) 377 wx.Panel.__init__(self, parent)
377 try: 378 try:
378 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) 379 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR)
@@ -385,9 +386,9 @@ class NeuronavigationPanel(wx.Panel): @@ -385,9 +386,9 @@ class NeuronavigationPanel(wx.Panel):
385 self.__bind_events() 386 self.__bind_events()
386 387
387 # Initialize global variables 388 # Initialize global variables
388 - self.pedal_connection = PedalConnection() if HAS_PEDAL_CONNECTION else None 389 + self.pedal_connection = pedal_connection
389 self.navigation = Navigation( 390 self.navigation = Navigation(
390 - pedal_connection=self.pedal_connection, 391 + pedal_connection=pedal_connection,
391 ) 392 )
392 self.icp = ICP() 393 self.icp = ICP()
393 self.tracker = tracker 394 self.tracker = tracker
@@ -451,7 +452,7 @@ class NeuronavigationPanel(wx.Panel): @@ -451,7 +452,7 @@ class NeuronavigationPanel(wx.Panel):
451 txt_fre = wx.StaticText(self, -1, _('FRE:')) 452 txt_fre = wx.StaticText(self, -1, _('FRE:'))
452 txt_icp = wx.StaticText(self, -1, _('Refine:')) 453 txt_icp = wx.StaticText(self, -1, _('Refine:'))
453 454
454 - if self.pedal_connection is not None and self.pedal_connection.in_use: 455 + if pedal_connection is not None and pedal_connection.in_use:
455 txt_pedal_pressed = wx.StaticText(self, -1, _('Pedal pressed:')) 456 txt_pedal_pressed = wx.StaticText(self, -1, _('Pedal pressed:'))
456 else: 457 else:
457 txt_pedal_pressed = None 458 txt_pedal_pressed = None
@@ -480,14 +481,14 @@ class NeuronavigationPanel(wx.Panel): @@ -480,14 +481,14 @@ class NeuronavigationPanel(wx.Panel):
480 self.checkbox_icp = checkbox_icp 481 self.checkbox_icp = checkbox_icp
481 482
482 # An indicator for pedal trigger 483 # An indicator for pedal trigger
483 - if self.pedal_connection is not None and self.pedal_connection.in_use: 484 + if pedal_connection is not None and pedal_connection.in_use:
484 tooltip = wx.ToolTip(_(u"Is the pedal pressed")) 485 tooltip = wx.ToolTip(_(u"Is the pedal pressed"))
485 checkbox_pedal_pressed = wx.CheckBox(self, -1, _(' ')) 486 checkbox_pedal_pressed = wx.CheckBox(self, -1, _(' '))
486 checkbox_pedal_pressed.SetValue(False) 487 checkbox_pedal_pressed.SetValue(False)
487 checkbox_pedal_pressed.Enable(False) 488 checkbox_pedal_pressed.Enable(False)
488 checkbox_pedal_pressed.SetToolTip(tooltip) 489 checkbox_pedal_pressed.SetToolTip(tooltip)
489 490
490 - self.pedal_connection.add_callback('gui', checkbox_pedal_pressed.SetValue) 491 + pedal_connection.add_callback('gui', checkbox_pedal_pressed.SetValue)
491 492
492 self.checkbox_pedal_pressed = checkbox_pedal_pressed 493 self.checkbox_pedal_pressed = checkbox_pedal_pressed
493 else: 494 else:
@@ -521,7 +522,7 @@ class NeuronavigationPanel(wx.Panel): @@ -521,7 +522,7 @@ class NeuronavigationPanel(wx.Panel):
521 (checkbox_icp, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)]) 522 (checkbox_icp, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)])
522 523
523 pedal_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5, vgap=5) 524 pedal_sizer = wx.FlexGridSizer(rows=1, cols=2, hgap=5, vgap=5)
524 - if HAS_PEDAL_CONNECTION and self.pedal_connection.in_use: 525 + if HAS_PEDAL_CONNECTION and pedal_connection.in_use:
525 pedal_sizer.AddMany([(txt_pedal_pressed, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL), 526 pedal_sizer.AddMany([(txt_pedal_pressed, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL),
526 (checkbox_pedal_pressed, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)]) 527 (checkbox_pedal_pressed, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL)])
527 528
@@ -872,7 +873,7 @@ class NeuronavigationPanel(wx.Panel): @@ -872,7 +873,7 @@ class NeuronavigationPanel(wx.Panel):
872 873
873 874
874 class ObjectRegistrationPanel(wx.Panel): 875 class ObjectRegistrationPanel(wx.Panel):
875 - def __init__(self, parent, tracker): 876 + def __init__(self, parent, tracker, pedal_connection):
876 wx.Panel.__init__(self, parent) 877 wx.Panel.__init__(self, parent)
877 try: 878 try:
878 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR) 879 default_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_MENUBAR)
@@ -883,6 +884,7 @@ class ObjectRegistrationPanel(wx.Panel): @@ -883,6 +884,7 @@ class ObjectRegistrationPanel(wx.Panel):
883 self.coil_list = const.COIL 884 self.coil_list = const.COIL
884 885
885 self.tracker = tracker 886 self.tracker = tracker
  887 + self.pedal_connection = pedal_connection
886 888
887 self.nav_prop = None 889 self.nav_prop = None
888 self.obj_fiducials = None 890 self.obj_fiducials = None
@@ -1045,7 +1047,7 @@ class ObjectRegistrationPanel(wx.Panel): @@ -1045,7 +1047,7 @@ class ObjectRegistrationPanel(wx.Panel):
1045 def OnLinkCreate(self, event=None): 1047 def OnLinkCreate(self, event=None):
1046 1048
1047 if self.tracker.IsTrackerInitialized(): 1049 if self.tracker.IsTrackerInitialized():
1048 - dialog = dlg.ObjectCalibrationDialog(self.tracker) 1050 + dialog = dlg.ObjectCalibrationDialog(self.tracker, self.pedal_connection)
1049 try: 1051 try:
1050 if dialog.ShowModal() == wx.ID_OK: 1052 if dialog.ShowModal() == wx.ID_OK:
1051 self.obj_fiducials, self.obj_orients, self.obj_ref_mode, self.obj_name, polydata, use_default_object = dialog.GetValue() 1053 self.obj_fiducials, self.obj_orients, self.obj_ref_mode, self.obj_name, polydata, use_default_object = dialog.GetValue()