Commit b35498fbb8ed2c0d26a9a33c631ccd4a75e67905
1 parent
0eb985ed
Exists in
master
and in
68 other branches
ADD: Remove mask feature (#156)
Showing
3 changed files
with
165 additions
and
22 deletions
Show diff stats
invesalius/data/slice_.py
| ... | ... | @@ -93,6 +93,22 @@ class Slice(object): |
| 93 | 93 | |
| 94 | 94 | ps.Publisher().subscribe(self.OnEnableStyle, 'Enable style') |
| 95 | 95 | ps.Publisher().subscribe(self.OnDisableStyle, 'Disable style') |
| 96 | + ps.Publisher().subscribe(self.OnRemoveMasks, 'Remove masks') | |
| 97 | + | |
| 98 | + | |
| 99 | + def OnRemoveMasks(self, pubsub_evt): | |
| 100 | + selected_items = pubsub_evt.data | |
| 101 | + proj = Project() | |
| 102 | + for item in selected_items: | |
| 103 | + proj.RemoveMask(item) | |
| 104 | + | |
| 105 | + index = self.current_mask.index | |
| 106 | + if (proj.mask_dict) and (index in selected_items): | |
| 107 | + self.SelectCurrentMask(0) | |
| 108 | + elif not proj.mask_dict: | |
| 109 | + self.blend_filter.SetOpacity(1, 0) | |
| 110 | + self.blend_filter.Update() | |
| 111 | + ps.Publisher().sendMessage('Update slice viewer') | |
| 96 | 112 | |
| 97 | 113 | |
| 98 | 114 | def OnEnableStyle(self, pubsub_evt): |
| ... | ... | @@ -157,8 +173,6 @@ class Slice(object): |
| 157 | 173 | |
| 158 | 174 | def __set_current_mask_threshold(self, evt_pubsub): |
| 159 | 175 | session = ses.Session() |
| 160 | - print session.project_status != const.PROJ_OPEN | |
| 161 | - print session.project_status | |
| 162 | 176 | #FIXME: find a better way to implement this |
| 163 | 177 | if (self.num_gradient >= 2) or \ |
| 164 | 178 | (session.project_status != const.PROJ_OPEN): | ... | ... |
invesalius/gui/data_notebook.py
| ... | ... | @@ -25,14 +25,21 @@ import Image |
| 25 | 25 | import wx |
| 26 | 26 | import wx.grid |
| 27 | 27 | import wx.lib.flatnotebook as fnb |
| 28 | +import wx.lib.platebtn as pbtn | |
| 28 | 29 | import wx.lib.pubsub as ps |
| 29 | 30 | |
| 31 | +import gui.dialogs as dlg | |
| 30 | 32 | import gui.widgets.listctrl as listmix |
| 31 | 33 | |
| 34 | + | |
| 35 | + | |
| 36 | +BTN_NEW, BTN_REMOVE, BTN_DUPLICATE = [wx.NewId() for i in xrange(3)] | |
| 37 | + | |
| 32 | 38 | # Panel that initializes notebook and related tabs |
| 33 | 39 | class NotebookPanel(wx.Panel): |
| 34 | 40 | def __init__(self, parent): |
| 35 | - wx.Panel.__init__(self, parent, pos=wx.Point(0, 50), size=wx.Size(256, 140)) | |
| 41 | + wx.Panel.__init__(self, parent, pos=wx.Point(0, 50), | |
| 42 | + size=wx.Size(256, 160)) | |
| 36 | 43 | |
| 37 | 44 | book = wx.Notebook(self, -1,style= wx.BK_DEFAULT) |
| 38 | 45 | # TODO: check under Windows and Linux |
| ... | ... | @@ -41,7 +48,7 @@ class NotebookPanel(wx.Panel): |
| 41 | 48 | if sys.platform != 'win32': |
| 42 | 49 | book.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) |
| 43 | 50 | |
| 44 | - book.AddPage(MasksListCtrlPanel(book), _("Masks")) | |
| 51 | + book.AddPage(MaskPage(book), _("Masks")) | |
| 45 | 52 | book.AddPage(SurfacesListCtrlPanel(book), _("Surfaces")) |
| 46 | 53 | #book.AddPage(MeasuresListCtrlPanel(book), _("Measures")) |
| 47 | 54 | #book.AddPage(AnnotationsListCtrlPanel(book), _("Annotations")) |
| ... | ... | @@ -56,6 +63,103 @@ class NotebookPanel(wx.Panel): |
| 56 | 63 | |
| 57 | 64 | # TODO: insert icons bellow notebook |
| 58 | 65 | |
| 66 | + | |
| 67 | +class MaskPage(wx.Panel): | |
| 68 | + """ | |
| 69 | + Page related to mask items. | |
| 70 | + """ | |
| 71 | + def __init__(self, parent): | |
| 72 | + wx.Panel.__init__(self, parent, pos=wx.Point(0, 50), | |
| 73 | + size=wx.Size(256, 120)) | |
| 74 | + self.__init_gui() | |
| 75 | + | |
| 76 | + def __init_gui(self): | |
| 77 | + # listctrl were existing masks will be listed | |
| 78 | + self.listctrl = MasksListCtrlPanel(self, size=wx.Size(256, 80)) | |
| 79 | + # button control with tools (eg. remove, add new, etc) | |
| 80 | + self.buttonctrl = ButtonControlPanel(self) | |
| 81 | + | |
| 82 | + sizer = wx.BoxSizer(wx.VERTICAL) | |
| 83 | + sizer.Add(self.listctrl, 0, wx.EXPAND) | |
| 84 | + sizer.Add(self.buttonctrl, 0, wx.EXPAND) | |
| 85 | + self.SetSizer(sizer) | |
| 86 | + self.Fit() | |
| 87 | + | |
| 88 | + | |
| 89 | +class ButtonControlPanel(wx.Panel): | |
| 90 | + """ | |
| 91 | + Button control panel that includes data notebook operations. | |
| 92 | + TODO: Enhace interface with parent class - it is really messed up | |
| 93 | + """ | |
| 94 | + def __init__(self, parent): | |
| 95 | + wx.Panel.__init__(self, parent, pos=wx.Point(0, 50), | |
| 96 | + size=wx.Size(256, 20)) | |
| 97 | + self.parent = parent | |
| 98 | + self.__init_gui() | |
| 99 | + | |
| 100 | + def __init_gui(self): | |
| 101 | + | |
| 102 | + # Bitmaps | |
| 103 | + BMP_NEW = wx.Bitmap("../icons/data_new.png", | |
| 104 | + wx.BITMAP_TYPE_PNG) | |
| 105 | + BMP_REMOVE = wx.Bitmap("../icons/data_remove.png", | |
| 106 | + wx.BITMAP_TYPE_PNG) | |
| 107 | + BMP_DUPLICATE = wx.Bitmap("../icons/data_duplicate.png", | |
| 108 | + wx.BITMAP_TYPE_PNG) | |
| 109 | + | |
| 110 | + # Plate buttons based on previous bitmaps | |
| 111 | + button_style = pbtn.PB_STYLE_SQUARE | pbtn.PB_STYLE_DEFAULT | |
| 112 | + button_new = pbtn.PlateButton(self, BTN_NEW, "", | |
| 113 | + BMP_NEW, | |
| 114 | + style=button_style) | |
| 115 | + button_remove = pbtn.PlateButton(self, BTN_REMOVE, "", | |
| 116 | + BMP_REMOVE, | |
| 117 | + style=button_style) | |
| 118 | + button_duplicate = pbtn.PlateButton(self, BTN_DUPLICATE, "", | |
| 119 | + BMP_DUPLICATE, | |
| 120 | + style=button_style) | |
| 121 | + | |
| 122 | + # Add all controls to gui | |
| 123 | + sizer = wx.BoxSizer(wx.HORIZONTAL) | |
| 124 | + sizer.Add(button_new, 0, wx.GROW|wx.EXPAND|wx.LEFT, 10) | |
| 125 | + sizer.Add(button_remove, 0, wx.GROW|wx.EXPAND) | |
| 126 | + sizer.Add(button_duplicate, 0, wx.GROW|wx.EXPAND) | |
| 127 | + self.SetSizer(sizer) | |
| 128 | + self.Fit() | |
| 129 | + | |
| 130 | + self.Bind(wx.EVT_BUTTON, self.OnButton) | |
| 131 | + | |
| 132 | + def OnButton(self, evt): | |
| 133 | + id = evt.GetId() | |
| 134 | + if id == BTN_NEW: | |
| 135 | + self.OnNew() | |
| 136 | + elif id == BTN_REMOVE: | |
| 137 | + self.OnRemove() | |
| 138 | + elif id == BTN_DUPLICATE: | |
| 139 | + self.OnDuplicate() | |
| 140 | + | |
| 141 | + def OnNew(self): | |
| 142 | + print "New" | |
| 143 | + #print self.parent.listctrl.GetSelected() | |
| 144 | + | |
| 145 | + def OnRemove(self): | |
| 146 | + print "Remove" | |
| 147 | + selected_items = self.parent.listctrl.GetSelected() | |
| 148 | + if selected_items: | |
| 149 | + for item in selected_items: | |
| 150 | + self.parent.listctrl.RemoveMask(item) | |
| 151 | + ps.Publisher().sendMessage('Remove masks', selected_items) | |
| 152 | + else: | |
| 153 | + dlg.MaskSelectionRequiredForRemoval() | |
| 154 | + | |
| 155 | + def OnDuplicate(self): | |
| 156 | + print "Duplicate" | |
| 157 | + selected_items = self.parent.listctrl.GetSelected() | |
| 158 | + if selected_items: | |
| 159 | + ps.Publisher().sendMessage('Duplicate masks', selected_items) | |
| 160 | + else: | |
| 161 | + dlg.MaskSelectionRequiredForDuplication() | |
| 162 | + | |
| 59 | 163 | class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
| 60 | 164 | |
| 61 | 165 | def __init__(self, parent, ID=-1, pos=wx.DefaultPosition, |
| ... | ... | @@ -68,7 +172,6 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
| 68 | 172 | wx.ListCtrl.__init__(self, parent, ID, pos, size, style=wx.LC_REPORT) |
| 69 | 173 | listmix.TextEditMixin.__init__(self) |
| 70 | 174 | self.mask_list_index = {} |
| 71 | - self.mask_bmp_idx_to_name = {} | |
| 72 | 175 | self.__init_columns() |
| 73 | 176 | self.__init_image_list() |
| 74 | 177 | self.__bind_events_wx() |
| ... | ... | @@ -91,7 +194,6 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
| 91 | 194 | def OnCloseProject(self, pubsub_evt): |
| 92 | 195 | self.DeleteAllItems() |
| 93 | 196 | self.mask_list_index = {} |
| 94 | - self.mask_bmp_idx_to_name = {} | |
| 95 | 197 | |
| 96 | 198 | def OnChangeCurrentMask(self, pubsub_evt): |
| 97 | 199 | |
| ... | ... | @@ -195,6 +297,35 @@ class MasksListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
| 195 | 297 | image_index = self.mask_list_index[index] |
| 196 | 298 | self.imagelist.Replace(image_index, image) |
| 197 | 299 | self.Refresh() |
| 300 | + | |
| 301 | + def GetSelected(self): | |
| 302 | + """ | |
| 303 | + Return all items selected (highlighted). | |
| 304 | + """ | |
| 305 | + selected = [] | |
| 306 | + for index in self.mask_list_index: | |
| 307 | + if self.IsSelected(index): | |
| 308 | + selected.append(index) | |
| 309 | + # it is important to revert items order, so | |
| 310 | + # listctrl update is ok | |
| 311 | + selected.sort(reverse=True) | |
| 312 | + return selected | |
| 313 | + | |
| 314 | + def RemoveMask(self, index): | |
| 315 | + """ | |
| 316 | + Remove item given its index. | |
| 317 | + """ | |
| 318 | + # it is necessary to update internal dictionary | |
| 319 | + # that maps bitmap given item index | |
| 320 | + old_dict = self.mask_list_index | |
| 321 | + new_dict = {} | |
| 322 | + for i in old_dict: | |
| 323 | + if i < index: | |
| 324 | + new_dict[i] = old_dict[i] | |
| 325 | + if i > index: | |
| 326 | + new_dict[i-1] = old_dict[i] | |
| 327 | + self.mask_list_index = new_dict | |
| 328 | + self.DeleteItem(index) | |
| 198 | 329 | |
| 199 | 330 | class SurfacesListCtrlPanel(wx.ListCtrl, listmix.TextEditMixin): |
| 200 | 331 | ... | ... |
invesalius/gui/task_slice.py
| ... | ... | @@ -26,6 +26,7 @@ import wx.lib.pubsub as ps |
| 26 | 26 | |
| 27 | 27 | import data.mask as mask |
| 28 | 28 | import constants as const |
| 29 | +import gui.dialogs as dlg | |
| 29 | 30 | import gui.widgets.gradient as grad |
| 30 | 31 | import gui.widgets.foldpanelbar as fpb |
| 31 | 32 | import widgets.colourselect as csel |
| ... | ... | @@ -142,25 +143,16 @@ class InnerTaskPanel(wx.Panel): |
| 142 | 143 | |
| 143 | 144 | def OnButtonNextTask(self, evt): |
| 144 | 145 | overwrite = self.check_box.IsChecked() |
| 145 | - ps.Publisher().sendMessage('Create surface from index', | |
| 146 | + if self.GetMaskSelected() != -1: | |
| 147 | + ps.Publisher().sendMessage('Create surface from index', | |
| 146 | 148 | (self.GetMaskSelected(), |
| 147 | 149 | overwrite)) |
| 150 | + else: | |
| 151 | + dlg.InexistentMask() | |
| 148 | 152 | |
| 149 | 153 | def OnLinkNewMask(self, evt=None): |
| 150 | - dlg = wx.TextEntryDialog(self, _('Name of new mask:'), | |
| 151 | - _('InVesalius 3 - New mask')) | |
| 152 | - dlg.CenterOnScreen() | |
| 153 | - default_mask_name = const.MASK_NAME_PATTERN %(mask.Mask.general_index+2) | |
| 154 | - dlg.SetValue(default_mask_name) | |
| 155 | - | |
| 156 | - try: | |
| 157 | - op = dlg.ShowModal() == wx.ID_OK | |
| 158 | - except(wx._core.PyAssertionError): | |
| 159 | - print "win64 - wx._core.PyAssertionError" | |
| 160 | - op = True | |
| 161 | - | |
| 162 | - if (op): | |
| 163 | - mask_name = dlg.GetValue() | |
| 154 | + mask_name = dlg.NewMask() | |
| 155 | + if mask_name: | |
| 164 | 156 | ps.Publisher().sendMessage('Create new mask', mask_name) |
| 165 | 157 | |
| 166 | 158 | if evt: |
| ... | ... | @@ -353,6 +345,7 @@ class MaskProperties(wx.Panel): |
| 353 | 345 | 'Set threshold values in gradient') |
| 354 | 346 | ps.Publisher().subscribe(self.SelectMaskName, 'Select mask name in combo') |
| 355 | 347 | ps.Publisher().subscribe(self.ChangeMaskName, 'Change mask name') |
| 348 | + ps.Publisher().subscribe(self.OnRemoveMasks, 'Remove masks') | |
| 356 | 349 | ps.Publisher().subscribe(self.OnCloseProject, 'Close project data') |
| 357 | 350 | ps.Publisher().subscribe(self.SetThresholdValues2, 'Set threshold values') |
| 358 | 351 | |
| ... | ... | @@ -366,7 +359,12 @@ class MaskProperties(wx.Panel): |
| 366 | 359 | n = self.combo_thresh.GetCount() |
| 367 | 360 | for i in xrange(n-1, -1, -1): |
| 368 | 361 | self.combo_thresh.Delete(i) |
| 369 | - | |
| 362 | + | |
| 363 | + def OnRemoveMasks(self, pubsub_evt): | |
| 364 | + print "OnRemoveMasks" | |
| 365 | + list_index = pubsub_evt.data | |
| 366 | + for i in list_index: | |
| 367 | + self.combo_mask_name.Delete(i) | |
| 370 | 368 | |
| 371 | 369 | def __bind_events_wx(self): |
| 372 | 370 | self.Bind(grad.EVT_THRESHOLD_CHANGE, self.OnSlideChanged, self.gradient) | ... | ... |