diff --git a/invesalius/constants.py b/invesalius/constants.py index 6fcdc48..924db39 100644 --- a/invesalius/constants.py +++ b/invesalius/constants.py @@ -475,6 +475,8 @@ ID_SWAP_XY = wx.NewId() ID_SWAP_XZ = wx.NewId() ID_SWAP_YZ = wx.NewId() +ID_BOOLEAN_MASK = wx.NewId() + #--------------------------------------------------------- STATE_DEFAULT = 1000 STATE_WL = 1001 @@ -565,3 +567,7 @@ PROJECTION_CONTOUR_MIDA=8 #------------ Projections defaults ------------------ PROJECTION_BORDER_SIZE=1.0 PROJECTION_MIP_SIZE=2 + +# ------------- Boolean operations ------------------ +BOOLEAN_UNION = 1 +BOOLEAN_DIFF = 2 diff --git a/invesalius/control.py b/invesalius/control.py index d27e7d1..c9c62a1 100644 --- a/invesalius/control.py +++ b/invesalius/control.py @@ -82,6 +82,8 @@ class Controller(): Publisher.subscribe(self.OnOpenRecentProject, 'Open recent project') Publisher.subscribe(self.OnShowAnalyzeFile, 'Show analyze dialog') + Publisher.subscribe(self.ShowBooleanOpDialog, 'Show boolean dialog') + def OnCancelImport(self, pubsub_evt): #self.cancel_import = True @@ -628,6 +630,6 @@ class Controller(): preset_name + '.plist') plistlib.writePlist(preset, preset_dir) - - - + def ShowBooleanOpDialog(self, pubsub_evt): + dlg = dialogs.MaskBooleanDialog(prj.Project().mask_dict) + dlg.ShowModal() diff --git a/invesalius/data/slice_.py b/invesalius/data/slice_.py index 106fb10..575cb8e 100644 --- a/invesalius/data/slice_.py +++ b/invesalius/data/slice_.py @@ -160,6 +160,8 @@ class Slice(object): Publisher.subscribe(self._set_projection_type, 'Set projection type') + Publisher.subscribe(self._do_boolean_op, 'Do boolean operation') + Publisher.subscribe(self.OnExportMask,'Export mask to file') Publisher.subscribe(self.OnCloseProject, 'Close project data') @@ -1080,12 +1082,17 @@ class Slice(object): else: node.value += shiftWW * factor - def do_threshold_to_a_slice(self, slice_matrix, mask): + def do_threshold_to_a_slice(self, slice_matrix, mask, threshold=None): """ Based on the current threshold bounds generates a threshold mask to given slice_matrix. """ - thresh_min, thresh_max = self.current_mask.threshold_range + if threshold: + thresh_min, thresh_max = threshold + else: + thresh_min, thresh_max = self.current_mask.threshold_range + + print ">>>> THreshold", thresh_min, thresh_max m = (((slice_matrix >= thresh_min) & (slice_matrix <= thresh_max)) * 255) m[mask == 1] = 1 m[mask == 2] = 2 @@ -1106,7 +1113,7 @@ class Slice(object): for n in xrange(1, mask.matrix.shape[0]): if mask.matrix[n, 0, 0] == 0: m = mask.matrix[n, 1:, 1:] - mask.matrix[n, 1:, 1:] = self.do_threshold_to_a_slice(self.matrix[n-1], m) + mask.matrix[n, 1:, 1:] = self.do_threshold_to_a_slice(self.matrix[n-1], m, mask.threshold_range) mask.matrix.flush() @@ -1209,6 +1216,43 @@ class Slice(object): return blend_imagedata.GetOutput() + def _do_boolean_op(self, pubsub_evt): + op, m1, m2 = pubsub_evt.data + self.do_boolean_op(op, m1, m2) + + def do_boolean_op(self, op, m1, m2): + name_ops = {const.BOOLEAN_UNION: _(u"Union"), + const.BOOLEAN_DIFF: _(u"Diff")} + + + name = u"%s_%s_%s" % (name_ops[op], m1.name, m2.name) + proj = Project() + mask_dict = proj.mask_dict + names_list = [mask_dict[i].name for i in mask_dict.keys()] + new_name = utils.next_copy_name(name, names_list) + + future_mask = Mask() + future_mask.create_mask(self.matrix.shape) + future_mask.name = new_name + + future_mask.matrix[:] = 1 + m = future_mask.matrix[1:, 1:, 1:] + + self.do_threshold_to_all_slices(m1) + m1 = m1.matrix[1:, 1:, 1:] + + self.do_threshold_to_all_slices(m2) + m2 = m2.matrix[1:, 1:, 1:] + + if op == const.BOOLEAN_UNION: + m[:] = ((m1 > 2) + (m2 > 2)) * 255 + + elif op == const.BOOLEAN_DIFF: + m[:] = ((m1 > 2) - (m2 > 2)) * 255 + + future_mask.was_edited = True + self._add_mask_into_proj(future_mask) + def apply_slice_buffer_to_mask(self, orientation): """ Apply the modifications (edition) in mask buffer to mask. diff --git a/invesalius/gui/dialogs.py b/invesalius/gui/dialogs.py index c6d7271..577924c 100644 --- a/invesalius/gui/dialogs.py +++ b/invesalius/gui/dialogs.py @@ -1384,3 +1384,65 @@ class ClutImagedataDialog(wx.Dialog): super(wx.Dialog, self).Show(show) if gen_evt: self.clut_widget._generate_event() + + +class MaskBooleanDialog(wx.Dialog): + def __init__(self, masks): + pre = wx.PreDialog() + pre.Create(wx.GetApp().GetTopWindow(), -1, style=wx.DEFAULT_DIALOG_STYLE|wx.FRAME_FLOAT_ON_PARENT) + self.PostCreate(pre) + + self._init_gui(masks) + + def _init_gui(self, masks): + mask_choices = [(masks[i].name, masks[i]) for i in sorted(masks)] + self.mask1 = wx.ComboBox(self, -1, mask_choices[0][0], choices=[]) + self.mask2 = wx.ComboBox(self, -1, mask_choices[0][0], choices=[]) + + for n, m in mask_choices: + self.mask1.Append(n, m) + self.mask2.Append(n, m) + + self.mask1.SetSelection(0) + self.mask2.SetSelection(0) + + op_choices = ((u"Union", const.BOOLEAN_UNION), + (u"Difference", const.BOOLEAN_DIFF)) + self.op_boolean = wx.ComboBox(self, -1, op_choices[0][0], choices=[]) + + for n, i in op_choices: + self.op_boolean.Append(n, i) + + self.op_boolean.SetSelection(0) + + btn_ok = wx.Button(self, wx.ID_OK) + btn_ok.SetDefault() + + btn_cancel = wx.Button(self, wx.ID_CANCEL) + + btnsizer = wx.StdDialogButtonSizer() + btnsizer.AddButton(btn_ok) + btnsizer.AddButton(btn_cancel) + btnsizer.Realize() + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.mask1, 1, wx.EXPAND) + sizer.Add(self.op_boolean, 1, wx.EXPAND) + sizer.Add(self.mask2, 1, wx.EXPAND) + sizer.Add(btnsizer, 1, wx.EXPAND) + + self.SetSizer(sizer) + sizer.Fit(self) + + self.Centre() + + btn_ok.Bind(wx.EVT_BUTTON, self.OnOk) + + def OnOk(self, evt): + op = self.op_boolean.GetClientData(self.op_boolean.GetSelection()) + m1 = self.mask1.GetClientData(self.mask1.GetSelection()) + m2 = self.mask2.GetClientData(self.mask2.GetSelection()) + + print op, m1.name, m2.name + + Publisher.sendMessage('Do boolean operation', (op, m1, m2)) diff --git a/invesalius/gui/frame.py b/invesalius/gui/frame.py index 24ab96c..f6f645c 100644 --- a/invesalius/gui/frame.py +++ b/invesalius/gui/frame.py @@ -397,6 +397,9 @@ class Frame(wx.Frame): elif id == wx.ID_REDO: self.OnRedo() + elif id == const.ID_BOOLEAN_MASK: + self.OnMaskBoolean() + def OnSize(self, evt): """ Refresh GUI when frame is resized. @@ -490,10 +493,11 @@ class Frame(wx.Frame): Publisher.sendMessage('Redo edition') + def OnMaskBoolean(self): + print "Mask boolean" + Publisher.sendMessage('Show boolean dialog') - - # ------------------------------------------------------------------ # ------------------------------------------------------------------ # ------------------------------------------------------------------ @@ -605,6 +609,8 @@ class MenuBar(wx.MenuBar): #app(const.ID_EDIT_LIST, "Show Undo List...") ################################################################# + file_edit.Append(const.ID_BOOLEAN_MASK, _(u"\tBoolean operations")) + # VIEW #view_tool_menu = wx.Menu() -- libgit2 0.21.2